簡體   English   中英

無法跟蹤實體類型的實例,因為已跟蹤具有相同鍵值的另一個實例

[英]The instance of entity type cannot be tracked because another instance with the same key value for is already being tracked

無法跟蹤實體類型“AssegnazioneLotto”的實例,因為已跟蹤具有與 {'Id_AssegnazioneLotto'} 相同鍵值的另一個實例。

附加現有實體時,確保只附加一個具有給定鍵值的實體實例。

考慮使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”來查看沖突的鍵值。

當我們從表中調用數據並更新它時,我遇到了這個錯誤。

我通過調用一個調用表的視圖來解決它。 為什么會這樣? 如何在不創建額外視圖的情況下解決問題?

最簡單的答案:不要在他們被讀取的 scope 之外傳遞實體。 傳遞視圖模型(POCO 對象而不是實體)並在更新時獲取實體以復制預期值。

復雜的答案是,當更新實體引用時,包括子 collections 和多對一引用在內的所有實體引用,您需要檢查 DbContext 是否正在跟蹤匹配的引用,或者用跟蹤的實體替換引用,或者告訴DbContext 在附加之前轉儲跟蹤的引用。

例如,接受分離或反序列化“實體”的更新方法。 什么有時有效,但其他時候卻很糟糕:

public void UpdateOrder(Order order)
{
    context.Update(order);

    // OR 
    context.Attach(order);
    context.Entry(order).State = EntityState.Modified;

    context.SaveChanges();
}

看起來簡單而干凈,但當 DbContext 實例可能已經在跟蹤匹配的 Order 實例時,它就會出錯。 當它是時,你會得到那個例外

安全檢查:

public void UpdateOrder(Order order)
{
    var existingOrder = context.Orders.Local.SingleOrDefault(o => o.OrderId == order.OrderId);
    if (existingOrder != null)
        context.Entry(existingOrder).State = EntityState.Detatched;

    context.Update(order);

    // OR 
    context.Attach(order);
    context.Entry(order).State = EntityState.Modified;

    context.SaveChanges();
}

該示例檢查本地跟蹤緩存以查找匹配的訂單並轉儲任何跟蹤的實例。 這里的關鍵是使用 DbSet 搜索.Local以搜索本地跟蹤緩存,而不是命中數據庫。

更復雜的地方是 Order 包含其他實體引用,例如 OrderLines,或對客戶的引用等。在處理分離的實體時,您需要檢查整個 object 圖表以獲取跟蹤的引用。

public void UpdateOrder(Order order)
{
    var existingOrder = context.Orders.Local.SingleOrDefault(o => o.OrderId == order.OrderId);
    if (existingOrder != null)
        context.Entry(existingOrder).State = EntityState.Detatched;

    var customer = context.Customers.Local.SingleOrDefault(c => c.CustomerId = order.Customer.CustomerId);
    if (customer != null)
        order.Customer = customer; // Replace our Customer reference with the tracked one.
    else
        context.Attach(order.Customer);

    context.Update(order);

    // OR 
    context.Attach(order);
    context.Entry(order).State = EntityState.Modified;

    context.SaveChanges();
}

正如您所看到的,這開始變得非常復雜和繁瑣,因為您需要檢查每個參考。 因此,避免傳遞分離或序列化的實體更簡單。 使用視圖 Model 為性能和簡化此類問題提供了許多好處。 再加上 Automapper 或類似的支持投影的映射器,可以使視圖模型的操作變得非常簡單:

選擇訂單:

var orders = context.Orders.Where(/* suitable conditions */)
    .ProjectTo<OrderViewModel>(_mapperConfig)
    .ToList();

其中 _mapperConfig 是一個 Automapper 配置,它告訴 Automapper 如何將 Order 轉換為 OrderViewModel。 這可以遵循約定或可選地包含映射規則來為訂單及其相關詳細信息構建平面視圖 model。 ProjectTo與 EF 的IQueryable一起使用,在實體圖中構建 SQL SELECT 語句,以僅返回填充視圖 model 所需的數據。 這比使用需要預先加載所有相關實體的Map更有效。

更新時:

public void UpdateOrder(UpdateOrderViewModel orderVM)
{
    var order = context.Orders.Single(o => o.OrderId == orderVM.OrderId);
    if (orderVM.RowVersion != order.RowVersion)
        throw new StaleDataException(); // placeholder to handle the situation where the data has changed since our view got the order details.

    var mapper = _mapperConfig.CreateMapper();
    mapper.Map(orderVM, order);
    context.SaveChanges();
}

orderVM 可以是返回的 OrderViewModel,但通常我建議只將可以更新的字段打包到專用視圖 model 中。 “魔術”在 Automapper 配置中,它控制從視圖 model 復制回實體的字段。 如果可以包含諸如 OrderLines 之類的子數據,在這種情況下,您需要確保這些子實體是預先加載的 /w 。 .Include在您的數據庫獲取中。 在這種情況下,Automapper 的Map方法是將映射值從源復制到目標的變體,因此值會直接復制到被跟蹤的實體實例中。 EF 將根據實際收費的值而不是覆蓋整個記錄來構建 SQL UPDATE 語句。

您還可以對分離的實體使用相同的技術來避免您的問題。 使用 Automapper 的好處是您可以配置哪些值可以從提供的反序列化/分離實體合法復制到真實數據中:

public void UpdateOrder(Order updatedOrder)
{
    var order = context.Orders.Single(o => o.OrderId == orderVM.OrderId);
    if (updatedOrder.RowVersion != order.RowVersion)
        throw new StaleDataException(); // placeholder to handle the situation where the data has changed since our view got the order details.

    var mapper = _mapperConfig.CreateMapper();
    mapper.Map(updatedOrder, order);
    context.SaveChanges();
}

這確保我們只更改允許更改的內容,並避免跟蹤引用的整個錯誤。 在我們的映射器配置中,我們確實有一個類似的條目:

cfg.CreateMap<Order, Order>(...)

它將包含明確的規則來忽略跨字段和相關實體的復制,我們不想在更新時復制。

這樣做的缺點是通過網絡來回發送整個實體和潛在的相關實體的開銷,加上為了“安全”免受篡改,需要更多的努力將 go 放入映射器配置或顯式復制允許的值.

我在使用 EF Core 和 Blazor 服務器時遇到了同樣的問題。 將服務集合中的 scope 切換為“Transient”並使用 ServiceScopeFactory 進行查詢/更新就可以了。 您將在下面看到我正在使用 Blazor 樣式的依賴注入,但構造函數注入對於 IServiceScopeFactory 的工作方式仍然相同

            [Inject]
        IServiceScopeFactory _serviceScopeFactory { get; set; }
private async Task UpdateItem(GridCommandEventArgs args)
        {
            var utilityItem = (EntityModelSample)args.Item;
            using (var scope1 = _serviceScopeFactory.CreateScope())
            {
                var dbContext = scope1.ServiceProvider.GetService<SampleDbContext>();
                dbContext.Update(utilityItem);
                await dbContext.SaveChangesAsync();
            }

            LoadData();
        }

在啟動代碼中:

builder.Services.AddDbContext<InternalUtilitiesDbContext>(option => option.UseSqlServer(connectionString), ServiceLifetime.Transient);

此代碼解決了您的問題:: builder.Services.AddDbContext(option => option.UseSqlServer(connectionString), ServiceLifetime.Transient);

ServiceLifetime.Transient

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM