-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Many-to-many (skip) navigation properties #19003
Comments
Implementation proposalFluent APImodelBuilder.Entity<Tag>()
.HasMany(e => e.Posts)
.WithMany(e => e.Tags)
.UsingEntity<PostTag>(
pt => pt
.HasOne()
.WithMany(e => e.PostTags),
pt => pt
.HasOne()
.WithMany(e => e.PostTags))
.ToTable("PostTags"); This is the signature: EntityTypeBuilder<TAssociationEntity> UsingEntity<TAssociationEntity>(
Func<EntityTypeBuilder<TAssociationEntity>, ReferenceCollectionBuilder<TLeftEntity, TAssociationEntity>> configureLeft,
Func<EntityTypeBuilder<TAssociationEntity>, ReferenceCollectionBuilder<TRightEntity, TAssociationEntity>> configureRight) Plus nested closure, non-generic and string-based overloads This shape has the following advantages:
Disadvantages:
Metadatapublic interface ISkipNavigation : IPropertyBase
{
IEntityType DeclaringEntityType { get; }
IEntityType TargetEntityType { get; }
IForeignKey ForeignKey { get; }
IForwardedNavigation Inverse { get; }
bool IsCollection { get; }
bool IsOnPrincipal { get; }
(Other methods with default implementations)
} Design limitations
Scoping limitations
|
Here are some comments (remember I'm a beginner here so be patient, there may be nonsense ahead :)):
Is this only a limitation of the proposed fluent API, i.e. would users be able to drop down to metadata to set up a forwarded navigation spanning more than one entity type? Here's a different proposal for a fluent API. There are probably several reasons why this makes no sense, am proposing this mainly to learn: modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.Through(p => p.PostTags)
.ThenThrough(pt => pt.Tag)
.WithMany(t => t.Posts)
.Through(t => t.PostTags)
.ThenThrough(pt => pt.Post); The Through/ThenThrough (can be called Using/Via/Whatever) allows skipping over more than one entity type and is consistent with Include/ThenInclude (and OrderBy). It's also defined based on the paths, from one side all the way to the other, rather than putting the join table in the center. This also may allow for some wackier setups, e.g. where the paths in the two directions don't go through the same entity types (no concrete/compelling scenario yet though :)). |
I think an entity that is joining two other entities also makes sense in non-relational context and has the benefit of being familiar in relational context. Intermediate is also ok, but is too long for my liking.
No, it's optional
After you call
For shared type entity types it would be more verbose as the Type and entity type name would need to be specified again.
No, in metadata
A big downside is that it doesn't allow to configure FK and principal key properties |
Right. What I meant to say is that Using returning an EntityTypeBuilder could be problematic, as using may assume the can configure the original entity type instead of the join table's entity type. In your example above, it kinda looks like the ToTable affects the Tag entity rather than the PostTag entity.
I guess there's one fundamental thing I'm misunderstanding... I was assuming that the basic navigations (Post.PostTags and Tag.PostTags) need to be configured independently and separately, and that the forwarded navigation is only configured on top of other existing (underlying) navigations. If that were the case, it seems like the forwarded navigation wouldn't need to deal with FK/PK properties, just reference the underlying navigations. In metadata, the IForwardedNavigation would then effectively contain a list of underlying navigations (which is where the FK/PK info would be taken from). |
Yes, that's what happens in the metadata, but we can add Fluent API that circumvents that restriction to allow terser configuration. We don't yet have shared-type entity types, but if we did separate configuration would look something like this: modelBuilder.Entity<Tag>()
.HasMany(e => e.PostTags, "PostTag")
.WithOne()
.HasForeignKey(e => e.TagId);
modelBuilder.Entity<Post>()
.HasMany(e => e.PostTags, "PostTag")
.WithOne()
.HasForeignKey(e => e.PostId);
modelBuilder.Entity<IDictionary<string, object>>("PostTag")
.HasKey("TagId", "PostId")
.ToTable("PostTags");
modelBuilder.Entity<Tag>()
.HasMany(e => e.Posts)
.WithMany(e => e.Tags)
.UsingEntity<IDictionary<string, object>>(
pt => pt
.HasOne()
.WithMany(e => e.PostTags),
pt => pt
.HasOne()
.WithMany(e => e.PostTags),
"PostTag"); As opposed to modelBuilder.Entity<Tag>()
.HasMany(e => e.Posts)
.WithMany(e => e.Tags)
.UsingEntity<IDictionary<string, object>>(
pt => pt
.HasOne()
.WithMany(e => e.PostTags)
.HasForeignKey("PostId"),
pt => pt
.HasOne()
.WithMany(e => e.PostTags)
.HasForeignKey("TagId"),
"PostTag")
.ToTable("PostTags")
.HasKey("TagId", "PostId"); |
It cannot be totally based on navigations since navigations could be missing in CLR type. Especially if we are going to use property bag entity to represent join entity in future so API needs to use FKs. Questions for @AndriySvyryd
Still thinking about how to integrate shared entityType configuration if any so that we are future proof. |
The string is necessary to name the shared-type entity type.
Navigations are optional.
Fixed |
I will present this in design meeting on Wednesday. |
* MemberEntry support for skip navigations Part of #19003
Hey, is there plans to add scaffolding for many-to-many relationships? It doesn't look like it's included in the tasks for this issue, and I couldn't see any other issues tracking it. |
@neoGeneva Detecting existing join tables in reverse engineering is not something we have done--thanks for bringing this up. I have filed #22475 to track this. |
Awesome, thanks @ajcvickers ! |
Hello, is there a way to avoid defining the property for one of the entities? builder.HasMany(p => p.Tags).WithMany(); // notice no parameter in `WithMany` Thank you. |
@gojanpaolo Not yet #3864 |
Hello, Well, I will try to explain this using the concept of responsible: I can have responsibles for an activity, for a place, for a person, etc... So, i want to map this relationship on a table called ResponsibleContext which will have three properties: ResponsibleId, ContextId and ContextType. Then, I had made a mapping to the first case using the suggested syntax.
It worked well. The skip navigation 'Person.Responsibles' doesn't have an inverse navigation configured. Every skip navigation should have an inverse skip navigation. It seems that when i made the first configuration EF has removed the ResponsibleContext table from being able to be mapped for another cases. My real scenario was with PhoneContext with the following mappings on each IEntityTypeConfiguration For a Place which have ISet Phone I Used:
For a Person which have ISetPhone I Used:
`public class Telefone : FullAuditedEntity, ITelefone
}` The JsonIgnore and IgnoreDataMember are there only because swagger falls into self referency loop. and this is the way i found to avoid that. And, all of that works fine until i add a second case. |
Now? |
Yes |
This is one of the building blocks for many-to-many. However, it is also more broadly useful than just many-to-many.
Problem
Consider a model typical for a many-to-many relationship:
Now consider how to load a Post and include all associated Tags. This requires using two navigation properties--one to go from Post to PostTag, and a second to go from PostTag to Post. For example:
Likewise, querying across Posts and Tags also requires use of PostTags:
Proposal
The goal of forwarded navigation properties is to allow navigation properties to be defined which forward to some of the direct navigation properties in the model.
For example, forwarded navigation properties could be added to Post and Tag in the model above:
These forwarded navigation properties can then used to write simpler queries. For example, the queries from above can now be:
Notes
Things to consider (non-exhaustive):
Tasks:
The text was updated successfully, but these errors were encountered: