当我们从表中调用数据并更新它时,会遇到此错误。通过调用一个调用表的视图来解决此问题。为什么会发生这种情况?如何在不创建其他视图的情况下解决此问题?
public void UpdateOrder(Order order)
{
context.Update(order);
// OR
context.Attach(order);
context.Entry(order).State = EntityState.Modified;
context.SaveChanges();
}
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或对Customer的引用等,情况会变得更加复杂。处理分离实体时,您需要检查整个对象图以查找已跟踪的引用。
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();
}
var orders = context.Orders.Where(/* suitable conditions */)
.ProjectTo<OrderViewModel>(_mapperConfig)
.ToList();
其中,_mapperConfig是AutoMapper的一个配置,告诉AutoMapper如何将Order转换为OrderViewModel。它可以遵循约定或可选地包含映射规则,以构建展平的视图模型,包括Order及其相关细节。ProjectTo
与EF的IQueryable
一起工作,跨实体图构建一个SQL SELECT语句,仅返回填充视图模型所需的数据。这比使用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,但通常我建议将只能更新的字段打包成一个专用视图模型。 "魔法" 在于 AutoMapper 配置,它控制从视图模型复制哪些字段回到实体中。如果可以包括子数据,例如 OrderLines 或这样的数据,那么您需要确保这些子实体在 DB 获取时使用 eager loaded /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>(...)
这将包含明确规则,以忽略在更新时不想被复制的字段和相关实体。
这样做的缺点是,需要在网络上传输整个实体及其可能相关的实体,而且为了避免篡改,需要在映射器配置或明确允许的值之间进行更多的努力。
[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);
ServiceLifetime.Transient