User-Friend relationship
I find an answer
Entity Framework Core: many-to-many relationship with same entity
and try like this.
Entitys:
public class User
{
public int UserId { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
public class Friend
{
public int MainUserId { get; set; }
public User ManUser { get; set; }
public int FriendUserId { get; set; }
public User FriendUser { get; set; }
}
The fluent API:
modelBuilder.Entity<Friend>()
.HasKey(f => new { f.MainUserId, f.FriendUserId });
modelBuilder.Entity<Friend>()
.HasOne(f => f.ManUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.MainUserId);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.FriendUserId);
When I Add-Migration, the error message is
Cannot create a relationship between 'User.Friends' and 'Friend.FriendUser', because there already is a relationship between 'User.Friends' and 'Friend.ManUser'.
Navigation properties can only participate in a single relationship.
What should I do? Or I should create an Entity FriendEntity:User?
The problem is that you can't have one collection to support both one-to-many associations. Friend has two foreign keys that both need an inverse end in the entity they refer to. So add another collection as inverse end of MainUser:
public class User
{
public int UserId { get; set; }
public virtual ICollection<Friend> MainUserFriends { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
And the mapping:
modelBuilder.Entity<Friend>()
.HasKey(f => new { f.MainUserId, f.FriendUserId });
modelBuilder.Entity<Friend>()
.HasOne(f => f.MainUser)
.WithMany(mu => mu.MainUserFriends)
.HasForeignKey(f => f.MainUserId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.FriendUserId);
One (or both) of the relationships should be without cascading delete to prevent multiple cascade paths.
It's not mandatory the second collection. You only need to left de .WithMany() empty like this:
modelBuilder.Entity<Friend>()
.HasOne(f => f.MainUser)
.WithMany()
.HasForeignKey(f => f.MainUserId);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany()
.HasForeignKey(f => f.FriendUserId);
look at this : https://github.com/aspnet/EntityFramework/issues/6052
Related
I have a table called contracts which has the id of entity 1 and entity 2. I use this to create links between entities, however I can't seem to get the definition of the dbContext correct. I can get the contracts to load but only one entity. When I view the contract either Entity1 is loaded or Entity2 is loaded but never both.
The models look like this:
public class Contract
{
public int Entity1ID { get; set; }
public int Entity2ID { get; set; }
public Entity Entity1 { get; set; }
public Entity Entity2 { get; set; }
}
My entity class looks like this
public class Entity
{
public int ID { get; set; }
public ICollection<Contract> Contracts1 { get; set; };
public ICollection<Contract> Contracts2 { get; set; };
}
This is my dbContext
public class EntityDbContext : DbContext
{
public EntityDbContext(DbContextOptions<EntityDbContext> options)
: base(options)
{
}
public DbSet<Models.Entity> Entities { get; set; }
public DbSet<Models.Contract> Contracts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasKey(n => n.ID);
modelBuilder.Entity<Contract>()
.HasKey(n => new { n.Entity1ID, n.Entity2ID });
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity1)
.WithMany(n => n.Contracts1)
.HasForeignKey(n => n.Entity1ID);
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity2)
.WithMany(n => n.Contracts2)
.HasForeignKey(n => n.Entity2ID);
}
}
From your model design , there is a self-referencing many-to-many relationship in Entity model , so you should change DeleteBehavior to Restrict(the default is cascade) like below:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasKey(n => n.ID);
modelBuilder.Entity<Contract>()
.HasKey(n => new { n.Entity1ID, n.Entity2ID });
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity1)
.WithMany(n => n.Contracts1)
.HasForeignKey(n => n.Entity1ID)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity2)
.WithMany(n => n.Contracts2)
.HasForeignKey(n => n.Entity2ID)
.OnDelete(DeleteBehavior.Restrict);
}
For loading related data , you could use the Include method that Sharif suggested above to specify related data to be included in query results
var result = _context.Contracts
.Include(c => c.Entity1)
.Include(c => c.Entity2)
.ToList();
Result
I have never been in the situation where I originally had two many-to-many's that were resolved. But then down another level is another many-to-many on both of those join tables. I am wondering if there is a better way to architect this w/ entity framework core. Here is what I got. I am trying to figure out which Project Owners are part of which Project Products.
I have 3 tables:
Project
Product
Owner
A Project can have many Products and a Project can have many Owners. A Product can have many Projects and an Owner can have many Projects. I resolved these two many-to-many relationships by doing the following:
ProjectProduct
Two keys: ProjectId, ProductId
ProjectOwner
Two keys: ProjectId, OwnerId
Additionally a ProjectProduct can have many ProjectOwners and a ProjectOwner can have many ProjectsProducts.
I thought the solution would be to add a unique Id to both ProjectOwner and ProjectProduct and create a new entity called ProjectProductOwner with the following keys:
ProjectProductId, ProjectOwnerId
Here is what my DBContext looks like:
// Key Specifications
modelBuilder.Entity<ProjectProductOwner>()
.HasKey(x => new { x.ProjectProductId, x.ProjectOwnerId });
// Project Product
modelBuilder.Entity<ProjectProduct>()
.HasOne(x => x.Project)
.WithMany(x => x.Products)
.HasForeignKey(x => x.ProjectId);
modelBuilder.Entity<ProjectProduct>()
.HasOne(x => x.Product)
.WithMany(x => x.Projects)
.HasForeignKey(x => x.ProductId);
// Project Owner
modelBuilder.Entity<ProjectOwner>()
.HasOne(x => x.Project)
.WithMany(x => x.Owners)
.HasForeignKey(x => x.ProjectId);
modelBuilder.Entity<ProjectOwner>()
.HasOne(x => x.Owner)
.WithMany(x => Projects)
.HasForeignKey(x => x.OwnerId);
// Project Product Owner
modelBuilder.Entity<ProjectProductOwner>()
.HasOne(x => x.ProjectProduct)
.WithMany(x => x.ProjectOwners)
.HasForeignKey(x => x.ProjectProductId);
modelBuilder.Entity<ProjectProductOwner>()
.HasOne(x => x.ProjectOwner)
.WithMany(x => x.ProjectProducts)
.HasForeignKey(x => x.ProjectOwnerId);
I am getting the error:
Introducing FOREIGN KEY constraint 'FK_ProjectProductOwner_ProjectProducts_ProjectProductId' on table 'ProjectProductOwner' may cause cycles or multiple cascade paths.
additionally:
'FK_ProjectProductOwner_ProjectOwners_ProjectOwnerId' on table 'ProjectProductOwner' may cause cycles or multiple cascade paths.
You can "merge" the ProjectProduct with the ProjectProductOwner into one table. Since you add Owners to the Project first, then you add one or more Products for each Owner in the Project I don't see a need for a third many-to-many Entity, thus simplifying it a bit :)
Note: I didn't even bother with the error, because as you said and I agree, most often these errors show up when your model isn't correct.
Owner
public class Owner
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProjectOwner> ProjectOwners { get; set; }
public ICollection<ProjectProductOwner> ProjectProductOwners { get; set; }
}
Product
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProjectProductOwner> ProjectProductOwners { get; set; }
}
Project and many-to-many tables
public class Project
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProjectOwner> ProjectOwners { get; set; }
public ICollection<ProjectProductOwner> ProjectProductOwners { get; set; }
public Project()
{
ProjectOwners = new List<ProjectOwner>();
ProjectProductOwners = new List<ProjectProductOwner>();
}
}
public class ProjectOwner
{
public int OwnerId { get; set; }
public Owner Owner { get; set; }
public int ProjectId { get; set; }
public Project Project { get; set; }
}
public class ProjectProductOwner
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int OwnerId { get; set; }
public Owner Owner { get; set; }
public int ProjectId { get; set; }
public Project Project { get; set; }
}
DbContext configuration
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure Owners in a Project
modelBuilder.Entity<ProjectOwner>()
.HasKey(p => new { p.ProjectId, p.OwnerId });
modelBuilder.Entity<ProjectOwner>()
.HasOne(bc => bc.Project)
.WithMany(b => b.ProjectOwners)
.HasForeignKey(bc => bc.ProjectId);
modelBuilder.Entity<ProjectOwner>()
.HasOne(bc => bc.Owner)
.WithMany(c => c.ProjectOwners)
.HasForeignKey(bc => bc.OwnerId);
// Configure Products for each owner in a Project
modelBuilder.Entity<ProjectProductOwner>()
.HasKey(p => new { p.ProjectId, p.ProductId, p.OwnerId });
modelBuilder.Entity<ProjectProductOwner>()
.HasOne(bc => bc.Project)
.WithMany(b => b.ProjectProductOwners)
.HasForeignKey(bc => bc.ProjectId);
modelBuilder.Entity<ProjectProductOwner>()
.HasOne(bc => bc.Product)
.WithMany(c => c.ProjectProductOwners)
.HasForeignKey(bc => bc.ProductId);
modelBuilder.Entity<ProjectProductOwner>()
.HasOne(bc => bc.Owner)
.WithMany(c => c.ProjectProductOwners)
.HasForeignKey(bc => bc.OwnerId);
}
Finally, you can add new projects and query it with something like this:
using (var db = new ProjectContext())
{
var project = new Project();
project.Name = "My project";
// assumes there's 3 owners and 2 products already inserted in the DB
// Add the 3 owners to the project
project.ProjectOwners.Add(new ProjectOwner { OwnerId = 1});
project.ProjectOwners.Add(new ProjectOwner { OwnerId = 2});
project.ProjectOwners.Add(new ProjectOwner { OwnerId = 3});
// Add Product 1 to Owner 1 and 2
project.ProjectProductOwners.Add(new ProjectProductOwner { ProductId = 1, OwnerId = 1 });
project.ProjectProductOwners.Add(new ProjectProductOwner { ProductId = 1, OwnerId = 2 });
// Add Product 2 to Owner 1 and 3
project.ProjectProductOwners.Add(new ProjectProductOwner { ProductId = 2, OwnerId = 1 });
project.ProjectProductOwners.Add(new ProjectProductOwner { ProductId = 2, OwnerId = 3 });
db.Add(project);
db.SaveChanges();
var projects = db.Project
.Include(p => p.ProjectOwners)
.ThenInclude(p => p.Owner)
.Include(p => p.ProjectProductOwners)
.ThenInclude(p => p.Product)
.FirstOrDefault();
}
I have an entity which has a self reference such that a Member can have a Witness who has to be a member and may also have a Reference who has to be a member. I modeled this as follows;
public class Member
{
public int Id { get; set; }
//omitted for brevity
public int? WitnessId { get; set; }
public virtual Member Witness { get; set; }
public int? ReferenceId { get; set; }
public virtual Member Reference { get; set; }
}
When I run the update-database on package manager console, I get the following error:
"XXX.Client.Entities.Member' and 'XXX.Client.Entities.Member'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations."
Any idea on how this can be resolved?
Try to define relationship with fluent api this way (works for me):
modelBuilder.Entity<Member>().HasKey(x => x.Id);
modelBuilder.Entity<Member>().HasOptional(x => x.Witness)
.WithMany()
.HasForeignKey(m => m.WitnessId);
modelBuilder.Entity<Member>().HasOptional(x => x.Reference)
.WithMany()
.HasForeignKey(m => m.ReferenceId);
This looks to be working:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Member>().
HasOptional(e => e.Witness).
WithMany().
HasForeignKey(m => m.WitnessID);
modelBuilder.Entity<Member>().
HasOptional(e => e.Reference).
WithMany().
HasForeignKey(m => m.ReferenceID);
base.OnModelCreating(modelBuilder);
}
This will also work for those of us who prefer to have things in the class deriving from the EntityTypeConfiguration
class MemberEntityConfiguration : EntityTypeConfiguration<Member>
{
public MemberEntityConfiguration()
{
HasKey(x => x.Id);
HasOptional(x => x.Witness).WithMany().HasForeignKey(m => m.WitnessId);
HasOptional(x => x.Reference).WithMany().HasForeignKey(m => m.ReferenceId);
}
}
I'm new to Entity Framework and have come across a problem while trying to map my entity.
Basically I have a Location entity which can have an optional parent location. So what I'd like on my Location object is to have a collection of child locations along with the parent of the current location. Below is my current Location entity:
public class Location : BaseEntity
{
private ICollection<Location> _childLocations;
public virtual ICollection<Location> ChildLocations
{
get { return _childLocations ?? (_childLocations = new List<Location>()); }
set { _childLocations = value; }
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Location ParentLocation { get; set; }
}
However, when it comes to mapping this, I'm getting pretty lost. The below is my attempt so far:
public partial class LocationMap : EntityTypeConfiguration<Location>
{
public LocationMap()
{
this.ToTable("Location");
this.HasKey(l => l.Id);
this.Property(l => l.Name).HasMaxLength(100);
this.HasMany(l => l.ChildLocations)
.WithMany()
.Map(m => m.ToTable("Location"));
this.HasOptional(l => l.ParentLocation)
.WithOptionalDependent()
.Map(m => m.ToTable("Location"));
}
}
Could anyone point me in the right direction?
You want something like:
this.HasOptional(l => l.ParentLocation)
.WithMany(l => l.ChildLocations)
.Map(m => m.ToTable("Location"));
But not two declarations of the relationship, ie the above replaces both of the below in your example
this.HasMany(l => l.ChildLocations)
.WithMany()
.Map(m => m.ToTable("Location"));
this.HasOptional(l => l.ParentLocation)
.WithOptionalDependent()
.Map(m => m.ToTable("Location"));
UPDATE: After a bit more research it seems a number of my many-to-many mappings aren't working. Hmmm...
I'm upgrading a data access project from EF 4.1 CTP4 to EF 4.1 RC and I'm having trouble with the new EntityTypeConfiguration<T> setup.
Specifically I'm having an issue with a Many-to-Many relationship. I'm getting a Sequence contains no elements exception when I'm trying to get the .First() item.
The particular exception isn't really that interesting. All it's saying is that there are no items BUT I know there should be items in the collection - so there must be an issue with my new mappings.
Here's the code I have so far:
Product Model
public class Product : DbTable
{
//Blah
public virtual ICollection<Tag> Categories { get; set; }
public Product()
{
//Blah
Categories = new List<Tag>();
}
}
BaseConfiguration
public class BaseConfiguration<T> : EntityTypeConfiguration<T> where T : DbTable
{
public BaseConfiguration()
{
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.UpdatedOn);
this.Property(x => x.CreatedOn);
}
}
ProductConfiguration
public class ProductConfiguration : BaseConfiguration<Product>
{
public ProductConfiguration()
{
this.ToTable("Product");
//Blah
this.HasMany(x => x.Categories)
.WithMany()
.Map(m =>
{
m.MapLeftKey("Tag_Id");
m.MapRightKey("Product_Id");
m.ToTable("ProductCategory");
});
}
}
Previous CTP4 Mapping the worked!
this.HasMany(x => x.Categories)
.WithMany()
.Map("ProductCategory", (p, c) => new { Product_Id = p.Id, Tag_Id = c.Id });
Can anyone see anything that needs fixing? Let me know if you want me to provide more code.
EDIT: More Code
DbTable
public class DbTable : IDbTable
{
public int Id { get; set; }
public DateTime UpdatedOn { get; set; }
public DateTime CreatedOn { get; set; }
}
Tag
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public string Slug { get; set; }
public bool Visible { get; set; }
public virtual TagType TagType { get; set; }
}
TagConfiguration
public class TagConfiguration : EntityTypeConfiguration<Tag>
{
public TagConfiguration()
{
this.ToTable("Tags");
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("tag_id");
this.Property(x => x.Name).HasMaxLength(300).HasColumnName("tag_name");
this.Property(x => x.Slug).HasMaxLength(500).HasColumnName("tag_slug");
this.Property(x => x.Visible).HasColumnName("tag_visible");
this.HasRequired(x => x.TagType).WithMany(tt => tt.Tags).Map(m => m.MapKey("tagtype_id"));
}
}
Yes, this is a legacy database with naming conventions up to boohai.
I know the Tag class must be wired up correctly because Product has another property Specialization which is also mapped to Tag and it loads correctly. But do note that it's mapped in a one-to-many manner. So it seems to be the many-to-many with Tag.
I'll start checking out if any many-to-many associations are working.
You need to specify both navigation properties to do many to many mapping.
Try adding the lambda in the WithMany property pointing back to the products:
this.HasMany(x => x.Categories)
.WithMany(category=>category.Products)
.Map(m =>
{
m.MapLeftKey(t => t.TagId, "Tag_Id");
m.MapRightKey(t => t.ProductId, "Product_Id");
m.ToTable("ProductCategory");
});
(crossing fingers...)
I haven't used the Code-First approach yet, but when working with POCOs I had to enable Lazy-Loading, to make Navigation Properties work. This is of course by design, but I don't know if you have to explicitly enable this behavior for Code-First.