Nested entity collection - entity-framework

I've got a scenario where I'm trying to have a growable hierarchy of a same-type nested collection for a specific purpose and I'm using EF Core 2.2.
public class Group:Entity
{
public Group(Guid id):base(id)
{
}
...
public List<Group> SubGroups { get; set; }
}
public abstract class Entity
{
protected Entity(Guid id)
{
Id = id;
}
public Guid Id { get; private set; }
}
The goal is to save data like:
|-GrandParent Group
-Parent Group
|--Child1 Group
---GrandChild1 Group
|--Child2 Group
---GrandChild2 Group
ERROR
{System.InvalidOperationException: No suitable constructor found for entity type 'Group'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'guid' in 'Group(Guid guid)'.
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConstructorBindingConvention.Apply(InternalModelBuilder modelBuilder)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelBuilt(InternalModelBuilder modelBuilder)
Could you please let me know how I could achieve this?

The issue has nothing to do with nested collection, but the entity constructor, and is not reproduced with the sample from the question.
But the exception message
No suitable constructor found for entity type 'Group'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'guid' in 'Group(Guid guid)'.
indicates that in your real code you have used
public Group(Guid guid):base(guid)
The problem is the name of the parameter guid (instead of id). As explained in the Entity types with constructors (inside Some things to note):
The parameter types and names must match property types and names, except that properties can be Pascal-cased while the parameters are camel-cased.
In this case, the property is called Id, hence the parameter must be called id as in the post.

Related

Using a common class for inheritance and for a member (code-first)

I've having difficulties getting EF code-first to generate a database from the following entities:
public class Person : Animal
{
public int Id { get; set; }
public Animal Pet { get; set; }
}
public class Animal
{
public string Name { get; set; }
}
So conceptually a person is an animal with a name, and they have a pet that is also an animal with a name. My context contains an array of people but not animals, which is why Animal doesn't contain a key:
public DbSet<Person> People { get; set; }
If I try to create a database using code-first I get the following error:
System.Data.Entity.ModelConfiguration.ModelValidationException: One or more validation errors were detected during model generation:
MyProject.Database.Animal: : EntityType 'Animal' has no key defined. Define the key for this EntityType.
Animals: EntityType: EntitySet 'Animals' is based on type 'Animal' that has no keys defined.
If I remove the Pet field I get a table with Id and Name fields, which is my expected behavior. Similarly, if I remove the Animal inheritance I get a table with Id and Pet_Name fields, which is again my expected behavior. What I'm trying to get is a table with Id, Name and Pet_Name fields.
Can't help but feel I'm missing something very basic here, because I've done this on other ORMs without issue. Can anyone tell me how to do this with EF 6.2?
For anyone else that reads this in future EF treats classes as either Entities or Complex Types. Entities get their own table while complex types get their own fields added as fields to the classes of parents that contain them as properties. If you declare a class instance as a property of another then EF immediately assumes it's an Entity; if it sees you trying to use it as a base class in an inheritance hierarchy then it assumes it's a complex type. The error shown above occurs when EF has already erroneously assumed that the type is an Entity but you then try to use it as a complex type. Seems to me that EF shouldn't be making the assumption in the first place if the class has no key property, but there it is. The solution is to simply flag it as a complex type from the start in your OnModelCreating function:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ComplexType<Animal>();
base.OnModelCreating(modelBuilder);
}

How to I create an Entity Framework ObjectSet using a generic type?

I am connecting to a database-first dll using Entity Framework 6.2.0 and I am trying to get the primary key for a given Entity at runtime. I don't know the Entity type until runtime, which is why I'm trying to use reflection to get the primary key.
Using the following, I'm getting the error Mapping and metadata information could not be found for EntityType 'System.Type':
private string GetPrimaryKey<T>(T entity) where T : class
{
Context.DefaultContainerName = EFContainerName;
var ESet = Context.CreateObjectSet<T>().EntitySet;
return ESet.ElementType.KeyMembers.Select(k => k.Name).ToArray().First();
}
I've seen a lot of information on the Mapping and Metadata error, but not with System.Type and so I feel like it may be less of a mapping error and more the way I'm using the Generic Type parameter?
create abstract class like this
public abstract class EntityObject
{
public abstract Guid EntityKey { get; }
}
inherit this class from an entity object
public class Model: EntityObject
{
public Guid Id { get; set; }
public override Guid EntityKey => Id;
}
for use;
var primaryKey = (Model as EntityObject)?.EntityKey;

Can I have an self referencing inverse navigation property with optional foreign key?

ASP.NET Identity developers should recognize my renamed ApplicationUser class (now called User) derived from IdentityUser which has a Guid-based Id property. In my User class I have an optional self referencing foreign key (public Guid? ManagerId) and one simple matching navigation property (public User Manager). All that works. My problem is I want a second navigation property (DirectlyManagedUsers) and I can't figure out how to annotate it such that it will contain a collection of this User's directly managed users. I'd appreciate some help.
Here is my User class:
public class User : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager, string authenticationType)
{
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
return userIdentity;
}
public User() : base()
{
DirectlyManagedUsers = new List<User>();
}
public User(string userName) : base(userName)
{
DirectlyManagedUsers = new List<User>();
}
[ForeignKey(nameof(Manager))]
public Guid? ManagerId { get; set; }
[ForeignKey(nameof(ManagerId))]
public User Manager { get; set; }
[InverseProperty(nameof(Manager))]
public ICollection<User> DirectlyManagedUsers { get; set; }
}
I'm getting the following error on model generation:
One or more validation errors were detected during model generation:
User_DirectlyManagedUsers_Source_User_DirectlyManagedUsers_Target: : The types of all properties in the Dependent Role of a referential constraint must be the same as the corresponding property types in the Principal Role. The type of property 'ManagerId' on entity 'User' does not match the type of property 'Id' on entity 'User' in the referential constraint 'User_DirectlyManagedUsers'. The type of property 'ManagerId' on entity 'User' does not match the type of property 'Id' on entity 'User' in the referential constraint 'User_DirectlyManagedUsers'.
I know that has to do with the nullable Guid type of the ManagerId. So what do I do?
Okay, I figured out what was going wrong. I was using a nullable Guid (Guid?) as the type of my ManagerId. Actually in the ASP.NET Identity framework, the prebuilt Entity Framework IdentityUser class uses a string type for its Id property. This string property is set to the value of a new Guid converted to a string using .ToString(). Once I figured that out (and since I know that string is nullable) I simply changed the type of my ManagerId property to string and everything worked. So my problem was addressed by figuring out the right type in the Identity framework, not by annotating the property a different way for the Entity Framework. I am curious if anyone could answer the original question if the Id was not a nullable type.

Entity Framework table splitting - how to initialize lazy-loaded properties?

Using Entity Framework 6.0, I am attempting to implement table splitting to improve query performance on tables with columns that contain BLOB data. I have followed the recommendations in this tutorial and it does indeed work as described.
Here's a very simplified example of the entity classes that map to one of my split tables ...
public class MyEntity
{
public string Id { get; set; }
public virtual MyEntityContent Content { get; set; }
public string Name { get; set; }
}
public class MyEntityContent
{
public string Id { get; set; }
public virtual MyEntity Entity { get; set; }
public byte[] Blob { get; set; }
}
... and the corresponding configuration code in the associated DbContext implementation ...
modelBuilder.Entity<MyEntity>().HasKey(e => e.Id).ToTable("MyEntities");
modelBuilder.Entity<MyEntityContent>().HasKey(c => c.Id).ToTable("MyEntities");
modelBuilder.Entity<MyEntity>().HasRequired(e => e.Content).WithRequiredPrincipal(d => d.Entity);
Given that the lazy-loaded Content property is Required by Entity Framework, it seems sensible to initialize it to a default value in the constructor of the containing MyEntity class ...
public MyEntity()
{
Content = new MyEntityContent();
}
... which enables a new instance of the class to be created and partially populated, without the risk of an exception being thrown by forgetting to initialize the required property value:
var entity = new MyEntity {Id = "XXX", Name = "something"};
I typically use a similar technique to initialize collection properties on EF entities and it works fine. However, in the above scenario, this initialization in the constructor has an unexpected effect: when retrieving existing entity instances from the database, the database value in the lazy-loaded property is ignored in favor of the empty default value.
This seems illogical to me. Doesn't Entity Framework create an entity object by first calling its default constructor and then applying its own property values to the created instance? If so, this should overwrite my default Content property value with a new instance of MyEntityContent, based on database data. This is how it seems to work with lazy-loaded collection properties.
If it's not possible to do this in the way I am expecting, is there an alternative technique for initializing lazy-loaded properties?
Don't initialize virtual members and perhaps, if you have to, handle any exceptions from uninitialized members.
I just had this issue with an entity with two virtual fields. Originally I had it initialize those two, but after removing them (and initializing the other fields to some default value), it started working for me. Try it out and let me know!
[Edit] I just realized I replied this to a slightly old post, didn't see the date. I guess I'll leave this answer here in case.

Entity Framework, LINQ and Generics

I have the following code:
public interface IKeyed<TKey>
{
TKey Id { get; }
}
// This is the entity framework generated model. I have added the
// IKeyed<Guid> interface
public partial class Person : IKeyed<Guid>
{
public Guid Id { get; set; }
}
public class Repository<TKey, TEntity> : IKeyedRepository<TKey, TEntity>
where TEntity : class, IKeyed<TKey>
{
private readonly IObjectSet<TEntity> _objectSet;
public Repository(IOjectSet<TEntity> objectSet)
{
_objectSet = objectSet;
}
public TEntity FindBy(TKey id)
{
return _objectSet.FirstOrDefault(x => x.Id.Equals(id));
}
}
[Update]
Here is how I am calling this:
Db2Entities context = new Db2Entities(_connectionString); // This is the EF context
IObjectSet<Person> objectSet = context.CreateObjectSet<Person>();
IKeyedRepository<Guid, Person> repo = new Repository<Guid, Person>(objectSet);
Guid id = Guid.NewGuid();
Person person = repo.FindBy(id); // This throws the exception.
The above code compiles. When the 'FindBy' method is executed, I get the following error:
Unable to create a constant value of type 'Closure type'. Only primitive types (for instance Int32, String and Guid) are supported in this context.
Since the type of my 'Id' is a Guid (one of the primitive types supported) it seems like I should be able to massage this into working.
Anyone know if this is possible?
Thanks,
Bob
It doesn't work this way. You cannot call Equals because EF doesn't know how to translate it to SQL. When you pass expression to FirstOrDefault it must be always only code which can be translated to SQL. It is probably possible to solve your problem with some manual building of expression tree but I can reference other solutions already discussed on Stack Overflow.
ObjectContext offers method named GetObjectByKey which is exactly what you are trying to do. The problem is that it requires EntityKey as parameter. Here are two answers which show how to use this method and how to get EntityKey:
Entity Framework Simple Generic GetByID but has differents PK Name
generic GetById for complex PK
In your case the code will be less complicated because you know the name of the key property so you generally need only something like this:
public virtual TEntity FindBy(TKey id)
{
// Build entity key
var entityKey = new EntityKey(_entitySetName, "Id", key);
// Query first current state manager and if entity is not found query database!!!
return (TEntity)Context.GetObjectByKey(entityKey);
}
The problem here is that you cannot get entitySetName from IObjectSet so you must either pass it to repository constructor or you must pass ObjectSet.
Just in case you will want to use DbContext API (EFv4.1) in the future instead of ObjectContext API it will be much simplified because DbSet offers Find method:
generic repository EF4 CTP5 getById