I have an entity that excludes entities of the same type under certain conditions. In order to achieve this, I have an entity class like:
public class Entity
{
public int ID { get; set; }
public virtual ICollection<EntityExcludedEntity> ExcludedEntities { get; set; }
}
public class ExcludedEntity
{
public int ID { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
public int EntityID { get; set; }
public virtual Entity Entity { get; set; }
public int ExcludedEntityID { get; set; }
public virtual Entity ExcludedEntity { get; set; }
}
//declared in the ExcludedEntity mapping class.
public ExcludedEntityMapping()
{
HasRequired(t => t.Entity).WithMany(t => t.ExcludedEntity).HasForeignKey(t => t.EntityID)
HasRequired(t => t.ExcludedEntity).WithMany(t => t.ExcludedEntity).HasForeignKey(t => t.ExcludedEntityID);
}
This causes in EF creating a third column and foreign key field called Entity_ID in my model. Seems like it thinks I have another relationship defined here but I don't understand why.
Here is the part related to foreign keys in the tables created:
.ForeignKey("dbo.Entities", t => t.EntityID)
.ForeignKey("dbo.Entities", t => t.ExcludedEntityID)
.ForeignKey("dbo.Entities", t => t.Entity_ID)
This post helped me find the answer.
Basically, EF cannot have two foreign keys to the same entity field. If you need to create two foreign key to the same entity you should bind them to different fields. So in this example:
public class Entity
{
public int ID { get; set; }
public virtual ICollection<EntityExcludedEntity> ExcludingEntities { get; set; }
public virtual ICollection<EntityExcludedEntity> ExcludedFromEntities { get; set; }
}
and this configuration:
public DBConceptAnswerExcludedAnswerMapping()
{
HasRequired(t => t.Entity).WithMany(t => t.ExcludingEntities).HasForeignKey(t => t.EntityID);
HasRequired(t => t.ExcludedEntity).WithMany(t => t.ExcludedFromEntities).HasForeignKey(t => t.ExcludedEntityID);
}
would solve the problem.
Related
I have a class that is an entity in the DB and has a owned entity:
public class EntityOne
{
public int Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
}
I have the owned entity with a list with the type of another persisted entity:
public class OwnedEntity
{
public int Id { get; set; }
public List<EntityTwo> EntityTwo { get; set; }
}
And here is the EntityTwo:
public class EntityTwo
{
public int Id { get; set; }
// all other properties
}
I need to create the relationship between EntityOne and EntityTwo as a many to one, but the property navigation is in my owned entity. How can I do that?
I tried to create a property navigation of my owned entity like this:
public class EntityTwo
{
public int Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
public int OwnedEntityId { get; set; }
// all other properties
}
And the map:
builder.HasOne(prop => prop.OwnedEntity)
.WithMany(prop => prop.EntityTwo)
.HasForeignKey(prop => prop.OwnedEntityId);
But I got an error because ef tries to make my owned entity as a entity table.
Then, I tried to reference the parent entity:
public class EntityTwo
{
public int Id { get; set; }
public EntityOne EntityOne { get; set; }
public int EntityOneId { get; set; }
// all other properties
}
And mapping with inner properties:
builder.HasOne(prop => prop.EntityOne.OwnedEntity)
.WithMany(prop => prop.EntityTwo)
.HasForeignKey(prop => prop.EntityOne.OwnedEntityId);
But it didn't work and i got another ef error:
Error: The expression 'prop => prop.EntityOne.OwnedEntity' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. (Parameter 'memberAccessExpression')
So, is there any way to create this relationship?
For those who want to know how I solved it, I did it all inside the map configuration of the entity one, using the OwnsMany. First, I changed the one-to-many reference of EntityTwo to the OwnedEntity:
public class EntityTwo
{
public int Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
public int OwnedEntityId { get; set; }
// all other properties
}
Then, i made this map (builder of the EntityOne):
builder.OwnsOne(prop => prop.OwnedEntity, subBuilder =>
{
// all map configurations of OwnedEntity
subBuilder.OwnsMany(prop => prop.EntityTwo, ownedBuilder =>
{
ownedBuilder.HasOne(prop => prop.EntityTwo)
.WithMany(prop => prop.OwnedEntity)
.HasForeignKey(prop => prop.EntityTwoId);
// all map configurations of EntityTwo
}
}
That's it.
This question already has an answer here:
ManyToMany Relation in EF Core fluent API
(1 answer)
Closed 1 year ago.
I'm trying to make many to many relationships between two classes with EF Core fluent API. How can I change the foreign key names of the table between that will be created for this relationship?
For example, if we create a many-to-many relationship between the following two classes:
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public string Description { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
// Fluent Api
public class UserMap : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.HasKey(x => x.Id);
builder.ToTable("Users");
builder.HasMany<Course>(user => user.Courses)
.WithMany(course => course.Users);
}
}
A table is created like this:
What I want is to create the in-between table with the foreign key names I give during code-first.
I know I can write the in-between table as a class and change the names with a one-to-many relationship, but I want to do it with code-first.
Example taken from EF core documentation.
You might want to add a new class for the in-between table:
public class StudentCourse
{
public int FKStudentId { get; set; }
public int FKCourseId { get; set; }
public virtual Student Student { get; set; }
public virtual Course Course { get; set; }
}
and replace the navigation properties:
public virtual ICollection<Course> Courses { get; set; }
with
public virtual ICollection<StudentCourse> Courses { get; set; }
do the same in the Course class.
Then in the fluent Api, you can you now use the new class.
public class StudentConfiguration : IEntityTypeConfiguration<Student>
{
public void Configure(EntityTypeBuilder<Student> entity)
{
entity.HasKey(x => x.StudentId);
entity.HasMany(student => student.Courses)
.WithOne(course => course.Student)
.HasForeignKey(x => x.FKStudentId);
}
}
public class CourseConfiguration : IEntityTypeConfiguration<Course>
{
public void Configure(EntityTypeBuilder<Course> entity)
{
entity.HasKey(x => x.CourseId);
entity.HasMany(course => course.Students)
.WithOne(stud => stud.Course)
.HasForeignKey(x => x.FKCourseId);
}
}
EDIT: If you don't want to add in-between class, you may want to try this:
entity.HasMany(student => student.Courses)
.WithMany(course => course.Students)
.UsingEntity(x => x.Property("StudentsStudentId").HasColumnName("FKStudentId"));
entity.HasMany(course => course.Students)
.WithMany(stud => stud.Courses)
.UsingEntity(x => x.Property("CoursesCourseId").HasColumnName("FKCourseId"));
Note: "StudentsStudentId" and "CoursesCourseId" are generated by naming convention. So you may want to add migration first without the .UsingEntity() and inspect the generated migration.
I have the following entities
public class Course
{
public long Id { get; set; }
public virtual ICollection<User> Users{ get; set; }
public virtual ICollection<UserCourse> CourseUsers { get; set; }
}
public class User
{
public long Id { get; set; }
public virtual ICollection<Course> Courses { get; set; }
public virtual ICollection<UserCourse> UserCourses { get; set; }
}
public class UserCourse
{
public long UserId { get; set; }
public User User { get; set; }
public long CourseId { get; set; }
public Course Course { get; set; }
public bool IsRequired { get; set; }
}
with the following mappings for
UserCourse mapping :
builder
.HasOne(nav => nav.User)
.WithMany(self => self.UserCourses)
.HasForeignKey(fk => fk.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder
.HasOne(nav => nav.Course)
.WithMany(self => self.CourseUsers)
.HasForeignKey(fk => fk.CourseId)
.OnDelete(DeleteBehavior.Cascade);
and the User mapping
builder
.HasMany(nav => nav.Courses)
.WithMany(nav => nav.Users);
When trying to create a new migration I'm not exactly sure why I'm getting this.
Cannot use table 'UserCourse' for entity type 'UserCourse' since it is
being used for entity type 'UserCourse(Dictionary<string, object>)'
and potentially other entity types, but there is no linking
relationship. Add a foreign key to 'UserCourse' on the primary key
properties and pointing to the primary key on another entity typed
mapped to 'UserCourse'.
I understand what the error is, but not sure how to force the UserCourse mapping to use the User mapping generated join table or vice-versa
Also, I need the direcat mapping for OData, and the indirect mapping using the join entity to conduct operations on DbSet<UserCourse>
The public virtual ICollection<User> Users{ get; set; } in Course entity and the the public virtual ICollection<Course> Courses { get; set; } in Users entity are redundant. The entities should look more like this
public class Course
{
public long Id { get; set; }
public virtual ICollection<UserCourse> UserCourses { get; set; }
}
public class User
{
public long Id { get; set; }
public virtual ICollection<UserCourse> UserCourses { get; set; }
}
public class UserCourse
{
public long UserId { get; set; }
public User User { get; set; }
public long CourseId { get; set; }
public Course Course { get; set; }
}
And the OnModelCreating method should have this code
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserCourse>()
.HasKey(uc => new { uc.UserId, uc.CourseId });
modelBuilder.Entity<UserCourse>()
.HasOne(uc => uc.Course)
.WithMany(c => c.Users)
.HasForeignKey(uc => uc.CourseId);
modelBuilder.Entity<UserCourse>()
.HasOne(uc => uc.User)
.WithMany(c => c.Courses)
.HasForeignKey(uc => uc.UserId);
}
If you use EF core 5 you can directly skip the join table. It will be generated and handled by EF behind the scenes. More on the topic here https://www.thereformedprogrammer.net/updating-many-to-many-relationships-in-ef-core-5-and-above/
I have 2 models:
public class Text
{
public long Id { get; set; }
public string Text { get; set; }
}
public class User
{
public int Id { get; set; }
public ICollection<Text> Texts { get; set; }
}
My model build on user is that
e.HasMany(o => o.Texts).WithOne().HasForeignKey(d => d.Id).IsRequired();
When I try to run:
dotnet ef migrations add
I get this error:
with foreign key properties {'Id' : long} cannot target the primary
key {'Id' : int} because it is not compatible. Configure a principal
key or a set of compatible foreign key properties for this
relationship.
UPDATE:
It should be able for new models to have a collection of the table Texts like:
public class Customer
{
public int Id { get; set; }
public ICollection<Text> Texts { get; set; }
}
....
e.HasMany(o => o.Texts).WithOne().HasForeignKey(d => d.Id).IsRequired();
Had similar problem using EF Core but didn't want to include the (equivalent in my class) UserId on the dependent entity Text, just to make happy EF. Finally found that you can replace the primary key used in the relationship (UserId)
using HasPrincipalKey()
modelBuilder.Entity<User>()
.HasMany(t => t.Texts)
.WithOne()
.HasPrincipalKey(u => u.Text);
Firstly, change your Model naming please,
public class Text
{
public long Id { get; set; }
public int UserId { get; set; }// add a foreign key that could point to User.Id
public string Body { get; set; }//you cannot have a string property called "Text".
public virtual User Owner { get; set; }
}
public class User
{
public int Id { get; set; }
public virtual ICollection<Text> Texts { get; set; } = new HashSet<Text>();
}
builder.Entity<Text>(table =>
{
table.HasKey(x => x.Id);
table.HasOne(x => x.User)
.WithMany(x => x.Texts)
.HasForeignKey(x => x.UserId)
.HasPrincipalKey(x => x.Id)//<<== here is core code to let foreign key userId point to User.Id.
.OnDelete(DeleteBehavior.Cascade);
});
the reason we have to figure out which key is referred is because of multiple primary keys. I saw it once in MSDN, but cannot find it back.
You can use shadow properties for foreign keys, it looks popular now.
public class Text
{
public long Id { get; set; }
public string Body { get; set; }
public virtual User Owner { get; set; }
}
public class User
{
public int Id { get; set; }
public virtual ICollection<Text> Texts { get; set; } = new HashSet<Text>();
}
builder.Entity<Text>(table =>
{
table.HasKey(x => x.Id);
// Add the shadow property to the model
table.Property<int>("UserId");
table.HasOne(x => x.User)
.WithMany(x => x.Texts)
.HasForeignKey("UserId")//<<== Use shadow property
.HasPrincipalKey(x => x.Id)//<<==point to User.Id.
.OnDelete(DeleteBehavior.Cascade);
});
In the EF context configuration, specifically in the HasForeignKey() you are supposed to specify Which property on the Text model should be the foreign key that points to the User model?
Since User model's primary key is an int, the foreign key pointing from Text to User should naturally also be an int.
I think the mistake you've made is that you are configuring the PK of Textto also be the FK for the relationship Text -> User. Try to change your Text model to :
public class Text
{
public long Id { get; set; }
public string Text{ get; set; }
public int UserId { get; set; }
}
And your configuration to:
e.HasMany(o => o.Texts).WithOne().HasForeignKey(d => d.UserId).IsRequired();
You can simply drop all the migrations or the migration that made that Id, drop the database (if it is small or has no data) and add a clean migration
I was facing the same issue in one-to-one relationship. If you are facing the issue in one-one relationship. Then try this:
public partial class document
{
public document()
{
groups = new group();
}
public int? group_id { get; set; }
public virtual group groups { get; set; }
}
[Table("group")]
public class group
{
[Key]
[Column("group_id")]
public int group_id { get; set; }
[ForeignKey(nameof(group_id))]
public virtual document document { get; set; }
}
Each document has single group. So, we can consider these settings.
modelBuilder.Entity<group>().HasOne(a => a.document)
.WithOne(y => y.groups).HasForeignKey<document>(b => b.group_id);
I have a class:
public class FormTemplate
{
public Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual string Code { get; set; }
public virtual string Shifr { get; set; }
public virtual FormType FormType { get; set; }
public virtual DateTime ActivationDate { get; set; }
}
and i need to split it into to tables in db. I have a fluent mapping wich do this for me:
en.Map(m =>
{
m.Properties(_ => new {_.Code, _.Name, _.Shifr});
m.ToTable("Table1");
})
.Map(m =>
{
m.Properties(_ => new {_.ActivationDate});
m.ToTable("Table2");
}).HasRequired(t => t.FormType).WithMany().Map(m =>
{
m.MapKey("FormType");
m.ToTable("Table2");
});
It works fine, but creates two tables with same primary key column "Id". Is it possible to map first table PK to column "Id" and second table PK to column "Form" ?