如何在实体框架中更新大型对象
本文关键字:更新 大型 对象 框架 实体 | 更新日期: 2023-09-27 18:29:49
在我的项目中,我有一个实体模型,看起来像:
public class OrdersDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderPosition> OrderPositions { get; set; }
}
public class Customer
{
public Guid Id { get; set; }
public virtual Order LiveOrder { get; set; }
}
public class Order
{
public Guid Id { get; set; }
public virtual IList<OrderPosition> Positions { get; set; }
public string Name { get; set; }
}
public class OrderPosition
{
public Guid Id { get; set; }
public int Price { get; set; }
}
我需要提供一种方法来更新一些客户的属性以及他的LiveOrder和所有OrderPosition(插入新的、更新现有的和删除旧的)。我尝试了几种方法,但都失败了:
- 删除订单和订单位置,然后插入新的失败
- 重复键异常。Detatch订单和附件更新-失败,原因为:
附加"OrderPosition"类型的实体失败,因为另一个实体相同类型的实体已经具有相同的主键值。
做这件事的正确方法是什么?
演示问题的完整控制台程序:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
public class OrdersDbContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderPosition> OrderPositions { get; set; }
}
public class Customer
{
public Guid Id { get; set; }
public virtual Order LiveOrder { get; set; }
}
public class Order
{
public Guid Id { get; set; }
public virtual IList<OrderPosition> Positions { get; set; }
public string Name { get; set; }
}
public class OrderPosition
{
public Guid Id { get; set; }
public int Price { get; set; }
}
class Program
{
static void Main(string[] args)
{
var parentId = Guid.NewGuid();
var childId = Guid.NewGuid();
using (var ctx = new OrdersDbContext())
{
var agg = new Customer{
Id = Guid.NewGuid(),
LiveOrder = new Order
{
Id = parentId,
Name = "First order.",
Positions = new[] { new OrderPosition { Id = childId, Price = 3 } }
}};
ctx.Customers.Add(agg);
ctx.SaveChanges();
}
var newParent = new Order
{
Id = parentId,
Name = "Updated order.",
Positions = new[] { new OrderPosition { Id = childId, Price = 5 } }
};
try
{
using (var ctx = new OrdersDbContext())
{
var agg = ctx.Customers.First(x => x.LiveOrder.Id == parentId);
ctx.OrderPositions.RemoveRange(agg.LiveOrder.Positions);
var parent = agg.LiveOrder;
agg.LiveOrder = null;
ctx.Entry(parent).State = EntityState.Detached;
Console.WriteLine(ctx.Entry(parent).State);
ctx.Entry(newParent).State = EntityState.Modified;
agg.LiveOrder = newParent;
ctx.SaveChanges();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
}
}
这完全不是EF问题——使用纯SQL也会遇到同样的问题。
SQL Server在每次更新时都会检查唯一性,而不是在提交时。由于重新排序会临时创建非唯一。。。。好
你需要两次传球。先把东西移开,然后更新到最终位置。
如何?SQL Server数据类型支持负数;)把它们放在负数的位置(即-4而不是4),然后你就可以为客户制作一个反转负数的SP(或直接SQL)。完成。
但在更新过程中,您需要打破唯一性。
唉,当涉及到这种类型的操作时,实体框架非常愚蠢,它为您提供了框架,而不是一个完整的解决方案。
正如你所做的;这可以通过多次调用SaveChanges来实现。
它不能在一次调用SaveChanges中实现的原因是,没有办法告诉实体框架你想要的操作顺序,即它可能会在更改外键等之前删除一个表记录。
现在,在事务中执行多个保存更改的更好方法如下:
TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions() { IsolationLevel = IsolationLevel.Serializable });
try
{
using (scope)
{
using (OrdersDbContext ctx = new OrdersDbContext())
{
ctx.Connection.Open();
//Do something with DBcontext - Update existing
//Save Changes
ctx.SaveChanges(false);
//Do something else with DBcontext - Delete old
//Save Changes
ctx.SaveChanges(false);
//Do something else with DBcontext - Insert new
//Save Changes
ctx.SaveChanges(false);
//if we get here things are looking good.
scope.Complete();
ctx.AcceptAllChanges();
}
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
ErrorLogger.LogException();
}
catch (ApplicationException ex)
{
ErrorLogger.LogException();
}
这里的主要好处是,如果一个SaveChanges失败,那么其他SaveChanges将被回滚/不执行操作。
当对SaveChanges(false)的调用将必要的命令发送到数据库时,上下文本身不会更改,因此如果需要,您可以再次执行此操作,或者如果需要,可以询问ObjectStateManager。
这意味着,如果事务实际上中止了,您可以通过重新尝试或在某个地方记录上下文ObjectStateManager的状态来进行补偿。
要扩展我上面的评论。。。
数据库和EF将无法处理此问题。您正在从数据库中分离实体存储,并在实体存储中创建一个新对象,该对象不会链接到数据库中的对象。
这里的问题是,该行仍然存在于数据库中,在客户端,您正在删除它,然后告诉实体存储忘记它,然后向实体存储添加一个新的行,然后提交它。这将使EF执行插入而不是更新,使用相同的Guid会导致Duplicate Key错误。
你在这里应该做的是。。。
try
{
using (var ctx = new OrdersDbContext())
{
var agg = ctx.Customers.First(x => x.LiveOrder.Id == parentId);
var pos = agg.LiveOrder.Single(o => o.Id == childId);
pos.Price = 5;
ctx.SaveChanges();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
实体在客户端存储对象,并将检测对它们的更改。您所要做的就是像普通对象一样修改它们并调用SaveChanges()
。