I have two entity for save Product and Product's Related Products ... there is One to Many Relationship ... Everything is right before storing the information But in the database two duplicate fields are stored
RelatedProducID
ProductID
public class RelatedCatalogs : EntityBase
{
public Guid ProductID { get; set; }
public Guid RelatedProducID { get; set; }
public Product RelatedProductCatalog { get; set; }
public int Priority { get; set; }
}
Product Class:
public class Product{
public Guid ProductID { get; set; }
public string Name { get; set; }
[ForeignKey("RelatedProductID")]
public virtual List<RelatedCatalogs> RelatedCatalogs { get; set; }
.
.
.
}
What needs to be done now to fix this problem?
Your problem seems similar to this, so try:
public class Product
{
public Guid ProductID { get; set; }
public string Name { get; set; }
...
[InverseProperty("Product")]
public virtual List<RelatedCatalogs> RelatedCatalogs { get; set; }
}
public class RelatedCatalogs : EntityBase
{
public Guid ID { get; set; }
[ForeignKey("Product")]
public Guid ProductID { get; set; }
public Product Product { get; set; }
[ForeignKey("RelatedProductCatalog")]
public Guid RelatedProductID { get; set; }
public Product RelatedProductCatalog { get; set; }
public int Priority { get; set; }
}
Then you can add the mentioned fluent code to avoid cycles:
modelBuilder.Entity<RelatedCatalogs>()
.HasRequired(r => r.RelatedProductCatalog)
.WithMany()
.HasForeignKey(r => r.RelatedProductID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<RelatedCatalogs>()
.HasRequired(r => r.Product)
.WithMany(p => p.RelatedCatalogs)
.HasForeignKey(r => r.ProductID);
Related
I'm trying to create a many to many relation in the Fluent API with Ef Core 6 but i am having trouble understanding how to do so.
I've looked around here in stackoverflow but couldn't understand this relation and how to reproduce it in my code.
I have a table in my SQL database called People:
People.cs:
public class People : PeopleBase
{
public People()
{
RegistrationList = new HashSet<Registration>();
}
public virtual ICollection<Registration> RegistrationList { get; set; }
public virtual User User { get; set; }
public virtual ActivityGroup ActivityGroup { get; set; }
}
PeopleBase.cs:
public abstract class PeopleBase: ModelBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid PeopleId { get; set; }
public Guid? UserId { get; set; }
public Guid? ActivityGroupId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public PeopleActiveType Active { get; set; }
}
And then i have another table called ActivityGroup:
ActivityGroup.cs:
public class ActivityGroup : ActivityGroupBase
{
public ActivityGroup()
{
PeopleList = new HashSet<People>();
ActivityList = new HashSet<Activity>();
}
public ICollection<People> PeopleList { get; set; }
public ICollection<Activity> ActivityList { get; set; }
}
ActivityGroupBase.cs:
public abstract class ActivityGroupBase : ModelBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ActivityGroupId { get; set; }
[Required]
[StringLength(20)]
public string Name { get; set; }
public StatusRecord Status { get; set; }
}
How would i do the mapping in the modelBuilder given that:
ActivityGroupId is the foreing key in the People database, pointing to the other table
One PeopleId can have multiple (many) ActivityGroupId
One ActivityGroupId can be assigned to multiple people.
What i've done so far:
modelBuilder.Entity<People>()
.HasOne(x => x.ActivityGroup)
.WithMany(x => x.PeopleList)
.HasForeignKey(x => x.ActivityGroupId);
Wouldn't i have to do this instead?
modelBuilder.Entity<People>()
.Hasmany(x => x.ActivityGroupList) //this is a ICollection<ActivityGroup> inside People class
.WithMany(x => x.PeopleList)
.HasForeignKey(x => x.ActivityGroupId); // this is not recognized by Ef Core
Can anyone help me please?
There are two main approaches for many-to-many relationships - with implicit junction table:
public class People : PeopleBase
{
// ...
public virtual List<Activity> Activities { get; set; }
}
public class Activity // : ...
{
// ...
public virtual List<People> PeopleList { get; set; }
}
modelBuilder.Entity<People>()
.HasMany(x => x.Activities)
.WithMany(x => x.PeopleList);
Or with explicit one:
public class People
{
// ...
public ICollection<PeopleActivity> PeopleActivities { get; set; }
}
public class Activity
{
// ...
public virtual ICollection<PeopleActivity> PeopleActivities { get; set; }
}
public class PeopleActivity
{
public Guid ActivityId { get; set; }
public Guid PeopleId { get; set; }
public Activity Activity { get; set; }
public People People { get; set; }
}
modelBuilder.Entity<PeopleActivity>()
.HasOne(pt => pt.People)
.WithMany(t => t.PeopleActivities)
.HasForeignKey(pt => pt.PeopleId);
Also maybe it worth changing entity name from People to Person (you can change table name with .ToTable("People") call)?
In my regular .NET Framework application, I was using EF 6.x and was also using some Inheritance, specifically:
PurchaseOrder.cs and SaleOrder.cs both inherit from Order.cs
And in the OnModelCreating() on my context class inheriting from IdentityDbContext, I was doing:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
This used to work, but now I am moving my application to .NET Core 2.0 and I am using EF Core. What achieves the same thing in EF Core? Because right now I am getting the error:
System.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_Order_Business_CustomerId' on table 'Order' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
UPDATE
Here's the code after Ahmar's answer. In my context class, I have:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.HasDefaultSchema("PD");
builder.Entity<Customer>()
.HasMany(c => c.SaleOrders)
.WithOne(e => e.Customer)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<Supplier>()
.HasMany(po => po.PurchaseOrders)
.WithOne(e => e.Supplier)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<PurchaseOrder>()
.HasMany(li => li.LineItems)
.WithOne(po => po.PurchaseOrder)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<SaleOrder>()
.HasMany(li => li.LineItems)
.WithOne(po => po.SaleOrder)
.OnDelete(DeleteBehavior.SetNull);
}
And as far the Entities, they are:
public abstract class Business : IEntity
{
protected Business()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string Name { get; set; }
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public ICollection<Address> Addresses { get; set; } = new List<Address>();
public ICollection<Contact> Contacts { get; set; } = new List<Contact>();
}
[Table("Customers")]
public class Customer : Business
{
public decimal AllowedCredit { get; set; }
public decimal CreditUsed { get; set; }
public int NumberOfDaysAllowedToBeOnMaxedOutCredit { get; set; }
public ICollection<SaleOrder> SaleOrders { get; set; }
}
[Table("Suppliers")]
public class Supplier : Business
{
public ICollection<PurchaseOrder> PurchaseOrders { get; set; }
}
public abstract class Order : IEntity
{
protected Order()
{
Date = DateTime.UtcNow;
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public DateTime Date { get; set; }
public decimal ShippingCost { get; set; }
public Currency ShippingCurrency { get; set; }
public decimal ShippingConversionRate { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public ICollection<Invoice> Invoices { get; set; }
public ICollection<Note> Notes { get; set; }
}
[Table("PurchaseOrders")]
public class PurchaseOrder : Order
{
public int SupplierOrderNumber { get; set; }
public PurchaseOrderStatus Status { get; set; }
public decimal Vat { get; set; }
public decimal ImportDuty { get; set; }
public int SupplierId { get; set; }
public Supplier Supplier { get; set; }
public ICollection<PurchaseOrderLineItem> LineItems { get; set; }
}
[Table("SaleOrders")]
public class SaleOrder : Order
{
public decimal AmountToBePaidOnCredit { get; set; }
public SaleOrderStatus Status { get; set; }
public ICollection<SaleOrderLineItem> LineItems { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
So after doing what Ahmar suggested, I still get the same error when I do update-database.
You need to configure cascade delete behavior on each entity in .Net Core EF.
The Entity Framework Core Fluent API OnDelete method is used to specify the action which should take place on a dependent entity in a relationship when the principal is deleted.
The OnDelete method takes a DeleteBehavior enum as a parameter:
Cascade - dependents should be deleted
Restrict - dependents are
unaffected
SetNull - the foreign key values in dependent rows should
update to NULL
Example:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int? CompanyId { get; set; }
public Company Company { get; set; }
}
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.HasMany(c => c.Employees)
.WithOne(e => e.Company).
.OnDelete(DeleteBehavior.SetNull);
}
When deleting the Company, it will set CompanyId property in Employee table to null.
Get more detail at Configuring One To Many Relationships
PS. Please make sure your all referencing properties should be null able so, EF Core can set them null on delete. like CompanyId in about example.
Currently I'm working with WebApi and Entity Framework, So I have 3 entities: Products, Categories and ProductCategory; their relationships are:
My problem is that Category entity has a Category Parent property, so it's recursive, my Category Controller looks like this:
public async Task<IHttpActionResult> GetCategory()
{
var category = await db.Category.Select(x=>new {
x.categoryDesc,
x.CategoryId,
x.categoryImage,
x.categoryName,
x.categoryParent
}).ToListAsync();
return Ok(category);
}
I'm returning an anonymous object, the propierty categoryParent its the same object as category so its recursive; when I fill the database with mock data in the Category table and call the get method, everything runs OK because I dont have any data en ProductCategory, but when I fill it(the ProductCategory table) the program crashes.
MY entity classes are:
public class Category {
public int CategoryId { set; get; }
public string categoryName { set; get; }
public string categoryDesc { set; get; }
public string categoryImage { set; get; }
public int? categoryParentId { set; get; }
public virtual ICollection<ProductCategory> ProductCategories { set; get; }
public virtual Category categoryParent { set; get; }
}
public class Product{
public int ProductId { set; get; }
public string productName { set; get; }
public string productDesc { set; get; }
public double productPrice { set; get; }
public string productUrl { set; get; }
public DateTime productPublishDate { set; get; }
public DateTime productModifyDate { set; get; }
public bool productStatus { set; get; }
public int productStock { set; get; }
public virtual ICollection<ProductCategory> ProductCategories { set; get; }
}
public class ProductCategory : IProductCategory {
[Required]
[Key]
[ForeignKey("Category")]
[Column(Order = 1)]
public int CategoryId { set; get; }
[Required]
[Key]
[ForeignKey("Product")]
[Column(Order = 2)]
public int ProductId { set; get; }
public virtual Product Product { set; get; }
public virtual Category Category { set; get; }
}
Can you help me to fix it?, So when I return categoryParent return it recursively, Thanks
I'm guessing you might have better luck if you explicitly state how you want the information organized, and remove the virtual property
IQueryable<Category> category = db.Category;
var result = category.Where(w => w.categoryParentId != null)
.Join(category,
child => (int)child.categoryParentId,
parent => parent.CategoryId,
(child, parent) => new {
child.categoryDesc,
child.CategoryId,
child.categoryImage,
child.categoryName,
parent
}
);
return Ok(await result.ToListAsync());
That should get you the same result as your query above, then you could remove:
public virtual Category categoryParent { set; get; }
Thank you very much but I found the solution: https://practiceaspnet.wordpress.com/2015/11/09/many-to-many-relationships-with-additional-fields/
I used fluent API to resolve the navigation recursive problem I had:
modelBuilder.Entity<Category>()
.HasMany(x => x.ProductCategories)
.WithRequired(x => x.Category)
.HasForeignKey(x=>x.CategoryId);
modelBuilder.Entity<Product>()
.HasMany(x => x.ProductCategories)
.WithRequired(x => x.Product)
.HasForeignKey(x => x.ProductId);
Basically the WithRequired method prevents a navigation property on the other side of the relationship so it stops the recursion
Im trying to do a one-to-many map by using fluent api.
This is my classes
public class Product : EntityBase
{
public Product()
{
this.ProductArticles = new List<ProductArticle>();
}
[Key]
public int ProductId { get; set; }
public string Description { get; set; }
public string ReportText1 { get; set; }
public string ReportText2 { get; set; }
public bool Standard { get; set; }
public int ProductGroupId { get; set; }
public decimal? Surcharge1 { get; set; }
public decimal? Surcharge2 { get; set; }
public decimal? Surcharge3 { get; set; }
public decimal? Surcharge4 { get; set; }
public decimal PriceIn { get; set; }
public decimal PriceOut { get; set; }
public decimal PriceArtisanIn { get; set; }
public decimal PriceArtisanOut { get; set; }
public decimal PriceTotalIn { get; set; }
public decimal PriceTotalOut { get; set; }
public decimal PriceTotalOutVat { get; set; }
public decimal PriceAdjustment { get; set; }
public bool Calculate { get; set; }
public string Notes { get; set; }
[ForeignKey("ProductGroupId")]
public virtual ProductGroup ProductGroup { get; set; }
public virtual ICollection<ProductArticle> ProductArticles { get; set; }
}
public class ProductArticle : EntityBase
{
[Key]
public int ProductArticleId { get; set; }
public int ProductId { get; set; }
public int ArticleId { get; set; }
public decimal Qty { get; set; }
public decimal PriceIn { get; set; }
public bool Primary { get; set; }
public virtual Product Product { get; set; }
public virtual Article Article { get; set; }
}
Now i want from single Product include all ProductArticles
This is my mapping
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
// Primary Key
this.HasKey(p => p.ProductId);
// Table & Column Mappings
this.ToTable("Product");
this.HasMany(p => p.ProductArticles)
.WithOptional()
.Map(p => p.MapKey("ProductId").ToTable("ProductArticle"));
}
But it doesnt work.. Please help :)
First - by convention EF treats property with name equal to Id or EntityTypeName + Id is a primary key. So, you don't need to configure that manually.
Second - if you don't want table names to be plural, just remove that convention from your context instead of providing table name for each entity mapping:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
And last - EF smart enough to define foreign keys which have names like RelatedEntityTypeName + Id. So, you don't need any fluent configurations here.
I am using EF5 (Code 1st) and doing all my configurations with the Fluent API. My model looks like this:
public class AddressType
{
public int AddressTypeID { get; set; }
public string Name { get; set; }
}
public class Address
{
public int AddressID { get; set; }
public int StateID { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public State State { get; set; }
public ICollection<Person> People { get; set; }
}
public class Person
{
public int PersonID { get; set; }
public string Name { get; set; }
public ICollection<Address> Addresses { get; set; }
}
My database contains tables for the above classes + the below Many:Many table:
Person.Person2Address
(
PersonID INT NOT NULL,
AddressID INT NOT NULL,
AddressTypeID INT NOT NULL,
)
The 3 fields above are all foreign keys & the 3 together make up the Primary Key for the table.
Typically my M:M setups only involve 2 fields in the PK. And I would map it like this:
var addressCfg = mb.Entity<Address>();
addressCfg.ToTable("Address", "Geo");
addressCfg.HasMany(a => a.People)
.WithMany(p => p.Addresses)
.Map(mmc =>
{
mmc.ToTable("Person2Address", "Person");
mmc.MapLeftKey("AddressID");
mmc.MapRightKey("PersonID");
});
But I don't know how to configure this 3rd field in the PK or how CRUD would even work in EF in this case.
Any examples of how this should be handled would be greatly appreciated.
You can't map this as many-to-many relationship. You need three one-to-many relationships with an intermediate additional entity Person2Address that represents the link table with the three keys. The collections in Person and Address must both refer to this intermediate entity (and optionally also a collection in AddressType).
The model would be like this:
public class AddressType
{
public int AddressTypeID { get; set; }
public string Name { get; set; }
// public ICollection<Person2Address> Person2Addresses { get; set; }
// optionally you can include this collection or not
}
public class Address
{
public int AddressID { get; set; }
public int StateID { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public State State { get; set; }
public ICollection<Person2Address> Person2Addresses { get; set; }
}
public class Person
{
public int PersonID { get; set; }
public string Name { get; set; }
public ICollection<Person2Address> Person2Addresses { get; set; }
}
public class Person2Address
{
public int PersonID { get; set; }
public int AddressID { get; set; }
public int AddressTypeID { get; set; }
public Person Person { get; set; }
public Address Address { get; set; }
public AddressType AddressType { get; set; }
}
And the mapping with Fluent API:
modelBuilder.Entity<Person2Address>()
.HasKey(p2a => new { p2a.PersonID, p2a.AddressID, p2a.AddressTypeID });
modelBuilder.Entity<Person2Address>()
.HasRequired(p2a => p2a.Person)
.WithMany(p => p.Person2Addresses)
.HasForeignKey(p2a => p2a.PersonID);
modelBuilder.Entity<Person2Address>()
.HasRequired(p2a => p2a.Address)
.WithMany(a => a.Person2Addresses)
.HasForeignKey(p2a => p2a.AddressID);
modelBuilder.Entity<Person2Address>()
.HasRequired(p2a => p2a.AddressType)
.WithMany()
.HasForeignKey(p2a => p2a.AddressTypeID);
Or use WithMany(at => at.Person2Addresses) in the last mapping if you want to include the collection in AddressType.