Suppose in the data model there is a many-to-many relationship. How can I set it up so that the list of id's is eager loaded and navigation properties are lazy loaded?
In a one-to-one relationship, I can easily use the ForeignKey attribute to link the id and the navigation property, but I'm not sure if there's a way to link collections. How can I make sure the two are consistent?
public class User {
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<User> FollwerIds { get; set; }
public ICollection<User> FollwingIds { get; set; }
public virtual ICollection<User> Followers { get; set; }
public virtual ICollection<User> Following { get; set; }
}
public class UserContext: DbContext {
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuiler.Entity<User>()
.HasMany(u => u.Followers)
.WithMany(u => u.Following)
.Map(m => m.MapLeftKey("FollowingUserId")
.MapRightKey("FollowerUserId")
.ToTable("UserFollowUser")
);
}
}
I'd like to have the controllers in ASP.NET always return User objects with the two arrays of User Ids.
Thank you.
In many-to-many you don't need this:
public ICollection<User> FollwerIds { get; set; }
public ICollection<User> FollwingIds { get; set; }
To make the many-to-many relationship work, you'll need :
public class User {
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Followers { get; set; }
public virtual ICollection<User> Following { get; set; }
}
public class UserContext: DbContext {
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuiler.Entity<User>()
.HasMany(u => u.Followers)
.WithMany(u => u.Following)
.Map(m => m.MapLeftKey("FollowingUserId")
.MapRightKey("FollowerUserId")
.ToTable("UserFollowUser")
);
}
}
I believe this would work as you wish.
Related
I have the following two entities:
public class Company
{
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public Guid Id { get; set; }
public string Town { get; set; }
public string Country { get; set; }
}
This corresponds to two DB tables:
Company:
Id uniqueidentifier
Name varchar
Address:
Id uniqueidentifier
Town varchar
Country varchar
RelationId uniqueidentifier
RelationId is the foreign key which stores the link back to CompanyId.
I can not change either the classes or the tables.
I am trying to figure out how to represent this construct in code first EFCore.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>().ToTable("Companies");
modelBuilder.Entity<Address>().ToTable("Addresses");
modelBuilder.Entity<Company>().HasKey(c => c.Id);
modelBuilder.Entity<Address>().HasKey(c => c.Id);
???????????????????????
}
What am I missing in the code above to prevent the creation of a CompanyId foreign key and use the 'RelationId' instead.
Create a Guid RelationId and a Company Company properties in Address, as purposed by #TanvirArjel and use the following fluent configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
.....
modelBuilder.Entity<Company>()
.HasOne(c => c.Address)
.WithOne(a => a.Company)
.HasForeignKey(a => a.RelationId);
}
If an address can have more than one company, change the WithOne by WithMany and the Company property type to ICollection<Company>
Here is the solution for your requirement :
public class Company
{
public Guid Id { get; set; }
public string Name { get; set; }
//remove virtual keyword as there is no lazy loading in entityframework core
public ICollection<Address> Addresses { get; set; }
}
public class Address
{
public Guid Id { get; set; }
[ForeignKey("Company ")]
public Guid RelationId { get; set; }
public string Town { get; set; }
public string Country { get; set; }
//remove virtual keyword as there is no lazy loading in entityframework core
public Company Company {get; set;}
}
Using Fluent API:
public class YourDbContext : DbContext
{
public DbSet<Company> Companies { get; set; }
public DbSet<Address> Addresses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Address>()
.HasOne(p => p.Company)
.WithMany(b => b.Addresses);
}
}
So I'm following this answer in trying to get two foreign keys to get to a single table and it works.
public class Team
{
public int TeamId { get; set;}
public string Name { get; set; }
public virtual ICollection<Match> HomeMatches { get; set; }
public virtual ICollection<Match> AwayMatches { get; set; }
}
public class Match
{
public int MatchId { get; set; }
public int HomeTeamId { get; set; }
public int GuestTeamId { get; set; }
public float HomePoints { get; set; }
public float GuestPoints { get; set; }
public DateTime Date { get; set; }
public virtual Team HomeTeam { get; set; }
public virtual Team GuestTeam { get; set; }
}
public class Context : DbContext
{
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Match>()
.HasRequired(m => m.HomeTeam)
.WithMany(t => t.HomeMatches)
.HasForeignKey(m => m.HomeTeamId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Match>()
.HasRequired(m => m.GuestTeam)
.WithMany(t => t.AwayMatches)
.HasForeignKey(m => m.GuestTeamId)
.WillCascadeOnDelete(false);
}
}
However, in my solution I don't want Team to have HomeMatches and AwayMatches collections as it doesn't make sense to be able to navigate to Match from that entity.
Is it possible to have two foreign keys pointing to the same table when the entity for the parent table doesn't have collections for the child tables.
I would like my Team entity to be like below.
public class Team
{
public int TeamId { get; set;}
public string Name { get; set; }
// HomeMatches and AwayMatches collection is no longer here
}
How could I use the modelBuilder to articulate to EntityFramework that I want to HomeTeamID and GuestTeamID to be foreign keys of Team?
Just remove collections & leave empty parameters for .WithMany().
Given the following rough code-first schema, the goal would appear to be quite simple. An Invoice can either be from or to a Company, and the Invoices collection should contain all invoices regardless of which it is.
public class Company
{
public int Id { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
public class Invoice
{
public int Id { get; set; }
public int FromCompanyId { get; set; }
public int ToCompanyId { get; set; }
public virtual Company FromCompany { get; set; }
public virtual Company ToCompany { get; set; }
}
You'll note in a Migration that a third Company_Id is generated for obvious reasons to support the Invoices navigation property as EF only appears to support a 1 Nav Prop -> 1 FK arrangement.
My question is whether or not it is possible to have the Invoices property contain both, or if I should map them individually (ie. IC<Inv> InvoicesFrom, IC<Inv> InvoicesTo) and create a client-side collection to have both manually.
I have tried:
Using InverseProperty on both FromCompany and ToCompany, which confuses EF as it can't determine the principal end of the relationship.
[ForeignKey(nameof(FromCompanyId)), InverseProperty(nameof(Company.Invoices))]
public virtual Company FromCompany { get; set; }
[ForeignKey(nameof(ToCompanyId)), InverseProperty(nameof(Company.Invoices))]
public virtual Company ToCompany { get; set; }
Using fluent API to map them, but it only takes into account the second which makes sense from a code perspective.
modelBuilder.Entity<Company>()
.HasMany(m => m.Invoices)
.WithRequired(m => m.ToCompany)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Company>()
.HasMany(m => m.Invoices)
.WithRequired(m => m.FromCompany)
.WillCascadeOnDelete(false);
There's of course no major issue if this isn't possible, I just could have sworn I've done it before.
For posterity, here is a complete version of the workaround to maintain an IEnumerable<Invoices> from company that contains both of the sets put together.
public class MyContext : DbContext
{
public MyContext() : base("DefaultConnection") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Company>().HasMany(c => c.InvoicesFrom).WithRequired(i => i.FromCompany).WillCascadeOnDelete(false);
modelBuilder.Entity<Company>().HasMany(c => c.InvoicesTo).WithRequired(i => i.ToCompany).WillCascadeOnDelete(false);
}
public DbSet<Company> Companies { get; set; }
public DbSet<Invoice> Invoices { get; set; }
}
public class Company
{
public int Id { get; set; }
public virtual ICollection<Invoice> InvoicesFrom { get; set; }
public virtual ICollection<Invoice> InvoicesTo { get; set; }
[NotMapped]
public IEnumerable<Invoice> Invoices
{
get {
return InvoicesFrom.Union(InvoicesTo);
}
}
}
public class Invoice
{
public int Id { get; set; }
public int FromCompanyId { get; set; }
public int ToCompanyId { get; set; }
public virtual Company FromCompany { get; set; }
public virtual Company ToCompany { get; set; }
}
I'm using EF Code First to query and create a database. One of my entities (relationship) has two navigation properties to the same entity (activity). My problem is that if I use EF to create the database schema it will create four foreign key columns and constraints instead of two.
Here are the relevant code parts:
activity class:
public class Activity {
public virtual ICollection<Relationship> Successors { get; set; }
public virtual ICollection<Relationship> Predecessors { get; set; }
}
relationship class:
public class Relationship {
public virtual Activity Activity1 { get; set; }
public int Activity1_ID { get; set; }
public virtual Activity Activity2 { get; set; }
public int Activity2_ID { get; set; }
}
Relationship mapping class:
this.HasRequired(t => t.Activity1)
.WithMany(t => t.Predecessors)
.HasForeignKey(m => m.Activity1_ID)
.WillCascadeOnDelete(false);
this.HasRequired(t => t.Activity2)
.WithMany(t => t.Successors)
.HasForeignKey(m => m.Activity2_ID)
.WillCascadeOnDelete(false);
Database structure:
Is there a way to prevent the creation of the last two columns?
This should create you only 2 foreign key columns.
public class Activity
{
public int Id { set; get; }
public virtual ICollection<Relationship> Successors { get; set; }
public virtual ICollection<Relationship> Predecessors { get; set; }
}
public class Relationship
{
public int Id { set; get; }
public virtual Activity Activity1 { get; set; }
public int Activity1_ID { get; set; }
public virtual Activity Activity2 { get; set; }
public int Activity2_ID { get; set; }
}
And the DbContext class where i am specifying the relationship/FK nature on my OnModelCreating.
public class MyDb: DbContext
{
public MyDb():base("EfDbContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Relationship>()
.HasRequired(f => f.Activity1)
.WithMany(f => f.Predecessors)
.HasForeignKey(g => g.Activity1_ID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Relationship>().
HasRequired(f => f.Activity2)
.WithMany(f => f.Successors)
.HasForeignKey(g => g.Activity2_ID)
.WillCascadeOnDelete(false);
}
}
Here's my code:
base class:
public class BaseEnt
{
[Key]
public int ID { get;set; }
public DateTime InsertDate { get; set; }
public int InsertUserID { get; set; }
public DateTime UpdateDate { get; set; }
public int UpdateUserID { get; set; }
[Timestamp]
public byte[] Timestamp { get; set; }
public virtual User InsertUser { get; set; }
public virtual User UpdateUser { get; set; }
}
user entity:
public class User:BaseEnt
{
public string Username { get; set; }
public string Fullname { get; set; }
public virtual ICollection<User> InsertedUsers { get; set; }
public virtual ICollection<User> UpdatedUsers { get; set; }
}
model creating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasRequired(t => t.InsertUser)
.WithMany(t=>t.InsertedUsers)
.HasForeignKey(t => t.InsertUserID).WillCascadeOnDelete(false);
modelBuilder.Entity<User>()
.HasRequired(t => t.UpdateUser)
.WithMany(t=>t.UpdatedUsers)
.HasForeignKey(t => t.UpdateUserID).WillCascadeOnDelete(false);
}
Seed:
protected override void Seed(MCFDataContext context)
{
context.Users.Add(new Entities.User {ID=1,Fullname="Rusty Boi ",Username="jhaskdhaksdhk",InsertUserID=1,UpdateUserID=1,UpdateDate=DateTime.Now,InsertDate=DateTime.Now });
}
and here's the error i encountered in the seed part:
{"Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values."}
what you have here is some sort of "chicken and egg" problem. Possible solutions:
Make the foreign keys InsertUserID and UpdateUserID nullable
Do a design change: Maybe the user table shouldn't have InsertUserID, UpdateUserID. this could be solved by introducing another base class which doesn't have the InsertUserID and UpdateUserID properties or something similar