Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/wordpress/12.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在实体框架中更新大型对象_C#_Entity Framework 6 - Fatal编程技术网

C# 如何在实体框架中更新大型对象

C# 如何在实体框架中更新大型对象,c#,entity-framework-6,C#,Entity Framework 6,在我的项目中,我的实体模型如下所示: public class OrdersDbContext : DbContext { public DbSet<Customer> Customers { get; set; } public DbSet<Order> Orders { get; set; } public DbSet<OrderPosition> OrderPositions { get; set; } } public cla

在我的项目中,我的实体模型如下所示:

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; }
}
公共类OrdersDbContext:DbContext
{
公共数据库集客户{get;set;}
公共数据库集命令{get;set;}
公共DbSet OrderPositions{get;set;}
}
公共类客户
{
公共Guid Id{get;set;}
公共虚拟订单LiveOrder{get;set;}
}
公共阶级秩序
{
公共Guid Id{get;set;}
公共虚拟IList位置{get;set;}
公共字符串名称{get;set;}
}
公共类OrderPosition
{
公共Guid Id{get;set;}
公共整数价格{get;set;}
}
我需要提供一个方法来更新一些客户的属性以及他的LiveOrder和所有OrderPositions(插入新的、更新现有的和删除旧的)。我尝试了几种方法,但都失败了:

  • 删除订单和订单位置,然后插入新订单失败
  • 重复密钥异常。删除订单,然后附上更新的- 失败,原因是:
  • 附加“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();
        }
    }
    
    使用系统;
    使用System.Collections.Generic;
    使用System.Data.Entity;
    使用System.Linq;
    公共类OrdersDbContext:DbContext
    {
    公共数据库集客户{get;set;}
    公共数据库集命令{get;set;}
    公共DbSet OrderPositions{get;set;}
    }
    公共类客户
    {
    公共Guid Id{get;set;}
    公共虚拟订单LiveOrder{get;set;}
    }
    公共阶级秩序
    {
    公共Guid Id{get;set;}
    公共虚拟IList位置{get;set;}
    公共字符串名称{get;set;}
    }
    公共类OrderPosition
    {
    公共Guid Id{get;set;}
    公共整数价格{get;set;}
    }
    班级计划
    {
    静态void Main(字符串[]参数)
    {
    var parentId=Guid.NewGuid();
    var childId=Guid.NewGuid();
    使用(var ctx=new OrdersDbContext())
    {
    var agg=新客户{
    Id=Guid.NewGuid(),
    LiveOrder=新订单
    {
    Id=父Id,
    Name=“第一订单。”,
    Positions=new[]{neworderposition{Id=childId,Price=3}
    }};
    ctx.Customers.Add(agg);
    ctx.SaveChanges();
    }
    var newParent=新订单
    {
    Id=父Id,
    Name=“已更新订单。”,
    Positions=new[]{neworderposition{Id=childId,Price=5}
    };
    尝试
    {
    使用(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();
    }
    }
    捕获(例外情况除外)
    {
    控制台写入线(ex);
    }
    控制台。WriteLine(“按任意键退出”);
    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


    这意味着,如果事务实际中止,您可以通过重新尝试或在某处记录context ObjectStateManager的状态来进行补偿。

    要扩展我上面的评论

    数据库和EF将无法处理该问题。您正在从数据库中分离实体存储,并在实体存储中创建一个新对象,该对象不会链接到数据库中的对象

    这里的问题是,该行仍然存在于数据库中,在客户端,您要删除它,然后告诉实体存储忘记它,然后向实体存储添加一个新的行,然后提交它。这将使EF执行插入而不是更新,使用相同的Guid会导致重复密钥错误

    你在这里应该做的是

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

    据我所知,Enities不会批量更新/插入,因此如果您更改订单位置y