EF Core 6 Update Data direct from an Object, n-n, n-1, 1-1
Hello together
I have some trouble to add a simple update Endpoint to my asp.Net API where I want to update an Entity and also his relations.
I already tried a lot of ways to update the data and the relations in one step, but I had different errors, like tracking error, duplicate key in joining table or no error but the relation was not updated.
So first I tried to update my data using the connected State with the tracking of EF Core. Normaly I want to use the DTO Pattenr with Automapper but to keep it simple I have an example without this step between.
Here is my model configuration of product:
public class Product
{
[Key]
public int ProductId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
[Required]
public double Price { get; set; }
public SubCategorie SubCategorie { get; set; } //Relation n-1 to Table Subcategorie
public ICollection<Extra> Extra { get; set; } //Relation n-n to Table Extra
public DateTime CreatedDate { get; set; } = DateTime.Now;
public string CreatedBy { get; set; }
public string UpdatedBy { get; set; }
public DateTime UpdatedDate { get; set; }
}
And here the config of the extra model which is in n-n relation to the product model:
public class Extra
{
[Key]
public int ExtraId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Tooltip { get; set; }
[Required]
public double Price { get; set; }
public ICollection<Product> Products { get; set; } //Relation n-n to Table Product
public ICollection<ProductConfig> ProductConfigs { get; set; } //Relation n-n to Table PrductConfig
public DateTime CreatedDate { get; set; } = DateTime.Now;
public string CreatedBy { get; set; }
public string UpdatedBy { get; set; }
public DateTime UpdatedDate { get; set; }
}
and here the onModelCreatin Method in DBContex, where I configure the relations:
// Product
// n-n to Extra
modelBuilder.Entity<Product>()
.HasMany<Extra>(e => e.Extra)
.WithMany(e => e.Products);
//n-n to Type
modelBuilder.Entity<Product>()
.HasMany<ProductType>(e => e.ProductType)
.WithMany(e => e.Products);
// n-1 to Subcategorie
modelBuilder.Entity<Product>()
.HasOne<SubCategorie>(e => e.SubCategorie)
.WithMany(e => e.Product);
// 1-n to productconfig
modelBuilder.Entity<Product>()
.HasMany<ProductConfig>(e => e.ProductConfigs)
.WithOne(e => e.Product);
Here is the example with the connected state. The problem is here that nothing at the relations happened, for example when an extra object is added and comes with the "porductNew" input.
public async Task<Product> UpdateProduct(int productId, Product productNew)
{
try
{
if (productId == productNew.ProductId)
{
//valid
Product product = await _db.Product.AsNoTracking()
.Include(x => x.Extra)
.Include(x => x.ProductType)
.FirstOrDefaultAsync(x => x.ProductId == productId);
product = productNew;
await _db.SaveChangesAsync();
return product;
}
else
{
//invalid
throw new Exception("Id for updating and Id in the Data not match");
}
}
catch (Exception ex)
{
throw new Exception(ex.ToString());
}
}
When I set the entity State manually to modified I have a tracking error "the istnace could not been tracked because another instance with the same keyvalue..."
So I tried also with the disconnected state schema. For this I use the update Method. There the relations will be added correct, but when i commit the same changes again, EF Core set the state inside the joining table to added than modified
Here is the repository for this pattern:
public async Task<Product> UpdateProduct(int productId, Product productNew)
{
try
{
if (productId == productNew.ProductId)
{
//valid
Product product = await _db.Product.AsNoTracking()
.Include(x => x.SideDishs)
.Include(x => x.Extra)
.Include(x => x.ProductType)
.FirstOrDefaultAsync(x => x.ProductId == productId);
product = productNew;
var updatedProduct = _db.Product.Update(product);
await _db.SaveChangesAsync();
return product;
}
else
{
//invalid
throw new Exception("Id for updating and Id in the Data not match");
}
}
catch (Exception ex)
{
throw new Exception(ex.ToString());
}
}
Here is the changetracker from EF Core debugging. The Joining table should in this case have the state modified than added because there was no change at the relations. The error of duplicate key in joining table happens:
Extra {ExtraId: 1} Modified
ExtraId: 1 PK
CreatedBy: 'Admin' Modified
CreatedDate: '19.06.2022 09:54:16' Modified
Name: 'zwei Teller' Modified
Price: 0 Modified
Tooltip: 'Das Essen wird auf zwei Teller serviert' Modified
UpdatedBy: Modified
UpdatedDate: '01.01.0001 00:00:00' Modified
ProductConfigs:
Products: [{ProductId: 1}]
Extra {ExtraId: 2} Modified
ExtraId: 2 PK
CreatedBy: 'Admin' Modified
CreatedDate: '19.06.2022 09:55:27' Modified
Name: 'extra scharf' Modified
Price: 0 Modified
Tooltip: 'Gewürzt mit spezieller Chili Paste' Modified
UpdatedBy: Modified
UpdatedDate: '01.01.0001 00:00:00' Modified
ProductConfigs:
Products: [{ProductId: 1}]
ExtraProduct {ExtraId: 1, ProductsProductId: 1} Added
ExtraId: 1 PK FK
ProductsProductId: 1 PK FK
ExtraProduct {ExtraId: 2, ProductsProductId: 1} Added
ExtraId: 2 PK FK
ProductsProductId: 1 PK FK
Product {ProductId: 1} Modified
ProductId: 1 PK
CreatedBy: 'Admin' Modified
CreatedDate: '19.06.2022 10:27:17' Modified
Description: 'Beschreibung Testprodukt' Modified
Name: 'Testprodukt 1' Modified
Price: 15 Modified
SubCategorieId: FK Unknown
UpdatedBy: 'string' Modified
UpdatedDate: '19.06.2022 08:26:45' Modified
ProductConfigs:
SubCategorie:
Extra: [{ExtraId: 1}, {ExtraId: 2}]
ProductType: []
So does anybody have a simple idea or know a documentation, where the update sequence for ef-core6 with all types of relations with external objects for updating is described?