Adding Owned Entities With Many Relation Cause Concurrency Exception Due to Update instead of Insert - entity-framework

I have an issue where adding an owned entity causes EF Core to issue an UPDATE statement instead of an INSERT, causing a Concurrency Exception.
I have the following classes (simplified to keep the question shorter):
public abstract class PaymentDemand
{
public Guid Id { get; set; }
public SettlementTransactions? SettlementTransactions { get; set; }
// And more properties
}
public class SettlementTransactions
{
public List<SettlementAllowance> ConsumedAllowances { get; set; } = new List<SettlementAllowance>();
public List<SettlementCharge> GeneratedCharges { get; set; } = new List<SettlementCharge>();
}
public class SettlementAllowance
{
public Guid PaymentDemandId { get; set; }
/// <summary>The type of the transaction.</summary>
public string TransactionType { get; set; }
// And more properties
}
Then I have setup the payment demand as such:
public void Configure(EntityTypeBuilder<PaymentDemand> builder)
{
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedNever();
builder.OwnsOne(p => p.SettlementTransactions, transactionBuilder =>
{
transactionBuilder.OwnsMany(a => a.ConsumedAllowances, allowanceBuilder =>
{
allowanceBuilder.WithOwner().HasForeignKey(t => t.PaymentDemandId);
allowanceBuilder.HasKey(p => p.AccountTransactionId);
allowanceBuilder.Property(p => p.Amount).HasColumnType("Money");
allowanceBuilder.ToTable("SettlementAllowances");
});
transactionBuilder.OwnsMany(c => c.GeneratedCharges, chargesBuilder =>
{
chargesBuilder.WithOwner().HasForeignKey(t => t.PaymentDemandId);
chargesBuilder.HasKey(p => p.AccountTransactionId);
chargesBuilder.Property(p => p.AccountTransactionId).ValueGeneratedNever();
chargesBuilder.Property(p => p.Amount).HasColumnType("Money");
chargesBuilder.ToTable("SettlementCharges");
});
});
builder.Navigation(t => t.SettlementTransactions).IsRequired();
}
Lastly I have the code that does the saving.
public async Task DoThings(DbContext context)
{
var demand = await context.PaymentDemands
.Include(st => st.SettlementTransactions)
.FirstOrDefaultAsync(d => d.Id == id, default);
if (demand == null)
{
throw new InvalidOperationException();
}
demand.SettleDate = DateTime.UtcNow;
demand.SettlementTransactions ??= new SettlementTransactions();
demand.SettlementTransactions.ConsumedAllowances.Add(new SettlementAllowance{ TransactionType = "Blah" });
await context.SaveChangesAsync(); // THROWS.
}
The change tracker seems to be working out what it should do (from the log):
DetectChanges starting for 'SqlBillingContext'.
2 entities were added and 0 entities were removed from navigation 'SettlementTransactions.ConsumedAllowances' on entity with key '{PaymentDemandId: d00544fe-c6c5-4b1d-a438-81508332af2d}'.
Context 'SqlBillingContext' started tracking 'SettlementAllowance' entity with key '{AccountTransactionId: 296596b3-d45d-4753-b2f5-0ba7566f6800}'.
Context 'SqlBillingContext' started tracking 'SettlementAllowance' entity with key '{AccountTransactionId: 7395965f-7fda-46d9-9e3b-3b428d4a7dce}'.
But for some reason it ends up with an Update just after:
Executing update commands individually as the number of batchable commands (2) is smaller than the minimum batch size (4).
Executing DbCommand [Parameters=[#p11='296596b3-d45d-4753-b2f5-0ba7566f6800', #p0='2022-10-25T09:34:41.7143804+00:00', #p1='49.5' (Precision = 3) (Scale = 1), #p2=NULL (Size = 4000), #p3='2022-12-24T09:18:36.3176378+00:00' (Nullable = true), #p4='6ca8c244-4438-4476-b417-ed501c378c0e' (Nullable = true), #p5='12032' (Nullable = true), #p6='d00544fe-c6c5-4b1d-a438-81508332af2d', #p7='df99b62b-d1b6-4d58-8c69-4df7eef33b61' (Nullable = true), #p8='d00544fe-c6c5-4b1d-a438-81508332af2d' (Nullable = true), #p9='2022-11-24T09:18:37.3176378+00:00' (Nullable = true), #p10='1'], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [SettlementAllowances] SET [AccountingTime] = #p0, [Amount] = #p1, [Description] = #p2, [EndTime] = #p3, [InvoiceId] = #p4, [InvoiceNumber] = #p5, [PaymentDemandId] = #p6, [PaymentId] = #p7, [SourcePaymentDemandId] = #p8, [StartTime] = #p9, [TransactionType] = #p10
WHERE [AccountTransactionId] = #p11;
SELECT ##ROWCOUNT;
I really donĀ“t understand whats going on here. Any suggestions, ideas or obvious reasons why this is NOT working as I hope it would?

As is often the case, writing these post is almost like rubber ducking.
The issue in my case is that I have the primary key of the entity I create (since im basically moving it).
Thus EF thinks that it needs to generate a key of one does not exist, and one is provided, it assumes the record exists (default behaviour).
The solution was to simply add a Generation strategy of NEVER:
allowancesBuilder.Property(p => p.AccountTransactionId).ValueGeneratedNever();
Which I already had on the charges and the payments!.
The default is apparently ValueGeneratedSometimes....

Related

How to properly replace an owned collection of items?

I receive an exception when trying to replace the owned collection with the one I receive through API (EF Core 6).
System.InvalidOperationException: 'The instance of entity type 'Product' cannot be tracked because 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.
Here is my model configuration:
public class Order
{
public int Id { get; set; }
public ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public Product Product { get; set; }
public int Quantity { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
modelBuilder.Entity<Product>(x =>
{
x.HasKey(e => e.Id);
x.Property(e => e.Name);
});
modelBuilder.Entity<Order>(x =>
{
x.HasKey(e => e.Id);
x.OwnsMany(e => e.OrderItems, p =>
{
p.ToTable("OrderItems")
.WithOwner();
p.HasOne(e => e.Product).WithMany();
p.Property(e => e.Quantity);
});
});
Act:
//these are updated items received through API
var newItems = new List<OrderItem>
{
new OrderItem{ Product = new Product { Id = 2}, Quantity = 2},
new OrderItem{ Product = new Product { Id = 3}, Quantity = 3}
};
var existingOrder = _dbContext.Orders
.Include(o => o.OrderItems).ThenInclude(i => i.Product)
.Single(o => o.Id == 1);
// Existing OrderItems are already referencing Product with Id 2
existingOrder.OrderItems = newItems;
// Error occurs because Product with Id = 2 was already tracked
_dbContext.SaveChanges();
I know that the issue might be resolved by replacing Product with ProductId within my OrderItem class but I would rather prefer to keep my domain model as it is.
What is the best practice to perform such update?
it's all about understanding of the modeling. You can create a model response to update also, but just have to update manually by calling it.
you have existingOrder from the query include, then include like this I guess
var existingOrder = ...
existingOrder.OrderItems.Update(yourentity_OrderItem);
await _dbContext.SaveChangesAsync();
So this mean you have to retrieve each Product.id by a for, and update each new items, then savechanges.

EF core - parent.InverseParent returns null for some rows

I have a Category table and it has a Parent Category, I try to iterate over all the categories and get the parents categories with it's Inverse Parent but some of them returns without the inverse parents from unknown reason.
Categories.cs
public partial class Categories
{
public Categories()
{
InverseParent = new HashSet<Categories>();
}
public int Id { get; set; }
public int? ParentId { get; set; }
public DateTime CreateDate { get; set; }
public bool? Status { get; set; }
public virtual Categories Parent { get; set; }
public virtual ICollection<Categories> InverseParent { get; set; }
}
This is how I try to iterate them to create a select list items:
var parentCategories = await _context.Categories.
Include(x => x.Parent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
So the problem is that some of my parent categories returns all their children categories and some don't and I don't why.
There are several issues with that code, all having some explaination in the Loading Related Data section of the documentation.
First, you didn't ask EF Core to include InverseParent, so it's more logically to expect it to be always null.
What you get is a result of the following Eager Loading behavior:
Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
Second, since the query is changing it's initial shape (Select, Disctinct), it's falling into Ignored Includes category.
With that being said, you should build the query other way around - starting directly with parent categories and including InverseParent:
var parentCategories = await _context.Categories
.Include(x => x.InverseParent)
.Where(x => x.InverseParent.Any(c => c.Status == true)) // to match your query filter
.ToListAsync();
While you are including Include(x => x.Parent), you don't seem to do the same for InverseParent. This might affect your results exactly the way you describe. Would including it fix it?
parentCategories = await _context.Categories.
Include(x => x.Parent).
Include(x => x.InverseParent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
UPD: Since you are selecting x => x.Parent anyway it might be necessary to use ThenInclude() method instead.

EF code first Seed many to many relationship not working when fluent API is used

I have an issue with seeding the many to many tables, when I add the Fluent API.
First I have created the two entities, and the many to many relationship between them like this:
[Table("meta.DataCategory")]
public partial class DataCategory : ResolvableEntityBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Column("DataCategoryId")]
public int Id { get; set; }
[Required]
[StringLength(50)]
[Column("DataCategory")]
[DisplayName("DataCategory")]
public string Name { get; set; }
public virtual ICollection<DestinationTable> DestinationTables { get; set; }
}
[Table("meta.DestinationTable")]
public partial class DestinationTable
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Column("DestinationTableId")]
public int Id { get; set; }
[Required]
[StringLength(200)]
[Column("TableName")]
public string Name { get; set; }
[Required]
[StringLength(200)]
[Column("SchemaName")]
public string Schema { get; set; }
public virtual ICollection<DataCategory> DataCategories { get; set; }
}
Adding the migration, and updating the database create the 3 tables, including the many to many table between the DataCategory and DestinationTable. The seed code showen here also worked as it should, and I was able to populate test data to all 3 tables:
context.DataCategories.AddOrUpdate(s => s.Id,
new DAL.DataCategory() { Id = 0, Name = "Unknown", RegexPattern = #"" },
new DAL.DataCategory() { Id = 1, Name = "ProductsMaster", RegexPattern = #"(?:\b|[_]{1})(product|products|produkter|articles)(?:\b|[_]{1})" },
new DAL.DataCategory() { Id = 2, Name = "CustomersTraffic", RegexPattern = #"(?:\b|[_]{1})(trafik|antal)(?:\b|[_]{1})" },
new DAL.DataCategory() { Id = 3, Name = "ProductSales", RegexPattern = #"(?:\b|[_]{1})(salg|sales|ugedata|uge data|uge_data)(?:\b|[_]{1})" },
new DAL.DataCategory() { Id = 4, Name = "CategorySales", RegexPattern = #"(?:\b|[_]{1})(kategory|kategori|category|kat)(?:\b|[_]{1})" },
new DAL.DataCategory() { Id = 5, Name = "StoresMaster", RegexPattern = #"(?:\b|[_]{1})(site\bmaster|store|stores|butik)(?:\b|[_]{1})" },
new DAL.DataCategory() { Id = 6, Name = "MultipleCategories", RegexPattern = #"" },
new DAL.DataCategory() { Id = 7, Name = "ConsultantsMaster", RegexPattern = #"" }
);
DAL.DestinationTable dt = new DAL.DestinationTable();
dt.Id = 1;
dt.Name = "Product";
dt.Schema = "DW";
dt.Type = "Reference";
dt.DataCategories = new List<DAL.DataCategory>();
dt.DataCategories.Add (context.DataCategories.Where(x => x.Name == "ProductsMaster").First());
context.DestinationTables.AddOrUpdate(x => x.Name, dt);
dt = new DAL.DestinationTable();
dt.Id = 2;
dt.Name = "Store";
dt.Schema = "DW";
dt.Type = "Reference";
dt.DataCategories = new List<DAL.DataCategory>();
dt.DataCategories.Add (context.DataCategories.Where(x => x.Name == "StoresMaster").First());
context.DestinationTables.AddOrUpdate(x => x.Name, dt);
dt = new DAL.DestinationTable();
dt.Id = 3;
dt.Name = "ProductSales";
dt.Schema = "DW";
dt.Type = "Transactions";
dt.DataCategories = new List<DAL.DataCategory>();
dt.DataCategories.Add (context.DataCategories.Where(x => x.Name == "ProductSales").First());
dt.DataCategories.Add (context.DataCategories.Where(x => x.Name == "ProductsMaster").First());
dt.DataCategories.Add (context.DataCategories.Where(x => x.Name == "StoresMaster").First());
context.DestinationTables.AddOrUpdate(x => x.Name, dt);
So far so good, however, the many to many table was created under the dbo schema, and the column names used the _Id suffix, which I do not use in the rest of the model, so I had to change that, so I used the following Fluent API in the OnModelCreating() method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<DAL.DestinationTable>()
.HasMany<DAL.DataCategory>(dest => dest.DataCategories)
.WithMany(dcat => dcat.DestinationTables)
.Map(m2m =>
{
m2m.MapLeftKey("DestinationTableId");
m2m.MapRightKey("DataCategoryId");
m2m.ToTable("DestinationTableToDataCategory", "meta");
});
}
This made the expected result in terms of naming the table, and naming the columns the way I want it to be, but now the seeding does not work as before.
The seed code populate the DataCatetory records, and the DestinationTable records, but the many to many table is empty. I tried to reveres the direction, so that the DataCategory was on the left side, but the result was the same, that is the many to many table was not populated.
Note that the seed method run completely without errors.
Why does this happens?
What is the way to seed many to many relationship, when using the Fluent API to bypass EF defaults?
Is there something wrong/missing in the Fluent API part?
OK,
I rolled back the entire database to the initial state, and then I applied all the migrations again, the result was that now the seed is working, and the data is populated in all 3 tables.
Cannot say what was wrong.

How to save ID as string in DB with Code-First EntityFramework

I have a model:
public class Something
{
public int Id { set; get; }
public string Name{ set; get; }
}
Also I have this class:
public class SomethingConfiguration : EntityTypeConfiguration<Something>
{
public SomethingConfiguration()
{
HasKey(t => t.Id).Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
Everything works fine and the Id generates automatically after inserting to DB and save changes (commit).
Now I want to add another column IdString (Id as string instead of int), so I can use it for searching-by-Id manipulations (autocompletes and more). How can I add IdString column that will get the automatic Id as string and will be save automatically while inserting and saving? Is it possible?
In Sql Server you can define a computed column with an underlying formula.
ALTER TABLE dbo.Something Add IsString AS cast(Id as nvarchar)
These columns can be mapped in you model like this.
public partial class Something
{
public int Id { get; set; }
public string IdString { get; set; }
public string Name { get; set; }
}
this.Property(p => p.IdString)
.HasMaxLength(30)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
And then used in your query.
var data1 = db.Something.Where(p => p.IdString.Contains("123"));
Because of the DatabaseGeneratedOption.Computed definition, EntityFramework will request the current Value of the column every time you update the row.
But if you only want a translatable version of ToString() for an integer value you could just use SqlFunctions.StringConvert instead.
var data = db.Something
.Where(p => SqlFunctions.StringConvert((decimal)p.Id).Contains("12"));
Update:
Add computed column with a Migration.
public override void Up()
{
CreateTable(
"dbo.Something",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(nullable: false, maxLength: 255),
})
.PrimaryKey(t => t.Id);
Sql("ALTER TABLE dbo.Something Add IsString AS cast(Id as nvarchar)");
}
public override void Down()
{
DropTable("dbo.Something");
}

How to maintain an ordered list in Entity Framework?

Changing order of elements in a simple list, doesn't stick in Entity Framework. The reason is pretty simple as the ordering information is never stored in the database.
Has anyone come across a generic implementation of ordered list which would work along with Entity Framework?
The requirement is that the user is allowed to reorder list of selected items, and the ordering of items need to be preserved.
Overview
Although there doesn't seem to be any 'magic' to implement this, there is a pattern that we have used to solve this problem, especially when dealing with hierarchies of objects. It boils down to three key things:
Build an Entity model separate from your Domain model. This has the benefit of providing a good separation of concerns, effectively allowing your domain model to be designed and changed without getting bogged down by persistence details.
Use AutoMapper to overcome the hassle of mapping between the Entity and Domain models.
Implement custom value resolvers to map the list in both directions.
The Magic
Because models often include hierarchical and cyclical references between objects, the following Map<>() method can be used to avoid StackOverflow errors during the custom mapping
private class ResolveToDomain : IValueResolver
{
ResolutionResult Resolve(ResolutionResult rr)
{
//...
((MappingEngine) rr.Context.Engine).Map<Product, ProductEntity>(rr.Context, subProduct)
//...
}
}
The Code
Domain Model. Note that the Subproducts list order is important.
class Product
{
public Product ParentProduct { get; set; }
public string Name { get; set; }
public IList<Product> Subproducts { get; set; }
}
Entity Model
class ProductEntity
{
public int Id { get; set; }
public ProductEntity ParentProduct { get; set; }
public string Name { get; set; }
public IList<ProductSubproductEntity> Subproducts { get; set; }
}
class ProductSubproductEntity
{
public int ProductId { get; set; }
public ProductEntity Product { get; set; }
public int Order { get; set; }
public ProductEntity Subproduct { get; set; }
}
Entity Framework Context
class Context : DbContext
{
public DbSet<ProductEntity> Products { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductEntity>()
.HasOptional(e => e.ParentProduct);
modelBuilder.Entity<ProductSubproductEntity>()
.HasKey(e => new {e.ProductId, e.Order})
.HasRequired(e => e.Product)
.WithMany(e => e.Subproducts)
.HasForeignKey(e => e.ProductId);
base.OnModelCreating(modelBuilder);
}
}
AutoMapper configuration
class Mappings : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Product, ProductEntity>()
.ForMember(m => m.Subproducts, a => a.ResolveUsing<ProductSubproductResolver>());
Mapper.CreateMap<ProductEntity, Product>()
.ForMember(m => m.Subproducts, a => a.ResolveUsing<ProductSubproductEntityResolver>());
base.Configure();
}
}
class ProductSubproductResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult rr)
{
var result = new List<ProductSubproductEntity>();
var subproductsSource = ((Product) rr.Context.SourceValue).Subproducts;
if (subproductsSource == null) return rr.New(null);
for (int i = 0; i < subproductsSource.Count; i++)
{
var subProduct = subproductsSource[i];
result.Add(new ProductSubproductEntity()
{
Product = (ProductEntity)rr.Context.DestinationValue,
Order = i,
Subproduct = ((MappingEngine) rr.Context.Engine).Map<Product, ProductEntity>(rr.Context, subProduct)
});
}
return rr.New(result);
}
}
class ProductSubproductEntityResolver: IValueResolver
{
public ResolutionResult Resolve(ResolutionResult rr)
{
var subproductEntitiesSource = ((ProductEntity) rr.Context.SourceValue).Subproducts;
if (subproductEntitiesSource == null) return rr.New(null);
var result = subproductEntitiesSource.OrderBy(p => p.Order).Select(p =>
((MappingEngine) rr.Context.Engine).Map<ProductEntity, Product>(rr.Context, p.Subproduct))
.ToList();
return rr.New(result);
}
}
Usage
private static IList<Product> CreateDomainProducts()
{
var result = new List<Product>();
var mainProduct1 = new Product()
{
Name = "T-Shirt"
};
var subProduct1 = new Product()
{
ParentProduct = mainProduct1,
Name = "T-Shirt (Medium)",
};
var subProduct2 = new Product()
{
ParentProduct = mainProduct1,
Name = "T-Shirt (Large)",
};
mainProduct1.Subproducts = new []
{
subProduct1,
subProduct2
};
var mainProduct2 = new Product()
{
Name = "Shorts"
};
result.Add(mainProduct1);
result.Add(mainProduct2);
return result;
}
static void Main(string[] args)
{
Mapper.Initialize(a => a.AddProfile<Mappings>());
Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
var products = CreateDomainProducts();
var productEntities = Mapper.Map<IList<ProductEntity>>(products);
using (var ctx = new Context())
{
ctx.Products.AddRange(productEntities);
ctx.SaveChanges();
}
// Simulating a disconnected scenario...
using (var ctx = new Context())
{
var productEntity = ctx.Products
.Include(p => p.Subproducts)
.Include(p => p.Subproducts.Select(p2 => p2.Subproduct))
.OrderBy(p=>p.Name)
.ToList();
var productsResult = Mapper.Map<IList<Product>>(productEntity);
// Should be 'T-Shirt (Medium)'
Console.WriteLine(productsResult[1].Subproducts[0].Name);
// Should be 'T-Shirt (Large)'
Console.WriteLine(productsResult[1].Subproducts[1].Name);
}
}
Voila. Hope that helps!
No magic here. If you want to persist a specific order of items in a list (other than a reproducible order by e.g. name) you must store a sequence number in the database.
There wont be an implementation of this for reordering on the database. The data in the database is physically ordered by default by the clustered index which is in essence ordering by the primary key.
Why do you want to do this? EF encourages all ordering to be done via LINQ queries.
If you are looking to optimize lookups you can create additional non-clustered indexes on the database by modifying the code generated for Migrations :
CreateTable(
"dbo.People",
c => new
{
ID = c.Int(nullable: false, identity: true),
Name = c.String()
})
.PrimaryKey(t => t.ID)
.Index(t => t.Name); // Create an index
Note that this will not impact the physical ordering in the database but will speed lookups, although this need to be balanced by slower writes/updates.
to find a solution for this challenge I faced to an article by the following link:
User-defined Order in SQL
this article analyzed different approaches for generating order index value during changing the order of the list. I found the algorithm mentioned in this article so performant by minimum limitation. this algorithm called True Fractions and it generates order index like the following figure:
I have prepared a code sample that I implement this approach by EF Core and InMemory database.
.NET Fiddle Code Sample