entity framework - many to many relationship - c#

Hi I try use Many to Many relationship with EF Fluent API. I have 2 POCO classes.
public class Project
{
public int ProjectId { get; set; }
public virtual ICollection<Author> Authors { get; set; }
public Project()
{
Authors = new List<Author>();
}
}
public class Author
{
public int AuthorId { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public Author()
{
Projects = new List<Project>();
}
}
And I map many to many relationship with this part of code:
////MANY TO MANY
modelBuilder.Entity<Project>()
.HasMany<Author>(a => a.Authors)
.WithMany(p => p.Projects)
.Map(m =>
{
m.ToTable("ProjectAuthors");
m.MapLeftKey("ProjectId");
m.MapRightKey("AuthorId");
});
This created table ProjectsAuthors in DB. It is my first attempt with this case of relationship mapping.
If I omitted this mapping it created table AuthorProject with similar schema. It is correct bevahior?

By trial and error I found the following. Given two classes...
public class AClass
{
public int Id { get; set; }
public ICollection<BClass> BClasses { get; set; }
}
public class BClass
{
public int Id { get; set; }
public ICollection<AClass> AClasses { get; set; }
}
...and no Fluent mapping and a DbContext like this...
public class MyContext : DbContext
{
public DbSet<AClass> AClasses { get; set; }
public DbSet<BClass> BClasses { get; set; }
}
...the name of the created join table is BClassAClasses. If I change the order of the sets...
public class MyContext : DbContext
{
public DbSet<BClass> BClasses { get; set; }
public DbSet<AClass> AClasses { get; set; }
}
...the name of the created join table changes to AClassBClasses and the order of the key columns in the table changes as well. So, the name of the join table and the order of the key columns seems to depend on the order in which the entity classes are "loaded" into the model - which can be the order of the DbSet declarations or another order if more relationship are involved - for example some other entity refering to AClass.
In the end, it doesn't matter at all, because such a many-to-many relationship is "symmetric". If you want to have your own name of the join table, you can specify it in Fluent API as you already did.
So, to your question: Yes, naming the join table AuthorProjects is correct behaviour. If the name had been ProjectAuthors it would be correct behaviour as well though.

Related

Entity Framework Code First in Serverside Blazor: Problem with two relations between the same tables

I'm working on a serverside blazor project (.net 6) using Entity Framework with code first. I have two tables, let's say (in order to protect private data), we have the Tables Band and Bandsman. Originally, every band could have exactly one bandsman, a bandsman could be connected to more then one band though. It's an example, so please don't question this assumptive circumstances.
I created two classes:
[Table("Band")]
public partial class Band
{
[Key]
public int Id { get; set; }
public string BandName { get; set; }
public int? BandsmanId { get; set; }
public virtual Bandsman Bandsman { get; set; }
}
[Table("Bandsman")]
public partial class Bandsman
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Band> Band { get; set; }
}
So far everything works fine. Entity Framework set the correct foreign key. But now I have to insert a second bandsman. Let's say, the first bandsman is a keyboarder, now I need a drummer as well. So I altered the existing classes:
[Table("Band")]
public partial class Band
{
[Key]
public int Id { get; set; }
public string BandName { get; set; }
public int? BandsmanId { get; set; }
public int? DrummerId { get; set; }
public virtual Bandsman Bandsman { get; set; }
public virtual Bandsman Drummer { get; set; }
}
[Table("Bandsman")]
public partial class Bandsman
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Band> Band { get; set; }
public virtual List<Band> Drummer { get; set; }
}
I know I have to tell Entity Framework now how to map the tables. So I added mapping instructions to the OnModelCreating-Method in DbContext:
builder.Entity<Band>().HasOne(a => a.Bandsman).WithMany().HasForeignKey(b => b.BandsmanId);
builder.Entity<Band>().HasOne(a => a.Drummer).WithMany().HasForeignKey(b => b.DrummerId);
This doesn't work. When I create the migrations I see that Entity Frameworks tries to create new Columns BandsmanId1 and BandsmanId2 to the Band-Table instead of using the Columns I defined.
So I tried to add the instructions the other way around, too, in addition to the previous ones:
builder.Entity<Bandsman>().HasMany<Band>(a => a.Band).WithOne().HasForeignKey(b => b.BandsmanId);
builder.Entity<Bandsman>().HasMany<Band>(a => a.Drummer).WithOne().HasForeignKey(b => b.DrummerId);
It's still the same, Entity Framework tries to add new columns and map the foreign keys to them.
I also tried to rename Band.BandsmanId to Band.KeyboarderId or rather add and map a new column with the new name (so existing data won't get lost), rename Band.Bandsman to Band.Keyboarder and Bandsman.Band to Bandsman.Keyboarder. With no effect, Entity Framework still seems incapable to use the colums I want it to use. I guess the instructions I added to OnModelCreating in DbContext are incorrect, but I'm not able to find out how to put it right. I found some examples here on stackoverflow and elsewhere, but I can't manage to convert one of this examples to my code.
So I hope someone can help me to put the classes and instructions right.
After posting my question, I found the solution in a post that was shown as possibly related:
Entity Framework Code First - two Foreign Keys from same table
I was close, my only mistake was not to name the virtual List-Property of the Bandsman-Class in the .HasMany()-Part of the instructions. So Entity Framework didn't now these properties were related to the foreign key columns in the band-table and tried to create the assumed-to-be-missing columns on its own. This way it works:
builder.Entity<Band>().HasOne(a => a.Bandsman).WithMany(b => b.Band).HasForeignKey(a => a.BandsmanId);
builder.Entity<Band>().HasOne(a => a.Drummer).WithMany(b => b.Drummer).HasForeignKey(a => a.DrummerId);

How can I create working lists and save them using the Entity Framework Core and ASP.NET Web API?

I am making a web app similar to google classroom in that you can join classes.
I have a class "Account" and inside that account I have a list that should hold the IDs of all the classes the account has joined. I tried to make the list a list of longs, but I couldn't do that because I got the error:
System.InvalidOperationException: 'The property
'Account._classesJoined' could not be mapped, because it is of type
'List' which is not a supported primitive type or a valid entity
type. Either explicitly map this property, or ignore it using the
'[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in
'OnModelCreating'.
The way I solved this problem is to create a class "JoinedClassId" to make a list of instead, with a property "classIdNumber". However, during testing, I noticed that the JoinedClassIds that I added to the the Account object were not saving. I think this is because I am not saving the database table for the JoinedClassId class.
Do I have to create a database context and controller for the JoinedClassId class? I don't want to be able to manipulate the JoinedClassId class from the API, I'm only using it as a data container. Is there a way I could either create a long list and save it or save the JoinedClassIds?
In EF Core "Many-to-many relationships without an entity class to represent the join table are not yet supported".
Book -> Category has many-to-may rel so this should create the 3 tables in DB :
Books, Category and BookCategory
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
//public ICollection<Category> Categories { get; set; } // cannot appear
// For the many-to-many rel
public List<BookCategory> BookCategories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
//public ICollection<Book> Books { get; set; } // cannot appear
// For the many-to-many rel
public List<BookCategory> BookCategories { get; set; }
}
// Class because of the many-to-many rel
public class BookCategory
{
public int BookId { get; set; }
public Book Book { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class MyContextDbContext : DbContext
{
public MyContextDbContext(DbContextOptions<MyContextDbContext> dbContextOptions)
: base(dbContextOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookCategory>()
.HasKey(t => new { t.BookId, t.CategoryId });
modelBuilder.Entity<BookCategory>()
.HasOne(bctg => bctg.Book)
.WithMany(ctg => ctg.BookCategories)
.HasForeignKey(book => book.CategoryId);
modelBuilder.Entity<BookCategory>()
.HasOne(bctg => bctg.Category)
.WithMany(ctg => ctg.BookCategories)
.HasForeignKey(ctg => ctg.BookId);
}
public DbSet<Book> Book { get; set; }
public DbSet<Category> Category { get; set; }
}

Entity Framework Code First One Property of Class and List of The Same Class

I'm using entity framework code first approach
I have a class
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public Person Director { get; set; }
public virtual ICollection<Person> Actors { get; set; }
}
and a class
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
When the database is created I get one table Movies with Id, Title, Director_Id and a table Person with Id and Name.
I expect to have a table Movies_Persons with columns Movie_Id and Actor_Id
How can I achieve this?
Your Problem is, that you don`t tell the Person Class, that there can be multiple Movies per person.
So by adding the following line in your person class:
public virtual ICollection<Movie> Movies { get; set; }
Your entity knows that both your classes can have multiple references to the other class.
To fulfill this requirement Entity Framework will create a third table with Movie_ID and Person_ID.
If you want more informations just look for:
Entity Framework - Many to many relationship
or follow this link:
http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
You can check out the other articels on that page too, if you are new to entity framework.
UPDATE:
Sorry i missed, that you are already have another reference to your person table.
Here you have to tell your entity framework, which way you want to reference the two tables by fluent api.
Check out this stackoverflow answer. That should do the trick.
You have to insert this code into your OnModelCreating Function of your DbContext Class.
So your final code should look like this:
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public virtual Person Director { get; set; }
public virtual ICollection<Person> Actors { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Movie> Movies_Actors { get; set; }
public virtual ICollection<Movie> Movies_Directors { get; set; }
}
And in your OnModelCreating add following code:
modelBuilder.Entity<Movie>()
.HasMany(a => a.Actors)
.WithMany(a => a.Movies_Actors)
.Map(x =>
{
x.MapLeftKey("Movie_ID");
x.MapRightKey("Person_ID");
x.ToTable("Movie_Actor");
});
modelBuilder.Entity<Movie>()
.HasRequired<Person>(s => s.Director)
.WithMany(s => s.Movies_Directors);
I don't have the possibility to test the code, but that should do the trick.
If you have to do some adjustments to make it work, plz add them in the comments, so other ppl can benefit from it.

EF7 Implement TPH + M2M

Is there a better way to accomplish this end-goal of having easily-queryable (and Include-able) cross-sections of a related many-to-many entity stored in the same table?
I started off without implementing TPH in the join table, but that makes consuming one type or another in queries more involved, afaict.
// table Related: [Id]
public class Related
{
public Guid Id { get; set; }
public List<RelatedOther> RelatedOthers { get; set; } = new List<RelatedOther>();
public List<RelatedOtherOne> RelatedOtherOnes { get; set; } = new List<RelatedOtherOne>();
public List<RelatedOtherTwo> RelatedOtherTwos { get; set; } = new List<RelatedOtherTwo>();
}
// table RelatedOther: [RelatedId, OtherId, Type]
public abstract class RelatedOther
{
public Guid RelatedId { get; set; }
public Guid OtherId { get; set; }
public Related Related { get; set; }
public Other Other { get; set; }
public abstract RelatedOtherType Type { get; }
}
public class RelatedOtherOne : RelatedOther
{
public override RelatedOtherType Type => RelatedOtherType.One;
// should be unnecessary, 'Other' should be correct type
public OtherOne OtherOne { get; set; }
}
public class RelatedOtherTwo : RelatedOther
{
public override RelatedOtherType Type => RelatedOtherType.Two;
// should be unnecessary, 'Other' should be correct type
public OtherTwo OtherTwo { get; set; }
}
public enum RelatedOtherType : int
{
One = 1,
Two = 2
}
// table Other: [Id, OneProp, TwoProp]
public abstract class Other
{
public Guid Id { get; set; }
public List<RelatedOther> RelatedOthers { get; set; } = new List<RelatedOther>();
}
public class OtherOne : Other
{
public string OneProp { get; set; }
}
public class OtherTwo : Other
{
public string TwoProp { get; set; }
}
TPH is mapped like this
M2M is mapped like this + discriminator in HasKey()
This gets even more complicated (if not impossible?) when the 'Related' entity evolves into a TPH strategy like the 'Other'.
I have no easy solution but as I stumbled across the same problem I thought I'll share what I have so far.
I found out that I usually need to load all or many types of the relations to the classes of a TPH structure.
So I use the base many-to-many class to load the related objects. Thus this class cannot be abstract:
public class Event2Location
{
[Required]
public Event Event { get; set; }
public int EventId { get; set; }
[Required]
public Location Location { get; set; }
public int LocationId { get; set; }
public byte EntityType { get; set; }
}
The derived class only adds some properties for easier access:
public class Event2Country : Event2Location
{
[NotMapped]
public Country Country
{
get { return base.Location as Country; }
set { base.Location = value; }
}
[NotMapped]
public int CountryId
{
get { return base.LocationId; }
set { base.LocationId = value; }
}
}
In the Event class I have:
public virtual ICollection<Event2Location> Event2Locations { get; set; }
[NotMapped]
public virtual ICollection<Event2Country> Event2Countries => Event2Locations?.OfType<Event2Country>().ToList();
// I should probably add some caching here if accessed more often
[NotMapped]
public virtual ICollection<Event2City> Event2Cities => Event2Locations?.OfType<Event2City>().ToList();
So when I load the joined tables I can use
.Include(e => e.Event2Locations).ThenInclude(j => j.Location)
And I can access the relations of a specific type as needed with the NotMapped Collections.
I still use the derived Event2... classes to add a new relationship.
As you see I have added a column EntityType to the many-to-many class which I use as TPH discriminator. With this column I can also declare which types of Relations/entities I want to load if I do not want to load all.
modelBuilder.Entity<Event2Location>()
.HasDiscriminator<byte>("EntityType")
.HasValue<Event2Location>(0)
.HasValue<Event2Country>(1)
This is surely far from perfect but I finally gave up on optimizing that. First EFCore has to become more mature. Second I want to see how I actually use these structures.
PS: Actually my Location TPH structure has parent-child-relationships within it. Here I did not create a TPH structure for the relation class (as you said - not possible or at least not reasonable). I added ParentType and ChildType. Thus I can determine which relations I actually want to load. Then I fetch the related Locations of the types I need manually on the client side from the result.

EF Code First Many to many relation store additional data in link table

How do I store additional fields in the "link table" that is automagically created for me if I have two entities associated as having a many to many relationship?
I have tried going the "two 1 to many associations"-route, but I'm having a hard time with correctly configuring the cascading deletion.
Unless those extra columns are used by some functions or procedures at the database level, the extra columns in the link table will be useless since they are completely invisible at the Entity Framework level.
It sounds like you need to re-think your object model. If you absolutely need those columns, you can always add them later manually.
You will most likely need to expose the association in your domain model.
As an example, I needed to store an index (display order) against items in an many-to-many relationship (Project <> Images).
Here's the association class:
public class ProjectImage : Entity
{
public Guid ProjectId { get; set; }
public Guid ImageId { get; set; }
public virtual int DisplayIndex { get; set; }
public virtual Project Project { get; set; }
public virtual Image Image { get; set; }
}
Here's the mapping:
public class ProjectImageMap : EntityTypeConfiguration<ProjectImage>
{
public ProjectImageMap()
{
ToTable("ProjectImages");
HasKey(pi => pi.Id);
HasRequired(pi => pi.Project);
HasRequired(pi => pi.Image);
}
}
From Project Map:
HasMany(p => p.ProjectImages).WithRequired(pi => pi.Project);
Maps to the following property on project:
public virtual IList<ProjectImage> ProjectImages { get; set; }
Hope that helps
Ben
Suppose there is a many-to-many association between two types: User and Message, and the association class is defined as UserMessageLink with additional properties.
public class User {
public int Id {get;set;}
}
public class Message {
public int Id {get;set;}
}
//The many-to-many association class with additional properties
public class UserMessageLink {
[Key]
[Column("RecieverId", Order = 0)]
[ForeignKey("Reciever")]
public virtual int RecieverId { get; set; }
[Key]
[Column("MessageId", Order = 1)]
[ForeignKey("Message")]
public virtual int MessageId { get; set; }
public virtual User Reciever { get; set; }
public virtual Message Message { get; set; }
//This is an additional property
public bool IsRead { get; set; }
}

Categories

Resources