【问题标题】:How to update existing entity containing not updated sub-entity?如何更新包含未更新子实体的现有实体?
【发布时间】:2015-04-28 15:28:08
【问题描述】:

我想修改实体支付对象,包含未修改的现有货币对象并将其保存到 EF 数据库:

public Payment()
{
   int Id {get;set;}
   public int Value {get;set;}
   public Currency SelectedCurrency{get;set;}
}

public Currency()
{
    int Id {get;set;}
    string Name;
}

我的 Edit 方法如下所示:

    public override void Edit(MwbePaymentMethod payment)
    {

        if (payment.Currency != null && payment.Currency.Id != 0 && Context.Entry(payment.UserData).State != EntityState.Unchanged)
        {
            Context.Entry(payment.Currency).State = EntityState.Unchanged;
        }

        Context.Entry(payment).State = EntityState.Modified;            
    }

关于 Edit 方法的几句话:它将包含的子实体 Currency 更改为 Unchanged,因为它不会被更新。但是当行

Context.Entry(payment).State = EntityState.Modified;  

被调用,显示错误:

附加“付款”类型的实体失败,因为同一类型的另一个实体已经具有相同的主键值。这个可以 使用“附加”方法或设置状态时发生 如果图中的任何实体具有,则实体为“未更改”或“已修改” 键值冲突。这可能是因为一些实体是新的并且 尚未收到数据库生成的键值。在这种情况下使用 'Add' 方法或 'Added' 实体状态来跟踪图形和 然后将非新实体的状态设置为“未更改”或“已修改”为 合适。

我也尝试使用DbSet.Attach(payment) 方法,但也报错。

添加1: 这是我调用 Edit 方法的外部方法。它调用 Context.Save 并按 Id 读取支付实体。

public void UpdateMwbePaymentMethod(MwbePaymentFilter filter, MwbePaymentDtoInOut mwbepayment)
        {
            var currentPayment = paymentMethodRepository.FindBy(x => x.UserData.Id == filter.userId && x.Id == filter.id);
            if (currentPayment==null || currentPayment.Count() != 1)
            {
                throw new DBConcurrencyException();
            }
            var mwbePayment = Mapper.Map<MwbePayment>(mwbepayment);

            mwbePayment.UserData = userRepository.Get(filter.userId).Data;            

            paymentRepository.Edit(mwbePayment);
            paymentRepository.SaveChanges();            
        }

添加2: 我在两个 Find 查询中都添加了 AsNoTracking。我被报告为补救措施。但是现在标记为已分离的其他实体,当调用方法 DbSet.Attach(payment) 时,它会给出错误

附加类型为“MobileWallet.Common.Repository.MwbeAddress”的实体失败,因为同一类型的另一个实体已经具有相同的主键值。如果图中的任何实体具有冲突的键值,则在使用“附加”方法或将实体的状态设置为“未更改”或“已修改”时,可能会发生这种情况。这可能是因为某些实体是新实体,尚未收到数据库生成的键值。在这种情况下,使用“添加”方法或“已添加”实体状态来跟踪图形,然后将非新实体的状态设置为“未更改”或“已修改”。

添加 3: 添加方法 FindBy:

public IEnumerable<TEntity> FindBy(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includes)
        {
            IQueryable<TEntity> query =  DbSet.AsNoTracking().Where(predicate);

            if (null != includes)
            {
                foreach (var include in includes)
                {
                    query = query.AsNoTracking().Include(include);
                }
            }
            IEnumerable<TEntity> result = query.AsEnumerable();
            return result;
        }

【问题讨论】:

  • 这背后的原因是什么?如果你不主动修改货币,它不会改变?!
  • 你使用oracle DB吗?
  • @Technovation:我正在使用 MS SQL Express 2013。
  • @Mario The Spoon:我想更新支付对象而不更新现有的货币子对象。 :)
  • 只需执行 context.SaveChanges() - EF 将自行检测任何更改。此外,在比较的第二部分中,您使用 payment.UserData - 此属性甚至不会出现在您的代码中

标签: c# entity-framework


【解决方案1】:

EF6 通常非常擅长猜测哪些需要提交到数据库,哪些不需要。 我通常使用这样的代码:

Payment payment = context.Payment.Find( 42 ); //get payment with id 42 from db

payment.Value = 199;

context.SaveChanges(); //only changed data is saved

更新: 一个更详细的例子:

DbContext:

class TestContext : DbContext
{
    public TestContext()
        : base()
    {}

    public DbSet<ParentEntity> Parent { get; set; }
    public DbSet<ChildEntity> Child { get; set; }    
}

父实体:

class ParentEntity
{
    [Key]
    public int Id { get; protected set; }

    public string Property1 { get; set; }
    public string Property2 { get; set; }

    public virtual ChildEntity child { get; set; } // virtual to enable lazy loading
}

子实体:

   class ChildEntity
    {
        [Key]
        public int Id { get; set; }

        public string Value1 { get; set; }    
        public string Value2 { get; set; }
}

测试代码:

   using ( TestContext context = new TestContext( ) )
    {
        context.Database.Log = Console.WriteLine;

        ParentEntity p1 = new ParentEntity( );
        p1.Property1 = "Value of P1";
        p1.Property2 = "Value of P2";

        ChildEntity c1 = new ChildEntity( );
        c1.Value1 = "V1";
        c1.Value2 = "V2";

        p1.child = c1;

        context.Parent.Add( p1 );

        context.SaveChanges( );

        myEntity = p1.Id;
    }

    using( TestContext context = new TestContext() )
    {
        context.Database.Log = Console.WriteLine;

        ParentEntity p = context.Parent.Where( x => x.Id == myEntity ).FirstOrDefault( );

        p.Property1 = "Changed";
        p.Property2 = "Value of P2";

        context.SaveChanges( );
    }

执行的sql:

28.04.2015 08:49:40 +02:00 开始交易

插入 [dbo].[ChildEntities]([Value1], [Value2]) 值 (@0, @1) 从 [dbo].[ChildEntities] 中选择 [Id],其中 @@ROWCOUNT > 0 并且 [Id] = scope_identity()

-- @0: 'V1'(类型 = 字符串,大小 = -1) -- @1: 'V2'(类型 = 字符串,大小 = -1) -- 执行于 28.04.2015 08:49:41 +02:00 -- 在 3 毫秒内完成,结果为:SqlDataReader

插入 [dbo].[ParentEntities]([Property1], [Property2], [child_Id]) 值(@0、@1、@2)从 [dbo].[ParentEntities] 中选择 [Id] @@ROWCOUNT > 0 AND [Id] = scope_identity()

-- @0: 'P1 的值'(类型 = 字符串,大小 = -1) -- @1: 'P2 的值'(类型 = 字符串,大小 = -1) -- @2: '2' (类型 = Int32) -- 执行于 28.04.2015 08:49:41 +02:00 -- 在 3 毫秒内完成,结果为:SqlDataReader

在 28.04.2015 08:49:41 +02:00 提交的交易关闭连接 在 28.04.2015 08:49:41 +02:00 在 28.04.2015 08:49:41 打开连接 +02:00

选择 [Limit1].[Id] AS [Id], [Limit1].[Property1] AS [Property1], [Limit1].[Property2] AS [Property2], [Limit1].[child_Id] AS [child_Id] 从(选择顶部(1) [Extent1].[Id] AS [Id], [Extent1].[Property1] AS [Property1], [Extent1].[Property2] AS [Property2], [Extent1].[child_Id] AS [child_Id] FROM [dbo].[ParentEntities] AS [Extent1] WHERE [Extent1].[Id] = @p__linq__0 ) AS [限制1]

-- p__linq__0: '2' (Type = Int32, IsNullable = false) -- 执行于 28.04.2015 08:49:41 +02:00 -- 在 0 毫秒内完成,结果为:SqlDataReader

在 28.04.2015 08:49:41 +02:00 关闭连接 28.04.2015 08:49:41 +02:00 开始交易于 28.04.2015 08:49:41 +02:00

更新 [dbo].[ParentEntities] SET [Property1] = @0 WHERE ([Id] = @1)

-- @0: '已更改'(类型 = 字符串,大小 = -1) -- @1: '2' (类型 = Int32) -- 执行于 28.04.2015 08:49:41 +02:00 -- 在 5 毫秒内完成,结果:1

在 28.04.2015 08:49:41 +02:00 提交的交易关闭连接 在 28.04.2015 08:49:41 +02:00

从 SQL 日志中可以看出,只有更改的属性被更新。 请注意:“已更改”表示也分配给相同的值。因此,如果您从 TextBox 更新,请在分配之前检查该值是否已更改。 更新

EF6 似乎真正跟踪更改 - 具有相同值的赋值不会触发更新 - 请参阅更新的代码

【讨论】:

  • 我保存了具有很多属性的整个对象,所以我需要手动完成所有操作:)。
  • 是的,你的代码证明了我的代码“深层”有问题。
  • 它使用 BDSet .. 而不是你的整个上下文
  • 对不起 ;-) - 但请检查您是否正在触摸真正没有改变的属性
  • 但请参阅更新的评论 - 该值确实需要更改才能触发更新。如果您重新分配相同的值,它不会更新表。
猜你喜欢
  • 2011-12-19
  • 2016-02-01
  • 1970-01-01
  • 2016-11-16
  • 1970-01-01
  • 2013-07-24
  • 2019-02-09
  • 1970-01-01
  • 2019-12-05
相关资源
最近更新 更多