I'm having a problem with many-to-many relationships in a pizzeria system I'm developing.
I have an entity called "Payament" that has a list of Pizzas and a list of Drinks.
ex:
public class Payament
{
public string? PayamentId { get; set; }
public virtual List<Pizza> Pizzas { get; set; }
public virtual List<Drink> Drinks { get; set; }
public string? CPFId { get; set; }
[JsonIgnore]
public virtual Client? Client { get; set; }
public double? TotalPay { get; set; }
public DateTime DateTransaction { get; set; }
public virtual StatusOrder StatusOrder { get; set; }
public Payament()
{
Pizzas = new List<Pizza>();
Drinks = new List<Drink>();
DateTransaction = DateTime.Now;
StatusOrder = StatusOrder.CARRINHO;
}
}
Also, I have two entities Pizza and Drink.
ex:
public class Pizza : IItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
public double Value { get; set; }
[JsonIgnore]
public virtual List<Payament> Payament { get; set; }
public Pizza()
{
Payament = new List<Payament>();
}
}
public class Drink : IItem
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Quantity { get; set; }
public double Value { get; set; }
[JsonIgnore]
public virtual List<Payament> Payament { get; set; }
public Drink()
{
Payament = new List<Payament>();
}
}
These classes are configured as many-to-many in OnModelCreating()...
Drink:
builder.HasMany(h => h.Payament)
.WithMany(d => d.Drinks);
Pizza:
builder.HasMany(h => h.Payament)
.WithMany(p => p.Pizzas);
Payament:
builder.HasMany(p => p.Pizzas)
.WithMany(p => p.Payament);
builder.HasMany(d => d.Drinks)
.WithMany(d => d.Payament);
Migrations were generated, all right. But when I go to send a payment, I get this error:
MySqlException: Cannot add or update a child row: a foreign key constraint fails (pizzaroller.drinkpayament, CONSTRAINT FK_DrinkPayament_Payament_PayamentId FOREIGN KEY (PayamentId) REFERENCES payament (PayamentId) ON DELETE CASCADE)
What I believe it could be:
If I'm not mistaken, a drinkpayament table and a pizzapayament table are created where the primary keys between the relations are joined.
And in this case, "Payament" has a primary key of type string, while items "Pizza" and "Drink" have a primary key of type int.
The conflict is likely to be found in this divergence. I could be wrong, of course.
So I would like to know how I can solve this problem. And also, if possible, tips on how I can work with existing items for sale and relate them to a payment.
Project is on github if you want to take a look:
https://github.com/newhobbye/pizza-roller-api
This project is for the exercise of knowledge and some patterns that I will implement to practice. I accept any kind of criticism! A big hug and many thanks!
I managed to solve.
I was resending the client entity in update. And I hadn't turned off the AsNoTracking() option;
In addition to this problem, I was also resending the items instead of associating the existing ones by id.
Thank you all for your help!
Related
I'm having trouble adding multiple entities with multiple children at once in foreach loop.
Its obvious ef cannot track ids in foreach loop. But maybe there are other solutions that you can guide me.
The error when I tried to add multiple entities with a child is:
The instance of entity type cannot be tracked because of another instance
with the same key value for {'Id'} is already being tracked. When
attaching existing entities, ensure that only one entity instance with
a given key value is attached.
For example:
public class Order
{
public Order()
{
OrderDetails = new HashSet<OrderDetail>();
}
[Key]
public int Id { get; set; }
[StringLength(50)]
public string Code { get; set; }
public int? CompanyId { get; set; }
public int? PartnerId { get; set; }
public decimal TotalNetPrice { get; set; }
public decimal TotalPrice { get; set; }
public bool IsActive { get; set; } = true;
public bool IsDeleted { get; set; } = false;
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
[ForeignKey("PartnerId")]
public virtual Partner Partner { get; set; }
public virtual ICollection<OrderDetail> OrderDetails { get; set; }
}
public class OrderDetail
{
[Key]
public int Id { get; set; }
public int OrderId { get; set; }
[StringLength(50)]
public string Code { get; set; }
public int LineNumber { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
public bool IsActive { get; set; } = true;
public bool IsDeleted { get; set; } = false;
[ForeignKey("OrderId")]
public virtual Order Order { get; set; }
[ForeignKey("ProductId")]
public virtual Product Product { get; set; }
}
Here is my code in the method:
foreach (var order in orderList)
{
// consider we create/cast object to Order class.
_orderService.Add(order);
// in here we don't have id, because we didn't call savechanges.
foreach(var orderDetail in order.orderDetailList)
{
// consider we create/cast object to OrderDetail class.
orderDetail.orderId = order.Id;
// in here, order id is always 0 as expected.
_order.OrderDetails.Add(orderDetail);
}
}
try
{
await uow.SaveChangesAsync();
}
catch(Exception exception)
{
var msg = exception.Message;
}
I tried to use [DatabaseGenerated(DatabaseGeneratedOption.Identity)] attribute for the identity columns.
According to documentation (https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations):
Depending on the database provider being used, values may be generated
client side by EF or in the database. If the value is generated by the
database, then EF may assign a temporary value when you add the entity
to the context. This temporary value will then be replaced by the
database generated value during SaveChanges().
So it should give at least temp id to track it. But it didn't work with my case.
I also tried the same approach on model creating a part in context. But again the same result. No success.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.Property(x => x.Id)
.ValueGeneratedOnAdd();
}
It seems like the best solution is to make a transaction and save order before details and get real id and then add details. But it has performance issues as you know.
I'm wondering if there is any other best practice for that issue?
Thank you.
Try this:
foreach (var order in orderList)
{
_orderService.Add(order);
foreach(var orderDetail in order.orderDetailList)
{
// Add reference Of Order to OrderDetails, not an id
orderDetail.Order = order;
_order.OrderDetails.Add(orderDetail);
}
}
In this case EF will know how to connect Order and OrderDetail on SaveChangesAsync
I'm trying to built a related items type model using Entity Framework in my .NET Core 1.1 MVC application. I keep running into the following error (tried all combinations of deletebehaviours with foreign keys):
Introducing FOREIGN KEY constraint
'FK_MenuItemRelation_MenuItems_RelatedMenuItemId' on table
'MenuItemRelation' may cause cycles or multiple cascade paths. Specify
ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN
KEY constraints. Could not create constraint or index. See previous
errors.
Before I go and simply use a mapping table, I would like to hear the community input on this.
Model builder
//MenuItem <> MenuItem many to many (related item) mapping
modelBuilder.Entity<MenuItemRelation>()
.HasKey(mr => new { mr.PrimaryMenuItemId, mr.RelatedMenuItemId });
modelBuilder.Entity<MenuItemRelation>()
.HasOne(mr => mr.PrimaryMenuItem)
.WithMany()
.HasForeignKey(mr => mr.PrimaryMenuItemId);
modelBuilder.Entity<MenuItemRelation>()
.HasOne(mr => mr.RelatedMenuItem)
.WithMany()
.HasForeignKey(mr => mr.RelatedMenuItemId).OnDelete(DeleteBehavior.Restrict);
Domain model
public class MenuItem
{
public string Category { get; set; }
public string Description { get; set; }
public long ID { get; set; }
public Menu Menu { get; set; }
public string Name { get; set; }
public string PictureUrls { get; set; }
public float Price { get; set; }
public string Reference { get; set; }
public ICollection<MenuItemRelation> RelatedItems { get; set; }
public string Status { get; set; }
}
Mapping Entity
public class MenuItemRelation
{
public MenuItem PrimaryMenuItem { get; set; }
public long PrimaryMenuItemId { get; set; }
public MenuItem RelatedMenuItem { get; set; }
public long RelatedMenuItemId { get; set; }
}
So I modeled the desired SQL outcome and reverse engineered the many to many relationship by using database first approach as explained here Entity Framework Core creating model from existing database. The result is below.
Basically it just required me to add both the incoming relation as well as the outgoing relation on the menuitem class.
Feel free to comment if there are better ways of doing this.
Table TSQL
Create TABLE MenuItem(
ID int IDENTITY(1,1),
Name nvarchar(max)
PRIMARY KEY (ID)
)
GO
CREATE TABLE MenuItemRelation (
PrimaryMenuItemId int,
RelatedMenuItemId int,
PRIMARY KEY (PrimaryMenuItemId, RelatedMenuItemId),
FOREIGN KEY (PrimaryMenuItemId) REFERENCES MenuItem (ID),
FOREIGN KEY (RelatedMenuItemId) REFERENCES MenuItem (ID)
)
GO
Insert into dbo.MenuItem values ('MenuItemA')
Insert into dbo.MenuItem values ('MenuItemB')
Insert into dbo.MenuItem values ('MenuItemC')
GO
INSERT into dbo.MenuItemRelation values (1,2)
INSERT into dbo.MenuItemRelation values (1,3)
GO
DELETE from dbo.MenuItem where ID = 1
GO
//Confirmed no cascading happens
Domain model (with some naming edits)
public class MenuItem
{
public string Category { get; set; }
public string Description { get; set; }
public long ID { get; set; }
public Menu Menu { get; set; }
public string Name { get; set; }
public string PictureUrls { get; set; }
public float Price { get; set; }
public string Reference { get; set; }
public ICollection<MenuItemRelation> RelatedItems { get; set; }
public ICollection<MenuItemRelation> RelatedTo { get; set; }
public string Status { get; set; }
}
Mapping object
public class MenuItemRelation
{
public MenuItem PrimaryMenuItem { get; set; }
public long PrimaryMenuItemId { get; set; }
public MenuItem RelatedMenuItem { get; set; }
public long RelatedMenuItemId { get; set; }
}
Model builder
modelBuilder.Entity<MenuItemRelation>()
.HasKey(e => new { e.PrimaryMenuItemId, e.RelatedMenuItemId });
modelBuilder.Entity<MenuItemRelation>()
.HasOne(d => d.PrimaryMenuItem)
.WithMany(p => p.RelatedItems)
.HasForeignKey(d => d.PrimaryMenuItemId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<MenuItemRelation>()
.HasOne(d => d.RelatedMenuItem)
.WithMany(p => p.RelatedTo)
.HasForeignKey(d => d.RelatedMenuItemId)
.OnDelete(DeleteBehavior.Restrict);
I'm missing something when using the data annotations.
This is my first class
[Table("PriceFeed")]
public class PriceFeed : History
{
public PriceFeed()
{
this.Votes = new List<PriceVote>();
this.History = new List<PriceFeed__History>();
}
[Key]
public long Id { get; set; }
[ForeignKey("Store")]
public long Store_Id { get; set; }
[ForeignKey("Item")]
public long Item_Id { get; set; }
[Required]
public decimal Price { get; set; }
public Store Store { get; set; }
public Item Item { get; set; }
public virtual ICollection<PriceFeed__History> History { get; set; }
}
And this is my second class
[Table("PriceFeed__History")]
public class PriceFeed__History : History
{
[Key]
public long Id { get; set; }
[ForeignKey("PriceFeed")]
public long PriceFeed_Id { get; set; }
[Required]
public decimal Price { get; set; }
public virtual PriceFeed PriceFeed { get; set; }
}
When I run the add-migration, it creates the database correctly but when I try to access PriceFeed.History it gives me an error
{"Message":"An error has occurred.","ExceptionMessage":"A specified Include path is not valid. The EntityType 'Verdinhas.Web.Contexts.PriceFeed' does not declare a navigation property with the name 'PriceFeed__History'."
I always worked with API Fluent and typed by myself the code like
.Entity<Student>()
.HasRequired<Standard>(s => s.Standard)
.WithMany(s => s.Students)
.HasForeignKey(s => s.StdId);
But now I'm using the data annotations and when I generate the migration, it does not create the "withmany" like the above.
What am I doing wrong?
The issue has nothing to do with Data Annotations which seems to be correct in your model.
As mentioned in the comments, the exception is caused by a code that tries to use Include method with string "'PriceFeed__History" - you seem to think that you should specify the related entity types, but in fact you need to specify the navigation property names, which in your case is "History".
I have a DbContext which I via the developer command prompt and creating a migrations schema turn in to my database. But if you look at the product object I have a dictionary object named Parts. That property does not get added to the Product table when the database is updated in the command prompt. I don't even know if it is possible what I am trying to do.
I want to add a table in the database named Parts and then add a foreign key to the Product table which connects the Parts dictionary object in the Product table, and the the new Parts table. Is this possible with Entity Framework Core?
public class ShoppingDbContext : IdentityDbContext<User>
{
public ShoppingDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
Dictionary<string, Part> Parts { get; set; }
}
EF Core can't currently map a dictionary property directly. If you want to create an association between Products and Parts, then define each of them as an entity. You can then create navigation properties between them--a reference from Part to the Product which it belongs, and a collection of Parts on Product. For example:
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
public ICollection<Part> Parts { get; set; }
}
public class Part
{
public int PartId { get; set; }
public int ProductId { get; set; }
public Product Product { get; set;}
}
Part also defines a property ProductId that acts as the FK to the Product entity. You don't need to add that property--EF will simulate it for you if you don't want it, but usually it is easier to deal with entities if the FK is mapped to a property.
Relationships are tracked through object references instead of foreign key properties. This type of association is called an independent association.
More Details Here:
https://msdn.microsoft.com/en-us/data/jj713564.aspx
Sample code:
public partial class Product
{
public Product()
{
this.Parts = new HashSet<Part>();
}
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
public virtual ICollection<Part> Parts { get; set; }
}
Basically like what Arthur said, EF Core does not support it yet.
However, another way is to create a composite table should you want to or if it's viable for your use.
Here's a simple example:
// -------------- Defining BrandsOfCategories Entity --------------- //
modelBuilder.Entity<BrandCategory>()
.HasKey(input => new { input.BrandId, input.CatId })
.HasName("BrandsOfCategories_CompositeKey");
modelBuilder.Entity<BrandCategory>()
.Property(input => input.DeletedAt)
.IsRequired(false);
// -------------- Defining BrandsOfCategories Entity --------------- //
public class BrandCategory
{
public int CatId { get; set; }
public int BrandId { get; set; }
public DateTime? DeletedAt { get; set; }
public Category Category { get; set; }
public Brands Brand { get; set; }
}
The DeletedAt is optional of course. This handles M-M Relationships.
I had the same issue, I resolved it by removing the keyword virtual on the navigation properties and with in the ApplicatinDbContext
I have a table UserForms that has two foreign keys to a Countries table, but on creating my controller and create view (for the UserForms model) the two fields linking to the foreign keys do not appear. What should I do to sort this problem? Below are the two models:
public class UserForms
{
public int Id { get; set; }
public string FullNames { get; set; }
public Countries IndividualsCountry { get; set; }
public Countries BusinessCountry { get; set; }
}
public class Countries
{
public Countries()
{
this.STRBusinessCountry = new HashSet<UserForms>();
this.STRIndividualsCountry = new HashSet<UserForms>();
}
public int Id { get; set; }
public string NameOfCountry { get; set; }
[InverseProperty("IndividualsCountry")]
public virtual ICollection<UserForm> STRIndividualsCountry { get; set; }
[InverseProperty("BusinessCountry")]
public virtual ICollection<UserForm> STRBusinessCountry { get; set; }
}
The comment left by #T.Glatzer is correct. You should expose foreign key properties on your dependent entities:
public class UserForms
{
public int Id { get; set; }
public string FullNames { get; set; }
public int IndividualsCountryId { get; set; }
[ForeignKey("IndividualsCountryId")]
public virtual Countries IndividualsCountry { get; set; }
public int BusinessCountryId { get; set; }
[ForeignKey("BusinessCountryId")]
public virtual Countries BusinessCountry { get; set; }
}
Here I used int, but if either of these navigation properties are optional, you would just substitute int? or System.Nullable<int> instead (which will create an int NULL column in the database rather than an int NOT NULL).
Although EF does not require you to expose navigation properties, it is generally a good practice to. Trust me. It will help you avoid unexpected exceptions later on. In fact, some EF exception messages actually recommend exposing foreign key properties on the entity classes to help EF better figure out how to map relationships. Here is an example of one such exception. Note "Additional Information" section:
{"The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_dbo.DependentTable_dbo.PrincipalTable_Id". The conflict
occurred in database "DatabaseName", table "dbo.PrincipalTable", column
'Id'. The statement has been terminated."}
Additional information: An error occurred while saving entities that
do not expose foreign key properties for their relationships. The
EntityEntries property will return null because a single entity cannot
be identified as the source of the exception. Handling of exceptions
while saving can be made easier by exposing foreign key properties in
your entity types. See the InnerException for details.
#danludwig thanks for expounding #T.Glatzer answer this has worked for me! thank you. my final code that is now working is
public class UserForms
{
public int Id { get; set; }
public string FullNames { get; set; }
[ForeignKey("IndividualsCountry")]
public int? IndividualsCountryId { get; set; }
[ForeignKey("BusinessCountry")]
public int? BusinessCountryId { get; set; }
public virtual Countries IndividualsCountry { get; set; }
public virtual Countries BusinessCountry { get; set; }
}
public class Countries
{
public Countries()
{
this.STRBusinessCountry = new HashSet<UserForms>();
this.STRIndividualsCountry = new HashSet<UserForms>();
}
public int Id { get; set; }
public string NameOfCountry { get; set; }
[InverseProperty("IndividualsCountry")]
public virtual ICollection<UserForms> STRIndividualsCountry { get; set; }
[InverseProperty("BusinessCountry")]
public virtual ICollection<UserForms> STRBusinessCountry { get; set; }
}