I have been dealing with some EF core issues that I cannot identify the source and although I can get around them it's resulting in inefficient code in my opinion. Let me explain, I am calling the below procedure within a cycle after a POST form submission:
if (form.Inventory != null && form.Inventory.Any())
{
foreach (var pi in form.Inventory)
{
var b = await _branchesActions.GetBranchesByIdNoTrackingAsync(pi.Branchesid).ConfigureAwait(false);
await _productActions.ModifyProductInventory(product, user, b, pi.ProductQty, "Producto agregado a sistema",
product.Productsid, "Create", "Alta de producto").ConfigureAwait(false);
}
}
this is the below code for the functions it is calling:
public async Task<Branches> GetBranchesByIdNoTrackingAsync(int id)
{
return await db.Branches.Where(b => b.IsActive && b.Branchesid == id)
.AsNoTracking()
.SingleOrDefaultAsync()
.ConfigureAwait(false);
}
ModifyProductInventory:
public async Task<bool> ModifyProductInventory(Products product, Users user, Branches branch, int qty, string action, int relid, string actionname, string actiontype)
{
try
{
var inventory = await db.BranchesInventory
.Where(bi => bi.Branch.Branchesid == branch.Branchesid
&& bi.Product.Productsid == product.Productsid)
.SingleOrDefaultAsync()
.ConfigureAwait(false);
if (inventory == null)
{
await InsertBranchInventory(qty, branch, product).ConfigureAwait(false);
}
else
{
if (!product.IsService)
{
db.BranchesInventory.Update(inventory);
inventory.ProductQty += qty;
await db.SaveChangesAsync().ConfigureAwait(false);
db.Entry(inventory).State = EntityState.Detached;
db.Entry(branch).State = EntityState.Detached;
}
}
await AddAuditLogEntry(action, qty, relid, actionname, actiontype, user, branch, product).ConfigureAwait(false);
return true;
}
catch (Exception e)
{
await _sewebUtilities.LogError(e, "Error al modificar inventario para producto: " + product.ProductCode).ConfigureAwait(false);
throw;
}
}
This second one is basically where I started having issues with EF core, the same code was on Framework with no issues so I ended up separating the problematic sections into its own functions for InsertBranchInventory:
public async Task InsertBranchInventory(int qty, Branches branch, Products product)
{
try
{
var inventory = new BranchesInventory
{
ProductQty = qty
};
await db.BranchesInventory.AddAsync(inventory).ConfigureAwait(false);
await db.SaveChangesAsync().ConfigureAwait(false);
//This works the first time the cycle runs but the second time but the second time the cycle runs it needs this code otherwise the id is 0 and I get error 1
if (inventory.BranchesInventoryid <= 0)
{
inventory = await db.BranchesInventory
.OrderByDescending(i => i.BranchesInventoryid)
.Take(1)
.SingleOrDefaultAsync()
.ConfigureAwait(false);
}
db.BranchesInventory.Update(inventory);
//if I do the below within the inventory declaration I get the error 2
inventory.Branch = branch;
inventory.Product = product;
await db.SaveChangesAsync().ConfigureAwait(false);
//I tried detaching the entities after the insertion was complete but it does not seem to help
db.Entry(inventory).State = EntityState.Detached;
db.Entry(branch).State = EntityState.Detached;
}
catch(Exception e)
{
await _sewebUtilities.LogError(e, "Error al agregar entrada de inventario para producto: " + product.ProductCode).ConfigureAwait(false);
throw;
}
}
}
and for AddAuditLogEntry:
public async Task AddAuditLogEntry(string actionPerformed, int qty, int relid, string actionname, string actiontype, Users user, Branches branch, Products product)
{
try
{
var actionTime = _sewebUtilities.GetCurrentTime();
var audit = new AuditLog
{
ActionPerformed = actionPerformed,
ItemType = "Products",
Quantity = qty,
EventDateTime = actionTime,
Relatedid = relid,
ActionName = actionname,
ActionType = actiontype
};
await db.AuditLog.AddAsync(audit);
await db.SaveChangesAsync().ConfigureAwait(false);
//I'll have to replace the below as in the InsertBranchInventory function as I started getting error 2 on this after the second cycle run
await db.Entry(audit).GetDatabaseValuesAsync().ConfigureAwait(false);
db.AuditLog.Update(audit);
//if I do the below within the inventory declaration I get the error 2, also for branches
audit.Users = user;
audit.Branches = branch;
audit.Products = product;
await db.SaveChangesAsync().ConfigureAwait(false);
db.Entry(audit).State = EntityState.Detached;
db.Entry(branch).State = EntityState.Detached;
}
catch (Exception e)
{
await _sewebUtilities.LogError(e, "Error al agregar entrada al log de productos");
}
}
for the errors I get they are as follow, Error 1:
System.InvalidOperationException: The property 'BranchesInventoryid' on entity type 'BranchesInventory' has a temporary value while attempting to change the entity's state to 'Modified'. Either set a permanent value explicitly or ensure that the database is configured to generate values for this property.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey)
at Microsoft.EntityFrameworkCore.DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState)
at Microsoft.EntityFrameworkCore.DbContext.Update[TEntity](TEntity entity)
at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Update(TEntity entity)
at SeWebERP.Infrastructure.ProductActions.InsertBranchInventory(Int32 qty, Branches branch, Products product) in Infrastructure\ProductActions.cs:line 386
at SeWebERP.Infrastructure.ProductActions.ModifyProductInventory(Products product, Users user, Branches branch, Int32 qty, String action, Int32 relid, String actionname, String actiontype) in Infrastructure\ProductActions.cs:line 326 Error al modificar inventario para producto: test001
Error 2:
Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert explicit value for identity column in table 'Branches' when IDENTITY_INSERT is set to OFF.
at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__164_0(Task`1 result)
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
ClientConnectionId:ebeb98ae-7c1a-498d-846f-63373ede3b86
Error Number:544,State:1,Class:16
and no matter all of this patching even after I use the the if to get the last inventory record added it fails on the second cycle run with error:
System.InvalidOperationException: The instance of entity type 'Branches' cannot be tracked because another instance with the key value '{Branchesid: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, Object oldValue, Object newValue)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.NavigationReferenceChanged(InternalEntityEntry entry, INavigation navigation, Object oldValue, Object newValue)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectNavigationChange(InternalEntityEntry entry, INavigation navigation)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.LocalDetectChanges(InternalEntityEntry entry)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.ChangeDetector.DetectChanges(IStateManager stateManager)
at Microsoft.EntityFrameworkCore.ChangeTracking.ChangeTracker.DetectChanges()
at Microsoft.EntityFrameworkCore.DbContext.TryDetectChanges()
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at SeWebERP.Infrastructure.ProductActions.AddAuditLogEntry(String actionPerformed, Int32 qty, Int32 relid, String actionname, String actiontype, Users user, Branches branch, Products product) in Infrastructure\ProductActions.cs:line 372 Error al agregar entrada al log de productos
and the inventory table end ups as such:
my models are not complicated, for the id I just use the key tag as below:
public class BranchesInventory
{
[Key]
public int BranchesInventoryid { get; set; }
public Branches Branch { get; set; }
public Products Product { get; set; }
public int ProductQty { get; set; }
[MaxLength(200)]
public string ProductLocation { get; set; }
}
but the documentation for ef core does not suggest to use anything else, I hope everything that is included here is enough information and someone can provide some guidance, or let me know if you would like me to include more information.
This is not a solution but in order to advance I ended up changing the process to use a SQL query:
public async Task InsertBranchInventory(int qty, Branches branch, Products product)
{
try
{
var p0 = branch.Branchesid;
var p1 = qty;
var p2 = product.Productsid;
await db.Database.ExecuteSqlInterpolatedAsync($"INSERT INTO[BranchesInventory]([Branchesid], [ProductQty], [Productsid]) VALUES({p0}, {p1}, {p2});");
}
catch(Exception e)
{
await _sewebUtilities.LogError(e, "Error al agregar entrada de inventario para producto: " + product.ProductCode).ConfigureAwait(false);
throw;
}
}
this is honestly really dumb and again don't consider this a solution but a workaround I guess and of the worst type becaus if I wanted to use SQL queries on my code I would use PHP.
User contributions licensed under CC BY-SA 3.0