Problem mapping multiple relations using Entity Framework code-first - c#

Scenario
I have a model that has the following setup:
A member can be member of multiple memberlists. A memberlist can have multiple members. I also defined that a memberlist can have a seperate set of optional members. Basically I have a double n-m relationship between member and memberlist. What's more, a memberlist is always owned by a member, the one person that created the memberlist that is.
Problem
Entity framework code-first is unable to map this relationship correctly eventhough I tell it how to map the relationships. I get the following error when I try to use the DbContext the first time
Schema specified is not valid. Errors:
(32,6) : error 0040: Type MemberList_Members is not defined in namespace NerdCooking.Models (Alias=Self).
(33,6) : error 0040: Type MemberList_OptionalMembers is not defined in namespace NerdCooking.Models (Alias=Self).
What I have tried
The first attempt to use the model in the scenario fails, because it's clear EF doesn't know how to map the n-m relationships. So I told the framework explicitly how to fix the situation.
// Map the relation between members of a memberlist and the memberlist
// to a Membership table
modelBuilder.Entity<MemberList>().HasMany(memberList => memberList.Members)
.WithMany(member => member.MemberLists)
.Map(mapping => mapping.MapLeftKey("MemberId")
.MapRightKey("MemberListId").ToTable("Membership"));
// Map the relation between optional members of a memberlist and the memberlist
// to a separate table
modelBuilder.Entity<MemberList>().HasMany(memberList => memberList.OptionalMembers)
.WithMany(member => member.MemberLists)
.Map(mapping => mapping.MapLeftKey("MemberId")
.MapRightKey("MemberListId").ToTable("OptionalMembership"));
// Map the relationship between the owner and the memberlist
modelBuilder.Entity<MemberList>().HasRequired(memberList => memberList.Owner)
.WithMany(member => member.MemberLists).WillCascadeOnDelete(true);
My question
Is this scenario possible with Entity framework 4.1 code-first? And if so, what's the best way to fix the mapping issue I'm experiencing?
I can always change the model so that it features a Membership entity that links a member to a memberlist. It makes the whole thing a bit more explicit and I can then add a property to that entity to mark it as optional.
I think however that this is a scenario that should have worked, so I hope someone did this before and knows what the trick is to get it working correctly.

All your mappings contain .WithMany(member => member.MemberLists). That is incorrect. You need separate navigation property for each relation = you can use each navigation property only for one relation mapping.

Related

Entity cannot be configured as keyless because it is a derived type

I have a Class ApplicationUser and a class vwUserConfig. vwUserConfig is a view in the database, and it has all columns of ApplicationUser including a few more.
I want, and need, vwUserConfig to inherit from ApplicationUser. But when I configure the view:
modelBuilder.Entity<ApplicationUser>().ToTable("AspNetUsers");
modelBuilder.Entity<VwUserConfig>().ToView("vwUserConfig");
I get an error indicating I cannot call .HasNoKey() since it inherits from ApplicationUser:
System.InvalidOperationException: 'VwUserConfig' cannot be configured as keyless because it is a derived type; the root type 'ApplicationUser' must be configured as keyless instead. If you did not intend for 'ApplicationUser' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder in 'OnModelCreating', or referenced from a navigation on a type that is included in the model.
If I remove .HasNoKey(), I get an error:
System.InvalidOperationException: Both 'VwUserConfig' and 'ApplicationUser' are mapped to the table 'AspNetUsers'. All the entity types in a hierarchy that don't have a discriminator must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
After looking into it a bit, I realized that for some reason, EF Core still thinks VwUserConfig should still be mapped to AspNetUsers, even though I'm calling .ToView(). To circumvent this, I had to do this hack:
modelBuilder.Entity<VwUserConfig>().ToTable("jcudncyd").ToView("vwUserConfig");
I had to assign it to a random, non-existing table for it to be un-mapped (?) to AspNetUsers. This seems like a bug.

Is it really necessary to ignore self-referencing loops while using many-to-many relationships in ef core?

I'm trying to set up a many-to-many relationship on ef core following this model:
https://stackoverflow.com/a/46184785/11234800
But every time I try to query a person with all its clubs from this relationship, as follows:
public async Task<IList<Person>> GetAll()
{
var query = _dbContext.Set<Person>()
.Include(pc => pc.PersonClubs).ThenInclude(c => c.Club)
.AsQueryable();
return await query.ToListAsync();
}
I end up running in a self-referencing loop error, which I solved by doing this: https://stackoverflow.com/a/34847316/11234800
Is it really necessary to ignore this type of error or there's a better way to solving this issue?
IMO the self-referencing loop issue is only a result from a poor design choice in your application.
I would recommend you not to return the entity which is connected to EF and instead return a model which represents the application endpoint interface, a data transfer object.
That object will only present the necessary data, which should be presented from that specific endpoint.
For example, if you return the Person entity as you do, is the relation property PersonClubsId really needed to represent a person?
Or for instance, if you have some metadata field like CreatedDate, CreatedBy. Those should most likely not be included.
Instead create your own class which will represent a Person with the properties that represents your entity in the best way.
Another reason why you should decouple the EF entity from your application endpoint interface is because if you make any changes in the db-structure for your Person entity,
those changes will be reflected upon the client as they both uses the same model.

How to map an entity for a table that already has a mapped entity and avoid EF conflicts?

I want to map a simplified read-only entity (e.g. for UI dropdowns, that need only id and name) to a table that already has full-feature entity mapped.
I have fairly typical mapping configurations using IEntityTypeConfiguration classes that map entities through EntityTypeBuilder<MyFullClass> and EntityTypeBuilder<MySimpleClass>.
I have no control over database, it's a legacy project and I cannot add new SQL views just to solve this code issue.
public class MyFullClassConfiguration : IEntityTypeConfiguration<MyFullClass>
{
public void Configure(EntityTypeBuilder<MyFullClass> builder)
{
builder.ToTable("MyTable");
... all properties mapped
public class MySimpleClassConfiguration : IEntityTypeConfiguration<MySimpleClass>
{
public void Configure(EntityTypeBuilder<MySimpleClass> builder)
{
builder.ToTable("MyTable");
... minimum of required properties mapped
When I run the project, I get an error:
Cannot use table 'MyTable' for entity type 'MySimpleClass' since it is being used for entity type 'MyFullClass' and there is no relationship between their primary keys.
I tried to artificially link both entities, adding one-to-one relation:
b.HasOne<MyFullClass>().WithOne().HasForeignKey<MySimpleClass>(e => e.Id);
This time the project was started normally, I could read and update entities, but when saving a new MyFullClass, EF threw:
The entity of type 'MyFullClass' is sharing the table 'MyTable' with entities of type 'MySimpleClass', but there is no entity of this type with the same key value that has been marked as 'Added'.
This seems so common scenario - to return simplified versions of complex entities for performance and bandwidth reasons, so I was surprised to discover that it's not supported in EF and that they will implement it only in v3, if I'm not mistaken: https://github.com/aspnet/EntityFrameworkCore/issues/15310
How do I solve this in .NET Core 2.2?

Get List of Entity Models in DbContext Entity Framework Core 2.1

I'm trying to find a way to get a List of all of the entity models in my DbContext. For instance, if I have two models called Customer and Invoice defined in C# which through code-first I created EF entities and a database, how do I now query the DbContext to get a List that has Customer and Invoice in it -- i.e., all of the entities in that context? I want to be able to call a method that returns a List of all the entities -- not the data, just a list of the entities.
It seems to me this should be so easy, but either it is not easy or I am missing something -- probably the latter. ;-).
Could someone please point me in the right direction? Thanks!!
You can use Model property to get the associated IModel, then GetEntityTypes method to enumerate all IEntityTypes. ClrType property of IEntityType will give you the associated class type, e.g.
DbContext db = ...;
var entityTypes = db.Model.GetEntityTypes().Select(t => t.ClrType).ToList();
IEntityType has many useful properties and (extension) methods for getting information about the primary/alternate keys, foreign keys, navigations, properties etc. in case you need them.
You can refer documetation at: https://learn.microsoft.com/en-us/ef/core/querying/related-data
For ex. If you have blogs and posts as two tables, then you can get related table as shown below. It depends on how the relations are present in the two tables.
You can also add the where clause to get only selected records.
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ToList();
}

Modelling polymorphic associations database-first vs code-first

We have a database in which one table contains records that can be child to several other tables. It has a "soft" foreign key consisting of the owner's Id and a table name. This (anti) pattern is know as "polymorphic associations". We know it's not the best database design ever and we will change it in due time, but not in the near future. Let me show a simplified example:
Both Event, Person, and Product have records in Comment. As you see, there are no hard FK constraints.
In Entity Framework it is possible to support this model by sublassing Comment into EventComment etc. and let Event have an EventComments collection, etc.:
The subclasses and the associations are added manually after generating the basic model from the database. OwnerCode is the discriminator in this TPH model. Please note that Event, Person, and Product are completely different entities. It does not make sense to have a common base class for them.
This is database-first. Our real-life model works like this, no problem.
OK. Now we want to move to code-first. So I started out reverse-engineering the database into a code first model (EF Power Tools) and went on creating the subclasses and mapping the associations and inheritance. Tried to connect to the model in Linqpad. That's when the trouble started.
When trying to execute a query with this model it throws an InvalidOperationExeception
The foreign key component 'OwnerId' is not a declared property on type 'EventComment'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.
This happens when I have bidirectional associations and OwnerId is mapped as a property in Comment. The mapping in my EventMap class (EntityTypeConfiguration<Event>) looks like this:
this.HasMany(x => x.Comments).WithRequired(c => c.Event)
.HasForeignKey(c => c.OwnerId);
So I tried to map the association without OwnerId in the model:
this.HasMany(x => x.Comments).WithRequired().Map(m => m.MapKey("OwnerId"));
This throws a MetaDataException
Schema specified is not valid. Errors:
(10,6) : error 0019: Each property name in a type must be unique. Property name 'OwnerId' was already defined.
(11,6) : error 0019: Each property name in a type must be unique. Property name 'OwnerId' was already defined.
If I remove two of the three entity-comment associations it is OK, but of course that's not a cure.
Some further details:
It is possible to create a working DbContext model ("code second") from the edmx by adding a DbContext generator item. (this would be a work-around for the time being).
When I export the working code-first model (with one association) to edmx (EdmxWriter) the association appears to be in the storage model, whereas in the original edmx they are part of the conceptual model.
So, how can I create this model code-first? I think the key is how to instruct code-first to map the associations in the conceptual model, not the storage model.
I personally stick with Database first when using EF on any schema that is this level of complexity. I have had issues with complex schemas in regards to code first. Maybe the newer versions are a little better, but worrying how to try and code complex relationships seems less straight forward then allowing the engine to generate it for you. Also when a relationship gets this complex I tend to avoid trying to generate it with EF and try and use stored procedures for easier troubleshooting of performance bottlenecks that can arise.

Categories

Resources