I am using EF7 and have a scenario which needs a many to many relationship.
I have a ParticipantSIR entity and a ParticipantAssessmentReport entity. There is a link table ParticipantSIRAssessmentReport between them.
public class ParticipantSIR
{
public int ParticipantSIRID { get; set; }
public virtual ICollection<ParticipantSIRAssessmentReport> ParticipantSIRAssessmentReport { get; set; }
public virtual Participant Participant { get; set; }
}
public class ParticipantAssessmentReport
{
public int ParticipantAssessmentReportID { get; set; }
public virtual ICollection<ParticipantSIRAssessmentReport> ParticipantSIRAssessmentReport { get; set; }
}
public partial class ParticipantSIRAssessmentReport
{
public int ParticipantSIRID { get; set; }
public int ParticipantAssessmentReportID { get; set; }
public virtual ParticipantAssessmentReport ParticipantAssessmentReport { get; set; }
public virtual ParticipantSIR ParticipantSIR { get; set; }
}
modelBuilder.Entity<ParticipantSIRAssessmentReport>(entity =>
{
entity.HasKey(e => new { e.ParticipantSIRID, e.ParticipantAssessmentReportID });
entity.HasOne(d => d.ParticipantAssessmentReport).WithMany(p => p.ParticipantSIRAssessmentReport).HasForeignKey(d => d.ParticipantAssessmentReportID).OnDelete(DeleteBehavior.Restrict);
entity.HasOne(d => d.ParticipantSIR).WithMany(p => p.ParticipantSIRAssessmentReport).HasForeignKey(d => d.ParticipantSIRID).OnDelete(DeleteBehavior.Restrict);
});
This appears to be the way this needs to be setup with EF core including the third entity. I got some of the information from. http://ef.readthedocs.io/en/latest/modeling/relationships.html#many-to-many
When I insert data the 2 outside entities get populated but not the link table.
Since there are no navigation properties between the ParticipantSIR and ParticipantAssessmentReport then I'm not sure how to add the linked data.
_db.ParticipantAssessmentReport.Add(participantAssessmentReport);
foreach (var sir in participantSirs)
{
_db.ParticipantSIR.Add(sir);
}
_db.SaveChanges();
Assuming we're talking about EF Core 1.0rc1 it looks like you have created your model correctly (except the virtual keyword doesn't do anything yet as lazy loading hasn't been implemented).
As many-to-many hasn't been implemented yet as of 1.0rc1 you need to do some extra work. See https://github.com/aspnet/EntityFramework/issues/1368#issuecomment-180066124 for the classic blog Post, Tag, PostTag example code.
In your case you need to explictly add to ParticipantSIRAssessmentReport, something like this:
var participantSIRAssessmentReport = new ParticipantSIRAssessmentReport {ParticipantSIR = participantSIR, ParticipantAssessmentReport = participantAssessmentReport };
_db.ParticipantSIRAssessmentReport.Add(participantSIRAssessmentReport);
_db.SaveChanges();
To map Many-To-Many relationships in EF you need to add the following to your DbContext's OnModelCreating() method:
modelBuilder.Entity<ParticipantSIR>()
.HasMany(e => e.ParticipantSIRAssessmentReport)
.WithMany(e => e.ParticipantSIR)
.Map(e => e.ToTable("ParticipantSIRAssessmentReport") //Name of the linking table
.MapLeftKey("ParticipantSIRId") //Name of the Left column
.MapRightKey("ParticipantSIRAssessmentReportId")); //Name of the right column
From here the relationship will be handled using the Collections inside each of the classes.
Related
I would like to ask if anyone can help me with EF Core 5.
I have two tables that are in "many-to-many" relationship: on the Join table, in addition to the columns that act as foreign keys I also have other columns that I would like to map in EF Core.
The only solution I can think of is to create the relationship as it was done in EF Core 3, that is to use a "one to many" relationship with the join table.
Any suggestions?
Thanks.
Ps. Sorry for my english.
The only solution I can think of is to create the relationship as it was done in EF Core 3, that is to use a "one to many" relationship with the join table.
You can have your cake and eat it too.
EF Core 5 supports custom linking entities and using skip-level navigation at the same time.
There is an example in the docs where the linking entity is in the model, has additional properties, but the main entities skip over the linking entity with Collection Navigation Properties.
internal class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId),
j => j
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId),
j =>
{
j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
j.HasKey(t => new { t.PostId, t.TagId });
});
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public ICollection<Tag> Tags { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public ICollection<Post> Posts { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public DateTime PublicationDate { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
PostTag is a regular entity, and you can access it with db.Set<PostTag>() or from a Post or a Tag. Note the (optional) navigation properties from Post and Tag to PostTag.
I am having the data model as follows.
class KnowledgeDocument
{
public int? Id {get; set;}
public virtual ICollection<KDValueCreation> KDValueCreations { get; set; }
}
class KDValueCreation
{
public int? Id{get; set;}
public int? KDReferenceId { get; set; }
public virtual KnowledgeDocument KDReference { get; set; }
public int KnowledgeDocumentId { get; set; }
public virtual KnowledgeDocument KnowledgeDocument { get; set; }
public decimal Amount {get; set;}
}
Now, when I am trying to create a new KnowledgeDocument along with KDValueCreations as follows.
KnowledgeDocument kd = new KnowledgeDocument();
kd.KDValueCreations.Add(new KDValueCreation{ Amount = "500000"});
When I save the kd, kd is saved without any issue and in KDValueCreation, 1 record is created and both KDReferenceId and KnowledgeDocumentId are populated with the same kdId. But, I want to populate only KnowledgeDocumentId and stop KDReferenceId from populating and set it to null.
As both the fields are pointing to the same reference, Entity framework is populating the Id on both the fields.
How can I achieve this still by saving the KnowledgeDocument with its children?
Please suggest. Thanks in advance.
As I can see, the reason is in two identical relationships. You need to specify what properties are related. You can do it by InverseProperty attribute or by FluentAPI like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// configures one-to-many relationship
modelBuilder.Entity<KDValueCreation>()
.HasRequired<KnowledgeDocument>(c => c.KnowledgeDocument)
.WithMany(d => d.KDValueCreations)
.HasForeignKey(c => c.KnowledgeDocumentId);
}
}
or this:
modelBuilder.Entity<KnowledgeDocument>()
.HasMany<KDValueCreation>(d => d.KDValueCreations)
.WithRequired(c => c.KnowledgeDocument)
.HasForeignKey(c => c.KnowledgeDocumentId);
I am trying to learn asp.net core and I am trying to implement a reddit like website.
what I am trying to do is when a subreddit is called, I want to load both admins and the members of the that particular subreddit and pass them to viewmodel.
to achieve this i have made two tables
public class MemberSubReddit
{
public long SubRedditId { get; set; }
public virtual SubReddit SubReddit { get; set; }
public string UserId { get; set; }
public virtual ApplicationUser ApplicationUser { get; set; }
}
// the names differ since I was trying to check if the problem is from naming conventions
public class AdminSubReddit
{
public long SubRedditId { get; set; }
public virtual SubReddit SubReddit { get; set; }
public string AspNetUsersId { get; set; }
public virtual ApplicationUser AspNetUsers { get; set; }
}
now when I execute this code below:
var sub = _subRedditRepository.GetSingle(e => e.Name == name, s => s.Posts, s => s.Admins, s => s.Members);
the SubRedditId and SubReddit object loads in both admins and members, but as for User object only the UserId(AspNetUserId) loads and the ApplicationUser do not load.
also this is my modelbuilder code (based on asp.net core website in entityframework core, many to many connections are not handled automatically and you have to do it manually):
modelBuilder.Entity<AdminSubReddit>()
.HasKey(t => new { t.SubRedditId, t.AspNetUsersId });
modelBuilder.Entity<MemberSubReddit>()
.HasKey(t => new { t.SubRedditId, t.UserId });
modelBuilder.Entity<MemberSubReddit>()
.HasOne(us => us.ApplicationUser)
.WithMany(ms => ms.MemberIn)
.HasForeignKey(us => us.UserId);
modelBuilder.Entity<MemberSubReddit>()
.HasOne(ms => ms.SubReddit)
.WithMany(ms => ms.Members)
.HasForeignKey(ms => ms.SubRedditId);
modelBuilder.Entity<AdminSubReddit>()
.HasOne(us => us.AspNetUsers)
.WithMany(ms => ms.AdminIn)
.HasForeignKey(us => us.AspNetUsersId);
modelBuilder.Entity<AdminSubReddit>()
.HasOne(ms => ms.SubReddit)
.WithMany(ms => ms.Admins)
.HasForeignKey(ms => ms.SubRedditId);
so please tell me what I am doing wrong :D
also since I am new to this if you can see any structural problem with the code I would be happy if you point them out.
EDIT
public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.Where(predicate).FirstOrDefault();
}
This is the method which is getting called in the controller
I have a sample code:
public class Tag
{
public int TagId { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
public int UserId { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
When I run EF over my model (i use code first approach), i get some tables automatically created in my db:
Users
Tags
UserTagUsers <-- junction table for many-to-many relationship
It is okay, till I decide to add one more property to User entity:
public class User
{
public int UserId { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Tag> Tags2 { get; set; }
}
in this case EF generates completely different relations, it removes UserTagUsers junction table, but adds some additional properties to Tags table in order to make it one-to-one mapping.
How can I explicitly tell EF to make the property Tags and Tags2 to be many-to-many?
Use fluent API to configure the mappings
modelBuilder.Entity<User>()
.HasMany(u => u.Tags).WithMany(t => t.Users)
.Map(m =>
{
m.ToTable("UserTags");
m.MapLeftKey("UserId");
m.MapRightKey("TagId");
});
modelBuilder.Entity<User>()
.HasMany(u => u.Tags2).WithMany(t => t.Users2)
.Map(m =>
{
m.ToTable("UserTags2");
m.MapLeftKey("UserId");
m.MapRightKey("TagId");
});
I have a question about defining Foreign Key in EF Code First Fluent API.
I have a scenario like this:
Two class Person and Car. In my scenario Car can have assign Person or not (one or zero relationship).
Code:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public Person Person { get; set; }
public int? PPPPP { get; set; }
}
class TestContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Car> Cars { get; set; }
public TestContext(string connectionString) : base(connectionString)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PPPPP)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
}
In my sample I want to rename foreign key PersonId to PPPPP. In my mapping I say:
modelBuilder.Entity<Car>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PPPPP)
.WillCascadeOnDelete(true);
But my relationship is one to zero and I'm afraid I do mistake using WithMany method, but EF generate database with proper mappings, and everything works well.
Please say if I'm wrong in my Fluent API code or it's good way to do like now is done.
Thanks for help.
I do not see a problem with the use of fluent API here. If you do not want the collection navigational property(ie: Cars) on the Person class you can use the argument less WithMany method.