EF core errors to add entities when running within a cycle

2

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:

inventory table

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.

c#
entity-framework
asp.net-core
entity-framework-core
.net-core-3.1
asked on Stack Overflow Jul 2, 2020 by HectorDZJ

1 Answer

0

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.

answered on Stack Overflow Jul 2, 2020 by HectorDZJ

User contributions licensed under CC BY-SA 3.0