Closed 0xdeafcafe closed 2 years ago
The workaround is to map the join table to an entity.
class Product
{
public int Id { get; set; }
public ICollection<Categorization> Categorizations { get; set; }
}
// NOTE: Entity key should be (ProductId, CategoryId)
class Categorization
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
class Category
{
public int Id { get; set; }
public ICollection<Categorization> Categorizations { get; set; }
}
To navigate, use a Select:
// product.Categories
var categories = product.Categorizations.Select(c => c.Category);
Thanks!
@bricelam,
Are there plans to support many-to-many without mapping the intersection table?
Also, will there be support for marking related entities as added or deleted to indicate they should be removed from the relationship and not the table? With EF6 and prior the only way to do this was to dip down to the ObjectContext API and work with independent associations. Thanks!
Yes, many-to-many associations will eventually be enabled via shadow state entities (#749) and skip-level navigation properties (not sure if there's a bug for that; I'll re-open this one).
I'm not sure I follow your second question, but @divega or @ajcvickers will know if it'll be covered by our "graph behavior" improvements.
@tonysneed Re your second question, not sure I understand it either, but here is some data that might help:
We should have similar default behavior in EF Core as in previous versions of EF for the general case of removing an entity from a collection navigation property: this will cause the relationship to be marked as removed, not necessarily the entity.
Removing the relationship is promoted to an actual removal of the entity for identifying relationship only, i.e. when the primary key of the dependent entity contains the primary key of the principal entity and hence the dependent cannot be orphaned or re-parented. I believe we will have similar behavior for that too, although we have talked about deferring to the SaveChanges to detect if there are orphans entities in that state rather than trying to delete them immediately. @ajcvickers can elaborate/correct me on that.
If your question is about having API that allows to control the state of relationships without manipulating the collection navigation property directly on the entity, as described in https://entityframework.codeplex.com/workitem/271, then yes, I believe that would be something nice to have, however we haven't prioritized it. Your feedback would be helpful.
Does this help answer your question?
@bricelam, thanks for answering my first question. I'll be interested to learn more about implementing many-to-many via shadow state entities.
@divega, I'll clarify my second question. Let's say I have two entity classes that have a many-to-many relationship, for example, Employee and Territory from the Northwind sample database. If I wanted to add or remove a Territory from Employee.Territories, in a disconnected fashion using EF6, I would not be able to do so by setting EntityState on the Territory to Added or Deleted. Instead, I would call ObjectStateManager.ChangeRelationshipState, specifying the property name. Since ObjectContext is going away in EF Core, I'm just wondering how changing relationship state will work for entities in many-to-many relationships when performed in a disconnected manner (for example, from within a Web API controller). Hopefully this helps clarify my question a bit. Thanks!
Alight, I think I can clear everything up now that I've had a chance to try out a few things. The way to add or remove entities from a relationship in a disconnected fashion in vCurrent is not very straightforward or consistent when it comes to many-to-many relations. Stating that it is not possible without resorting to the OSM was incorrect. It can be done via the DbContext API but it's awkward. And what I'm wondering is if the API for EF Core could be improved to allow direct manipulation of the relationship, similar to what could be done via the OSM (albeit without needing to specify the navigation property name)?
For those following this conversation, here is what the OSM API looks like in vCurrent:
// Attach territory then set relationship state to added
context.Territories.Attach(territory);
var osm = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
osm.ChangeRelationshipState(employee, territory, "Territories", EntityState.Added);
So let's start with the vCurrent behavior of adding and removing entities from a m-m relation. Assuming that Employees and Territories have a m-m relationship, if I add a territory to an employee, then the territory will be marked as Added and EF will attempt to add it to the Territories table. If I want to just add the relationship to an existing territory, then I need to explicitly mark it as Unchanged, which makes sense but isn't all that intuitive. It's also inconsistent with the behavior for removing entities from a relationship, where removing a territory from an employee will not mark it as Deleted, without the need to explicitly mark it as Unchanged.
It seems to me that the remove behavior is a bit more straightforward. Adding a territory to the Territories collection of an employee could also leave it in an Unchanged state but result in adding it to the m-m relation. Then explicitly marking the entity as Added or Deleted would mean that you wanted to add to or remove from the Territories table as well as the m-m relation. This is the behavior that I would recommend for EF Core.
So this leads to the question of an API that would let you deal with the relationship directly. My question (which is hopefully clear by now) is whether could be a way in EF Core to change the state of a relationship. Following the proposal from CodePlex issue 271, it could look something like this:
context.Entry(employee).Member(e => e.Territories).Add(territory);
Even though may look somewhat intimidating, the important thing would be the ability to use the API from within the callback for AttachGraph (#1248), which is an Action
void AttachGraph(object root, Action<EntityEntry, DbMemberEntry, EntityEntry> callback)
I'm probably getting this wrong, but we would need a parameter that represents how the current entity being iterated is related to its parent. (Here DbMemberEntry represents the navigation property and the second EntityEntry represents the parent of the current entity.) That way, we could figure out what kind of relationship it is and use the correct approach to attach the entity. Please let me know if I'm making sense here. :)
Cheers, Tony
Just chiming in on this issue. Many-to-many is the one scenario that is blocking us from EF Core adoption. I realise that you can use intermediate mapping classes, but that requires significant changes to how we'd like to model things.
I note this is still tagged for the backlog. Is this feature on the roadmap for v1?
Sooo....gimme. Many-To-Many is probably the only thing left that I NEED in order to do what I want...I WANT more features, but many-to-many is all I NEED...probably.
@bricelam, @rowanmiller Will this feature make into the RTM? I did not see it in the list of upcoming feature in the latest blog post. For me this is the only remaining feature preventing a full migration from EF6 to EF Core.
@popcatalin81 you can have a many-to-many relationship but you need to include an entity that represents the join table. This is how it will be for our first stable release. In the future we will enable shadow state entities (entities that don't have a CLR type) and then you will be able to model it without a CLR class to represent the join table.
Note: see also #3352 where the user has a requirement to support a join table which defines the combination of the 2 foreign keys as unique, but does not provide a primary key - which makes defining an entity type for it impossible.
Regarding the original suggestion with the CLR class for join table, how should the "Categorization" class be used? Is it application's duty to create and delete instances of those class directly? Something like...
var product = new Product();
var category = new Category();
var categorization = new Categorization()
{
Product = product,
Category = category
};
dbContext.Add(product);
dbContext.Add(category);
dbContext.Add(categorization);
dbContext.SaveChanges();
And to remove a relationship preserving product and category:
dbContext.Remove(categorization);
dbContext.SaveChanges();
I'll add a +1 for wanting many-to-many relationships without CLR objects for the join table. I have a website using EF6 that I'm trying to move forward to EF Core and it would save some hassle changing the model on the CLR side to be able to use the direct many-to-many modeling option that EF6 had.
+1 for wanting "many-to-many relationships without CLR objects for the join table" from my side as well.
What's interesting is when a Many-To-Many relationship has additional attributes/columns (e.g. DateOfAssociation, Priority, etc.) it becomes an observed entity. I can see that consistency would drive towards creating an intermediate Entity and I'm comfortable with the current implementation.
With that, requiring that we implement an intermediate Many-To-Many entity forces our object model to conform to database implementation, whereas hiding this detail leaves it in the hands of the ORM; not requiring an explicit intermediate makes the relationship seem more natural and represents the majority of Many-To-Many relationships.
@sthiakos Well, it depends. Most of the data models and their relations I saw, do not need additional attributes/columns. In this cases the additional entity classes would just pollute the data model with unnecessary bloat. And depending on the size of a project, upgrading from EF6 could be a tremendously big task.
So I'm also voting for a solution without additional entity classes, and looking forward for the shadow state entities.
i'm not sure I understand how this feature request would work. Do you mean that no entity class is required but a table is still created and managed automatically by Entity? I'm asking because after some initial confusion in creating navigations, I'd say that handling many-to-many mapping entities manually is not that bad...
@bragma yep that is exactly what this feature is about. If the join table only contains the foreign key properties to each side of the relationship, then we want to allow folks not to have a join entity.
How exactly are you intended to go about creating an entity that has one or more many to many relationships, and adding to those before saving changes to the database?
How do I work with the join table, assuming it is initially empty for this new entity? I already have a list of currently existing entities I wish to add to it, but not sure how to approach.
@twilliamsgsnetx Given we don't support modelling this without a join entity just yet, it's the same as any other one-to-many relationship. In the following example, we're inserting a new Post, a new Tag, and an entry in the PostTag join entity/table that represents the many-to-many.
var blog = new Blog { ... };
context.Blogs.Add(blog);
var tag = new Tag { ... };
context.Tags.Add(tag);
var postTag = new PostTag { Tag = tag, Post = post };
context.PostsTags.Add(postTag);
context.SaveChanges();
Really waiting for this now i need to move my code back to EF6
Is there any progress on this? Cause I really would like this feature :D
@TomGroeneboer - this will be a post 1.0.0 feature. In the meantime, you can model many-to-many with a join entity http://docs.efproject.net/en/latest/modeling/relationships.html#many-to-many.
@rowanmiller - Been fiddling with this for a few days now and still have questions. I reviewed the last link to the docs here. Not really sure what the OnModelCreating code is doing except setting up for creating foreign keys in the SQL table. I'll try building it tomorrow. My current experience is based on a non-trivial app with at least two many:many, one of which is a simple join table with just the two foreign keys. But, as I review what you replied to @twilliamsgsnetx I am not clear how an anonymous type will automagically populate the join table if on one side of the many:many you are creating a new entity. In my case, the ID for the new entity is an auto-identity in the SQL table. (guessing that I'm going to need to return the new PK and use it to create the join table record)
Sorry if this is obvious to old EF hands, but the code first and EF Core approach are still pretty new to me. Working with RC1 bits ASPNET Core MVC project. Just trying to get in line with best practices.
Also, in the example in the docs, the navigation properties are written like this:
public List<PostTag> PostTags { get; set; }
as opposed to pre EF Core where I usually wrote them like this:
public virtual ICollection<PostTag> PostTags { get; set; }
Does that make any difference?
Lastly, in the example there is no property DbSetpublic DbSet<Post> Posts { get; set; } public DbSet<Tag> Tags { get; set; }
This means some operations, like say Seed, will not be able to call upon the many:many entity. Is there anything wrong with setting the DbSet property for the join table?
@bdelaney there really isn't anything here different than other entities, the many-to-many is just modelled as two separate one-to-many relationships. There is nothing magic that happens with the join entity, you need to add and remove instances of it the same as you would for other entities.
Also, in the example in the docs, the navigation properties are written like this:
public List<PostTag> PostTags { get; set; }
as opposed to pre EF Core where I usually wrote them like this:public virtual ICollection<PostTag> PostTags { get; set; }
Does that make any difference?
Using virtual
in EF6.x would give you lazy loading proxies, but lazy loading is not implemented in EF Core, so it makes no difference.
Lastly, in the example there is no property DbSet, just the two parent entities:
public DbSet<Post> Posts { get; set; } public DbSet<Tag> Tags { get; set; }
This means some operations, like say Seed, will not be able to call upon the many:many entity. Is there anything wrong with setting the DbSet property for the join table?
You can add it. The entity is discovered as part of the model because it is referenced from other entities that do have a DbSe
t. So totally up to you if it has a DbSet
or not. BTW you can use DbContext.Add()
to add entities that don't have a DbSet
exposed
Shouldn't this be a higher priority?
Especially for enterprise level applications that have a high number of many-to-many relationships.
It would be annoying to have to create two separate one-to-many relationships for each many-to-many relationship.
@BradleyDHobbs it's just a matter of ordering that features make sense to add to the new EF Core code base. This is a high priority feature, but some of the other things we are working on make more sense to implement first.
This is soooooooo needed!
High priority on this feature, please!
+1
Has anyone been able to get the many-to-many relations working using a join entity?
I have a simple scenario with 3 entities and 2 join tables:
join entities:
public class Member : Person
{
public List<MemberChildren> Children { get; set; }
public List<MemberSpouse> Spouses { get; set; }
}
setup tph in modelBuilder
modelBuilder.Entity<Person>().HasDiscriminator<string>("PersonType")
.HasValue<Member>("Member")
.HasValue<Child>("Child")
.HasValue<Spouse>("Spouse");
setup relations
//setup complex key for relationnal tables
modelBuilder.Entity<MemberChildren>()
.HasKey(t => new { t.ChildId, t.ParentId});
modelBuilder.Entity<MemberSpouse>()
.HasKey(t => new { t.MemberId, t.SpouseId});
//setup relations.. many to many
modelBuilder.Entity<MemberChildren>().HasOne(p => p.Parent).WithMany(p => p.Children)
.HasForeignKey(fk => fk.ParentId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
modelBuilder.Entity<MemberChildren>().HasOne(p => p.Child).WithMany(p => p.Parents)
.HasForeignKey(fk => fk.ChildId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
modelBuilder.Entity<MemberSpouse>().HasOne(p => p.Member).WithMany(p => p.Spouses)
.HasForeignKey(fk => fk.MemberId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
modelBuilder.Entity<MemberSpouse>().HasOne(p => p.Spouse).WithMany(p => p.Members)
.HasForeignKey(p => p.SpouseId)
.OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Restrict);
I can then add relations simply by using:
mem1.Children.Add(new MemberChildren() { Child = child1, Parent = mem1 });
mem1.Children.Add(new MemberChildren() { Child = child2, Parent = mem1 });
and if still in the same context (still in the using) I can fetch that information back
var member = context.Members.Include(c => c.Children).Where(m => m.Id == 1).FirstOrDefault();
var children = member.Children.Select(c => c.Child);
foreach(var child in children)
{
Console.WriteLine(child.FirstName);
}
But if I exit the console, rerun the same code above to fetch a member and its children, the children collection will be 2 null entries.
@lpmeunier Here's how I do it: (Works as of RC2)
We have an entity named User
that has many to many relations with 3 other entities:
public class User
{
public User()
{
UserClaims = new HashSet<UserClaim>();
UserPayments = new List<UserPayment>();
ShoppingOrders = new HashSet<ShoppingOrder>();
}
[Key]
public long UserId { get; set; }
[StringLength(256)]
[Required]
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
// other stuff
public ICollection<UserClaim> UserClaims { get; set; }
public ICollection<UserPayment> UserPayments { get; set; }
public ICollection<ShoppingOrder> ShoppingOrders { get; set; }
}
now for example for the relation with Claim
entity:
public class Claim
{
public Claim()
{
UserClaims = new HashSet<UserClaim>();
}
[Key]
public long ClaimId { get; set; }
// other stuff
public ICollection<UserClaim> UserClaims { get; set; }
}
and the join entity:
public class UserClaim
{
public long UserId { get; set; }
public User User { get; set; }
public long ClaimId { get; set; }
public Claim Claim { get; set; }
}
finally this should go in the OnModelCreating
of your db context class:
modelBuilder.Entity<UserClaim>().HasKey(t => new { t.UserId, t.ClaimId });
modelBuilder.Entity<UserClaim>()
.HasOne(pt => pt.User)
.WithMany(p => p.UserClaims)
.HasForeignKey(pt => pt.UserId);
modelBuilder.Entity<UserClaim>()
.HasOne(pt => pt.Claim)
.WithMany(t => t.UserClaims)
.HasForeignKey(pt => pt.ClaimId);
modelBuilder.Entity<UserClaim>().Property(pt => pt.Count);
@VSG24 how does fetching related entities look in your code?
For example,
var user = context.Users.Include(c => c.Claims).FirstOrDefault(u => u.Id == 3);
Do you then have access to:
var claims = user.Claims.Select(c => c.Claim);
make sure you have not created the user and claims in the current context instance IE: make sure this is fetched from the db in a fresh context.
My setup is pretty much exacly like yours, and the claims variable will return null for me.
The only difference is that im using tph on my entities..
@lpmeunier Get the user, also included related UserClaims and THEN include Claims:
var user = _db.Users.Include(d => d.UserClaims).
ThenInclude(d => d.Claim).First(d => d.UserId == 3);
And now I have access to claims.
var claims = user.UserClaims.Select(d => d.Claim);
@VSG24 Thanks a lot, the ThenInclude() extension did the trick!
Just wondering how is it possible in the identity repository, where the requirement of CLR classes in the join table was not needed...
If you notice there is a join table entity called IdentityUserRole which maps the entities IdentityUser and IdentityRole however this join table entity has no CLR object in its definition.
Currently moving my code into 1.0.0 to get many to many relationships working. Got it working on a small sample piece of code.
@Lutando You can have a relationship with a navigation property on only one side, so IdentityUser
has a Roles property (that contains IdentityUserRole
), but there is no navigation pointing back the other way. Here is the line of code that sets this up in their model.
@Lutando you may have already seen this, but here are the docs on setting up many-to-many https://docs.efproject.net/en/latest/modeling/relationships.html#many-to-many
@rowanmiller thanks for the heads up. I dont think I need a reverse nav prop in my use case, but that's good to know.
@rowanmiller If I set up the many-to-many model like the doc you recommended Post and Tag scenario how could I Remove a relationship (remove a tag from a post)? I mean how I could detach a tag from a post?
var user = _db.Users.Include(d => d.UserClaims).
ThenInclude(d => d.Claim).First(d => d.UserId == 3);
how to use Take & Skip on Claims before this line?
.First(d => d.UserId == 3);
Linq : (use Skip & Take)
var post1Tags = context.BlogPosts
.Where(blogPosts => blogPosts.Id == 1)
.SelectMany(blogPosts => blogPosts.BlogPostsJoinTags)
.Select(blogPostsJoinTags => blogPostsJoinTags.Tag)
.Skip(0)
.Take(10)
.ToList();
Sql Export :
SELECT [blogPosts.BlogPostsJoinTags.Tag].[Id],
[blogPosts.BlogPostsJoinTags.Tag].[Name]
FROM [BlogPosts] AS [blogPosts]
INNER JOIN
[BlogPostsJoinTags] AS [blogPosts.BlogPostsJoinTags]
ON [blogPosts].[Id] = [blogPosts.BlogPostsJoinTags].[BlogPostId]
INNER JOIN
[Tags] AS [blogPosts.BlogPostsJoinTags.Tag]
ON [blogPosts.BlogPostsJoinTags].[TagId] = [blogPosts.BlogPostsJoinTags.Tag].[Id]
WHERE [blogPosts].[Id] = 1
ORDER BY @@ROWCOUNT
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY;
I'm working on a library similar to GraphDiff for EF Core and added a very rudimentary patch that allows to implement simple many to many association scenarios like User -> Roles. It uses a generic ManyToManyEntity<,> as a Clr type and a convention to map table and property names. I know it is not optimal and doesn't support a lot of scenarios, but if you can adapt it for your needs, the project is here: EF Core Detached. It doesn't pollute the model, just need a [ManyToMany] attribute to be added to the property. BTW, EF core model metadata is just awesome! I would like to implement it directly on EF, but I'm not up to the task.
So curious about when, if ever, this is going to be implemented.
This is a complete blocker for our adoption of EntityFramework Core, and obviously a blocker for a lot of other teams based on the commentary. I recognize the complexity of the problem, but this, along with lazy loading, are the reason we've stayed away from core in general.
@nicklbailey thanks for your feedback. Although we have made a lot of progress, the comment @rowanmiller made at https://github.com/aspnet/EntityFramework/issues/1368#issuecomment-205406766 still applies: Many-to-many competes with other fundamental features we need to implement. We will use your feedback in our upcoming prioritization discussions.
Guys, I'm having some thoughts about the way I'm getting data from a many-to-many relationship. Could not find some examples besides de Blog/Post.
Given the classes
public sealed class Rule: BaseDomain
{
public List<Option> Options{ get; set; }
///Some More props
}
public sealed class Option: BaseDomain
{
///Some More props
}
public sealed class Menu : BaseDomain
{
[Required]
[JsonIgnore]
public ICollection<MenuDishes> MenuDishes { get; set; }
public List<Dish> Dishes { get; set; }
///Some More props
}
public sealed class MenuDishes
{
public int MenuId { get; set; }
public Menu Menu { get; set; }
public int DishId { get; set; }
public Dish Dish { get; set; }
}
public sealed class Dish: BaseDomain
{
[Required]
public Rule Rule{ get; set; }
[JsonIgnore]
public ICollection<MenuDish> MenuDishes { get; set; }
///Some More props
}
On My Db Context I have configured the relation:
modelBuilder.Entity<MenuDishes>()
.HasKey(t => new { t.MenuId, t.DishId });
modelBuilder.Entity<MenuDish>()
.HasOne(md=> md.Menu)
.WithMany(m => m.MenuDishes)
.HasForeignKey(md => md.MenuId);
modelBuilder.Entity<MenuDish>()
.HasOne(md => md.Dish)
.WithMany(d => d.MenuDishes)
.HasForeignKey(md => md.DishId);
The Menu class has the ICollection for Db storage and a List of dish to be returned by a web api, since the user don't need to know how i persist data. So, at my MenuDAO I do the following to get a full object
public Menu GetFull(int id)
{
var menu = Context.Menus
.Include(m => m.MenuDishes)
.Where(m => m.Id == id)
.FirstOrDefault();
menu.Dishes = menu.MenuDishes.Select(md =>
{
Context.Entry(md).Navigation(nameof(MenuDishes.Dish)).Load();
Context.Entry(md.Dish).Navigation(nameof(Dish.Rule)).Load();
return cp.Dish
}).ToList();
return menu;
}
And since MenuDishes is a collection I could't use the ThenInclude
approach. Am I doing something wrong? I sense some bad smell here. What if i had a deeper hierarchy? I would have to replicate the `Load()" as many time as goes my tree?
Insertion is going like this:
public override Menu Add(Menu instance)
{
instance.MenuDishes = instance.Dishes.Select(d =>
new MenuDish { Menu = instance, DishId = d.Id }
).ToList();
Context.Add(instance);
Context.SaveChanges();
return instance;
}
@rpedretti ThenInclude should work:
var menu = Context.Menus
.Include(m => m.MenuDishes).ThenInclude(e => e.Dish)
.Where(m => m.Id == id)
.FirstOrDefault();
If you are using Visual Studio, then there is a bug that makes intellisense show the wrong thing. See #4117 You may want to provide feedback on the Roslyn bug tracking this: https://github.com/dotnet/roslyn/issues/8237
Thank @ajcvickers, things are very smooth now!
I'm so stuck with this many to many relationship
The docs (https://docs.microsoft.com/en-us/ef/core/modeling/relationships) mention this limitation:
Many-to-many relationships without an entity class to represent the join table are not yet supported.
But don't provide any clues as to when this will be coming. Is there an ETA for this?
Note:
With regard to feedback, I think it is worth reiterating some comments made a few months ago. We read and consider all feedback (and try to make the right choices based on it) no matter how it is delivered; that is, whether it is provided politely and with respect or not. Yelling at us or others with different opinions at best makes you feel good and us/them feel bad. It doesn’t achieve anything concrete. We are doing our best to prioritize features and implement them in the best way we can. We really appreciate your feedback and it makes a big difference on shaping the product. We personally appreciate it more when feedback is delivered in a respectful way, but please don’t stop providing constructive feedback.
Original issue:
I'm assuming there isn't a way to do Many-to-Many relationships in EF Core yet, as I've found nothing mentioning how to do it. Are there any examples on getting round this? Or am I just completely missing it, and it's actually there after all.