I am trying to establish foreign key to 2 classes using FluentAPI and bit confused on the way to implement it.
ApplicationUser uses ASP.NET Identity model and has UserId as string
public class ApplicationUser : IdentityUser
{
public virtual List<UserProduct> Orders { get; set; }
}
Product that has a composite key on columns ProductID and ProductCategoryID
public class Product
{
public int ProductID { get; set; }
public string ProductCategoryID { get; set; }
public virtual List<UserProduct> Orders { get; set; }
...
}
and another class UserProduct that will have many-to-many relationship between ApplicationUser and Product table
public partial class UserProduct
{
public string UserId { get; set; }
public int ProductID { get; set; }
public string ProductCategoryID { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual Product Product { get; set; }
}
The FluentAPI code looks like
modelBuilder.Entity<Product>().Property(t => t.ProductID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Product>().HasKey(x => new { x.ProductID, x.ProductCategoryID });
modelBuilder.Entity<UserProduct>().HasKey(x => new {x.UserId, x.ProductID, x.ProductCategoryID});
How do I establish the foreign key relationship of UserProduct with ApplicationUser and Product?
You can add the id-property (e.g. OrderId) to the class UserProduct and use this code to connect entities by foreign key.
modelBuilder.Entity<UserProduct>()
.HasRequired(x => x.User)
.WithMany(u => u.Orders)
.HasForeignKey(x => x.OrderId);
Related
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 the following entity:
public class ShoppingCartItem
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ShoppingCartItemId { get; set; }
public virtual Product Product { get; set; }
public int Quantity { get; set; }
public string ShoppingCartId { get; set; }
public DateTime ItemAddedToCart { get; set; }
}
There is a one to one foreign key relationship from Product to ShoppingCartItem.
My question is, how can I make entity framework core skip deleting the ShoppingCartItem entity on Product cascade delete?
You can use something like the following in your builder object
builder.Entity<ShoppingCartItem>()
.HasOne(s => s.Product)
.WithOne(p => p.ShoppingCartItem)
.Metadata.DeleteBehavior = DeleteBehavior.Restrict;
I am trying to create a composite foreign key (if that is the correct term) which enforces a product class type of relationship on a table where the product is mandatory but class is not. I would like to use Code First and the Fluent API to achieve this model.
The scenario is:
I have commodities which can have many grades and tickets (deliveries) which are required to have a commodity and optionally a grade.
The problem I have is that with the model definition below the commodity/grade relationship is not enforced on tickets. So I can enter a ticket with a grade which is not related to the commodity specified on the ticket.
I have tried various Fluent statements with no success.
How do I create a foreign key which prevents an invalid Commodity Grade from being created in the tickets table?
I have the following model:
public class Commodity
{
public int CommodityId { get; set; }
...
public virtual ICollection<Grade> Grades { get; set; } = new HashSet<Grade>();
public virtual ICollection<Ticket> Tickets { get; set; } = new HashSet<Ticket>();
}
public class Grade
{
public int GradeId { get; set; }
...
public virtual Commodity Commodity { get; set; }
public virtual ICollection<Ticket> Tickets { get; set; } = new HashSet<Ticket>();
}
public class Ticket
{
public int TicketId { get; set; }
...
public virtual Commodity Commodity { get; set; }
public virtual Grade Grade { get; set; }
}
public class MyEntities : DbContext
{
public virtual DbSet<Commodity> Commodities { get; set; }
public virtual DbSet<Grade> Grades { get; set; }
public virtual DbSet<Ticket> Tickets { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Commodity>()
.HasMany(c => c.Grades)
.WithRequired(c => c.Commodity)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Commodity>()
.HasMany(c => c.Tickets)
.WithRequired(t => t.Commodity)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Grade>()
.HasMany(g => g.Tickets)
.WithOptional(t => t.Grade);
}
}
Thanks
Tony
We have a requirement that several tables need to be referred in a one table as shown in the diagram.
Please note that the diagram is NOT the correct DB model but just represent what our requirement. Can you suggest a proper way to implement above in SQL Server DB and Entity Framework 6?
Example: A sales order (in SalesOrder table) can have multiple files and those uploaded file details will be stored in UploadedFile table. Likewise OrderTable and Invoice too.
So we need to have a proper DB model with FK relationships between FileUpload table with each other related table.
Note: All tables PKs are auto-increment int values and we may need add more entities (tables) in future
In order to provide you an alternative, as you said you are using Entity Framework, here is a sample of Code First implementation done on C#. You can create and update the schema via Package Manager Console migrations, within Visual Studio. I have used the Fluent API in order to define the relationships, as this is recommended over the alternative.
public class SampleContext : DbContext
{
public SampleContext()
: base("name=YourConnection")
{
}
public DbSet<SalesOrder> SalesOrders { get; set; }
public DbSet<CreditOrder> CreditOrders { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<UploadedFile> UploadedFiles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<SalesOrder>()
.HasKey(so => so.Id);
modelBuilder.Entity<CreditOrder>()
.HasKey(co => co.Id);
modelBuilder.Entity<Invoice>()
.HasKey(i => i.Id);
modelBuilder.Entity<UploadedFile>()
.HasKey(u => u.Id);
modelBuilder.Entity<UploadedFile>()
.HasRequired(u => u.SalesOrder)
.WithMany(s => s.UploadedFiles)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UploadedFile>()
.HasRequired(u => u.CreditOrder)
.WithMany(c => c.UploadedFiles)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UploadedFile>()
.HasRequired(u => u.Invoice)
.WithMany(c => c.UploadedFiles)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UploadedFile>()
.Property(uf => uf.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<SalesOrder>()
.Property(so => so.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<CreditOrder>()
.Property(co => co.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Invoice>()
.Property(i => i.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
base.OnModelCreating(modelBuilder);
}
}
// Collections of navigation properties should be included in classes for a one-to-many relationship
public class SalesOrder
{
public int Id { get; set; }
public string MyColumn { get; set; }
public IList<UploadedFile> UploadedFiles { get; set; }
}
public class CreditOrder
{
public int Id { get; set; }
public string MyColumn { get; set; }
public IList<UploadedFile> UploadedFiles { get; set; }
}
public class Invoice
{
public int Id { get; set; }
public string MyColumn { get; set; }
public IList<UploadedFile> UploadedFiles { get; set; }
}
public class UploadedFile
{
public int Id { get; set; }
public SalesOrder SalesOrder { get; set; }
public CreditOrder CreditOrder { get; set; }
public Invoice Invoice { get; set; }
public string FilePath { get; set; }
public string FileType { get; set; }
}
public class SalesOrder
{
public int Id { get; set; }
public string MyColumn { get; set; }
public IList<UploadedFile> UploadedFiles { get; set; }
}
public class CreditOrder
{
public int Id { get; set; }
public string MyColumn { get; set; }
public IList<UploadedFile> UploadedFiles { get; set; }
}
public class Invoice
{
public int Id { get; set; }
public string MyColumn { get; set; }
public IList<UploadedFile> UploadedFiles { get; set; }
}
public class UploadedFile
{
public int Id { get; set; }
public SalesOrder SalesOrder { get; set; }
public CreditOrder CreditOrder { get; set; }
public Invoice Invoice { get; set; }
public string FilePath { get; set; }
public string FileType { get; set; }
}
Before Reading My Answer take notice in the below information:
A better answer can be found at this previous post at Foreign Key to multiple tables.
My Answer:
Refer here for more information from the Creating Tables documentation off of the MDSN page.
The design I went for gave each table there own id as the Primary Key. Then I used the UploadedFile table to add references to each of these tables in the shape of Foreign Keys.
I have created a few dummy scripts that might help you create these tables. Please let me know if this helps. Thank you!
SQL Scripts:
SalesOrder Table:
Create Table dbo.SalesOrder(
SalesOrderID int not null, identity primary key,
--enter whatever other columsn you have here
)
CreditOrder Table:
Create Table sbo.CreditOrder(
CreditOrderID int not null, identity primary key,
--enter whatever other columsn you have here
)
Invoice Table:
Create Table dbo.Invoice(
InvoiceID int not null, identity primary key,
--enter whatever other column you have here
)
UploadedFile Table:
Create table dbo.UploadedFile(
UploadFileID int not null identity primary key,
SalesOrderID int null Foreign Key References SalesOrder(SalesOrderID),
CreditOrderID int null Foreign Key References CreditOrder(CreditOrderID),
InvoiceID int null Foreign Key References CreditOrder(InvoiceID),
--enter whatever other columns you have here
)
I have 2 model classes for Users and Organizations.
public class User : IdentityUser
{
[Required]
public string Name { get; set; }
[Required]
public string Surname { get; set; }
public int? OrganizationID { get; set; }
public virtual OrgList org { get; set; }
}
public class OrgList
{
public OrgList()
{
employees = new HashSet<User>();
}
public int id { get; set; }
public String name { get; set; }
public String ownerId { get; set; }
public virtual ICollection<User> employees { get; set; }
public virtual User ownerUser { get; set; }
}
User can be owner of some organization and also he is employee of the same organization (But other employees can't be owners of the organization).
First i've created a relationship for employees and it works OK
modelBuilder.Entity<OrgList>(entity =>
{
entity.HasMany(e => e.employees)
.WithOne(e => e.org)
.HasForeignKey(e => e.OrganizationID)
.OnDelete(DeleteBehavior.SetNull);
}
but when i try to add another relationship for owner
entity.HasOne(e => e.ownerUser)
.WithOne(e => e.org)
.HasForeignKey<OrgList>(e => e.ownerId)
.OnDelete(DeleteBehavior.Cascade);
i have an error on migration:
Cannot create a relationship between 'User.org' and
'OrgList.ownerUser', because there already is a relationship between
'OrgList.employees' and 'User.org'. Navigation properties can only
participate in a single relationship.
How can i fix it? I've found an answers for EF6 (not EF Core) with HasOptional() and WithOptionalPrincipal() methods that not exist in EF Core.
Can i do it without creating additional table for employees or without creating additional virtual OrgList on User class?
You're trying to create the owner relationship with the same property on the user that you are using for the employee relationship. Entity framework wouldn't know which relationship to assign the property. If you created another property on the user like
public int? OwnedOrganizationID { get; set; }
public virtual OrgList OwnedOrg { get; set; }
and change the statement to
entity.HasOne(e => e.ownerUser)
.WithOne(e => e.OwnedOrg)
.HasForeignKey<OrgList>(e => e.ownerId)
.OnDelete(DeleteBehavior.Cascade);
I imagine it should work.