简体   繁体   English

无法跟踪实体类型的实例,因为已跟踪具有相同键值的另一个实例

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

The instance of entity type 'AssegnazioneLotto' cannot be tracked because another instance with the same key value for {'Id_AssegnazioneLotto'} is already being tracked.无法跟踪实体类型“AssegnazioneLotto”的实例,因为已跟踪具有与 {'Id_AssegnazioneLotto'} 相同键值的另一个实例。

When attaching existing entities, ensure that only one entity instance with a given key value is attached.附加现有实体时,确保只附加一个具有给定键值的实体实例。

Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.考虑使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”来查看冲突的键值。

I encounter this error when we call data from a table and update it.当我们从表中调用数据并更新它时,我遇到了这个错误。

I solved it by calling a view which calls the table.我通过调用一个调用表的视图来解决它。 Why does this happen?为什么会这样? How can I solve without creating additional views?如何在不创建额外视图的情况下解决问题?

The simplest answer: Don't pass entities around outside of the scope they were read.最简单的答案:不要在他们被读取的 scope 之外传递实体。 Pass view models (POCO objects rather than entities) and fetch entities on update to copy expected values across.传递视图模型(POCO 对象而不是实体)并在更新时获取实体以复制预期值。

The complex answer is that when updating entity references, all entity references including child collections and many-to-1 references, you need to check if the DbContext is tracking a matching reference, and either replace the references with the tracked entity, or tell the DbContext to dump the tracked reference before attaching.复杂的答案是,当更新实体引用时,包括子 collections 和多对一引用在内的所有实体引用,您需要检查 DbContext 是否正在跟踪匹配的引用,或者用跟踪的实体替换引用,或者告诉DbContext 在附加之前转储跟踪的引用。

For example, an update method that accepts a detatched or deserialized "entity".例如,接受分离或反序列化“实体”的更新方法。 What works sometimes, but then craps other times:什么有时有效,但其他时候却很糟糕:

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

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

    context.SaveChanges();
}

Looks simple and clean, but craps out when the DbContext instance might already be tracking a matching Order instance.看起来简单而干净,但当 DbContext 实例可能已经在跟踪匹配的 Order 实例时,它就会出错。 When it is, you get that exception当它是时,你会得到那个例外

The safety check:安全检查:

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();
}

That example checks the local tracking cache for a matching order and dumps any tracked instance.该示例检查本地跟踪缓存以查找匹配的订单并转储任何跟踪的实例。 The key here is searching the .Local with the DbSet to search the local tracking cache, not hitting the DB.这里的关键是使用 DbSet 搜索.Local以搜索本地跟踪缓存,而不是命中数据库。

Where this gets more complex is where Order contains other entity references like OrderLines, or a reference to a Customer, etc. When dealing with detached entities you need to check over the entire object graph for tracked references.更复杂的地方是 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();
}

As you can see, this starts to get complex and cumbersome pretty quick as you need to check every reference.正如您所看到的,这开始变得非常复杂和繁琐,因为您需要检查每个参考。 Hence, it's simpler to avoid passing detached or serialized entities around.因此,避免传递分离或序列化的实体更简单。 Using a View Model offers many benefits for performance and simplifying issues like this.使用视图 Model 为性能和简化此类问题提供了许多好处。 Coupled with Automapper or a similar mapper that supports projection can make operations with view models very simple:再加上 Automapper 或类似的支持投影的映射器,可以使视图模型的操作变得非常简单:

Selecting Orders:选择订单:

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

Where _mapperConfig is an Automapper configuration that tells Automapper how to convert an Order into an OrderViewModel.其中 _mapperConfig 是一个 Automapper 配置,它告诉 Automapper 如何将 Order 转换为 OrderViewModel。 This can follow conventions or optionally contain mapping rules to build a flattened view model for an Order and it's relative details.这可以遵循约定或可选地包含映射规则来为订单及其相关详细信息构建平面视图 model。 ProjectTo works with EF's IQueryable to build an SQL SELECT statement across the entity graph to return only the data needed to populate the view model. ProjectTo与 EF 的IQueryable一起使用,在实体图中构建 SQL SELECT 语句,以仅返回填充视图 model 所需的数据。 This is far more efficient than using Map which would require all related entities to be eager loaded.这比使用需要预先加载所有相关实体的Map更有效。

When updating:更新时:

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 could be an OrderViewModel returned, but typically I would recommend packaging just the fields that can be updated into a dedicated view model. orderVM 可以是返回的 OrderViewModel,但通常我建议只将可以更新的字段打包到专用视图 model 中。 The "magic" is in the Automapper configuration which governs what fields get copied from the view model back into the entity. “魔术”在 Automapper 配置中,它控制从视图 model 复制回实体的字段。 If can include child data such as OrderLines or such, in which case you would want to ensure those child entities are eager loaded /w .Include in your DB fetch.如果可以包含诸如 OrderLines 之类的子数据,在这种情况下,您需要确保这些子实体是预先加载的 /w 。 .Include在您的数据库获取中。 Automapper's Map method in this case is the variant that copies mapped values from a source to a destination, so values are copied across directly into the tracked entity instance.在这种情况下,Automapper 的Map方法是将映射值从源复制到目标的变体,因此值会直接复制到被跟踪的实体实例中。 EF will build an SQL UPDATE statement based on what values actually charge rather than overwriting the entire record. EF 将根据实际收费的值而不是覆盖整个记录来构建 SQL UPDATE 语句。

You can also use the same technique with detached entities to avoid your issue.您还可以对分离的实体使用相同的技术来避免您的问题。 The benefit of using Automapper is that you can configure which values can be legally copied over from the deserialized/detached entity provided into the real data:使用 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();
}

This ensures we only change what is allowed to change, and avoids the whole crapshoot of tracked references.这确保我们只更改允许更改的内容,并避免跟踪引用的整个错误。 In our mapper configuration we literally have an entry like:在我们的映射器配置中,我们确实有一个类似的条目:

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

which will hold explicit rules to ignore copying across fields and related entities we don't want copied across on an Update.它将包含明确的规则来忽略跨字段和相关实体的复制,我们不想在更新时复制。

The downside of doing this is the overhead of sending entire entities and potentially their related entities across the wire back and forth, plus to be "safe" from tampering, a lot more effort needs to go into the mapper configuration or copying across allowed values explicitly.这样做的缺点是通过网络来回发送整个实体和潜在的相关实体的开销,加上为了“安全”免受篡改,需要更多的努力将 go 放入映射器配置或显式复制允许的值.

I had the same issue with EF Core and Blazor Server.我在使用 EF Core 和 Blazor 服务器时遇到了同样的问题。 Switching the scope in the service collection to "Transient" and using a ServiceScopeFactory for the queries/updates did the trick.将服务集合中的 scope 切换为“Transient”并使用 ServiceScopeFactory 进行查询/更新就可以了。 You'll see below I'm using the Blazor style dependency injection, but constructor injection will still work the same way for an IServiceScopeFactory您将在下面看到我正在使用 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();
        }

In the startup code:在启动代码中:

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

this code fix your problems:: builder.Services.AddDbContext(option => option.UseSqlServer(connectionString), ServiceLifetime.Transient);此代码解决了您的问题:: builder.Services.AddDbContext(option => option.UseSqlServer(connectionString), ServiceLifetime.Transient);

ServiceLifetime.Transient ServiceLifetime.Transient

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 无法跟踪实体类型“Item”的实例,因为已跟踪另一个具有与 {'Id'} 相同键值的实例 - The instance of entity type 'Item' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked 无法跟踪实体类型的实例,因为已在跟踪具有相同键值的另一个实例 - The instance of entity type cannot be tracked because another instance with the same key value is already being tracked 无法跟踪实体类型“书签”的实例,因为已经在跟踪具有相同键值 {'ID'} 的另一个实例 - The instance of entity type 'Bookmark' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked 实体类型的实例 <T> 无法跟踪,因为已经跟踪了另一个具有相同的{&#39;Id&#39;}键值的实例 - The instance of entity type <T> cannot be tracked because another instance with the same key value for {'Id'} is already being tracked 无法跟踪实体类型 Model 的实例,因为已跟踪另一个具有相同 {&#39;Id&#39;} 键值的实例 - The instance of entity type Model cannot be tracked because another instance with the same key value for {'Id'} is already being tracked "使用 AsNoTracking() 获取“无法跟踪实体类型的实例,因为已经在跟踪具有相同键值的另一个实例”" - Getting 'The instance of entity type cannot be tracked because another instance with same key value for is already being tracked' with AsNoTracking() 无法跟踪实体类型“Bus”的实例,因为已经在跟踪具有相同键值 {'Id'} 的另一个实例 - The instance of entity type ‘Bus’ cannot be tracked because another instance with the same key value for {‘Id’} is already being tracked 无法跟踪实体类型“AppUser”的实例,因为已跟踪另一个具有与 {'Id'} 相同键值的实例 - The instance of entity type 'AppUser' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked 无法跟踪实体类型“产品”的实例,因为已在跟踪具有相同键值的另一个实例 - The instance of entity type 'Product' cannot be tracked because another instance with the same key value is already being tracked 无法跟踪实体类型“ Article”的实例,因为已经跟踪了另一个具有相同键值的{&#39;Id&#39;}实例 - The instance of entity type 'Article' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM