如何使用Entity Framework仅更新一个字段?

239

这里是表格

用户

UserId
UserName
Password
EmailAddress

以及代码..

public void ChangePassword(int userId, string password){
//code to update the password..
}

36
“Password”指的是哈希密码,对吗? :-) - Edward Brey
17个回答

3
我知道这是一个旧的帖子,但我也在寻找类似的解决方案,并决定采用@Doku-so提供的解决方案。我发表评论来回答@Imran Rizvi提出的问题,我遵循了@Doku-so的链接,显示了类似的实现。@Imran Rizvi的问题是,他在使用提供的解决方案时遇到了错误“无法将Lambda表达式转换为类型'Expression> []',因为它不是委托类型”。如果其他人遇到此错误并决定使用@Doku-so的解决方案,我想提供我对@Doku-so解决方案进行的小修改,以修复此错误。
问题出在Update方法的第二个参数,
public int Update(T entity, Expression<Func<T, object>>[] properties). 

要使用提供的语法调用此方法,请按如下方式操作:
Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn); 

您需要在第二个参数前添加 'params' 关键字。

public int Update(T entity, params Expression<Func<T, object>>[] properties)

如果你不想更改方法签名,那么调用Update方法时需要添加new关键字,指定数组的大小,最后使用集合对象初始化语法来更新每个属性,如下所示。

Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });

在 @Doku-so 的示例中,他指定了一个表达式数组,因此您必须将要更新的属性传递到一个数组中,由于这是一个数组,您还必须指定数组的大小。为了避免这种情况,您还可以将表达式参数更改为使用 IEnumerable 而不是数组。

以下是我对 @Doku-so 解决方案的实现。

public int Update<TEntity>(LcmsEntities dataContext, DbEntityEntry<TEntity> entityEntry, params Expression<Func<TEntity, object>>[] properties)
     where TEntity: class
    {
        entityEntry.State = System.Data.Entity.EntityState.Unchanged;

        properties.ToList()
            .ForEach((property) =>
            {
                var propertyName = string.Empty;
                var bodyExpression = property.Body;
                if (bodyExpression.NodeType == ExpressionType.Convert
                    && bodyExpression is UnaryExpression)
                {
                    Expression operand = ((UnaryExpression)property.Body).Operand;
                    propertyName = ((MemberExpression)operand).Member.Name;
                }
                else
                {
                    propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
                }

                entityEntry.Property(propertyName).IsModified = true;
            });

        dataContext.Configuration.ValidateOnSaveEnabled = false;

        return dataContext.SaveChanges();
    }

用法:

this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId);

@Doku-so提供了一种很酷的方法,使用了泛型,我使用这个概念来解决我的问题,但你不能直接使用@Doku-so的解决方案,在这篇文章和链接的文章中都没有人回答使用错误的问题。


当程序执行到entityEntry.State = EntityState.Unchanged;这一行时,我正在处理您的解决方案。此时,参数entityEntry中的所有更新值都会被还原,因此不会保存任何更改。请问您能否帮忙解决这个问题?谢谢。 - sairfan

1

结合几个建议,我提出以下方案:

    async Task<bool> UpdateDbEntryAsync<T>(T entity, params Expression<Func<T, object>>[] properties) where T : class
    {
        try
        {
            var entry = db.Entry(entity);
            db.Set<T>().Attach(entity);
            foreach (var property in properties)
                entry.Property(property).IsModified = true;
            await db.SaveChangesAsync();
            return true;
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("UpdateDbEntryAsync exception: " + ex.Message);
            return false;
        } 
    }

被调用者

UpdateDbEntryAsync(dbc, d => d.Property1);//, d => d.Property2, d => d.Property3, etc. etc.);

或者通过

await UpdateDbEntryAsync(dbc, d => d.Property1);

或者通过

bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;

如何使这个方法对其他类可用,类似于扩展方法? - Velkumar
这个.NET CORE教程中,他们展示了使用(新的)EF Core在MVC中更新特定属性的最佳实践。请查找“TryUpdateModelAsync”。 - Guy
1
@Guy 很棒。不过,微软的“最佳实践”又一次是做与他们的工具构建不同的事情... - Auspex

1
我使用ValueInjecter nuget将绑定模型注入到数据库实体中,使用以下代码:
public async Task<IHttpActionResult> Add(CustomBindingModel model)
{
   var entity= await db.MyEntities.FindAsync(model.Id);
   if (entity== null) return NotFound();

   entity.InjectFrom<NoNullsInjection>(model);

   await db.SaveChangesAsync();
   return Ok();
}

注意使用自定义约定,如果属性为null,则不会从服务器更新属性。

ValueInjecter v3+

public class NoNullsInjection : LoopInjection
{
    protected override void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp)
    {
        if (sp.GetValue(source) == null) return;
        base.SetValue(source, target, sp, tp);
    }
}

使用方法:

target.InjectFrom<NoNullsInjection>(source);

Value Injecter V2

查看这个答案

注意事项

你将不知道属性是否有意被清空为null,还是本来就没有任何值。换句话说,属性值只能被替换为另一个值,而不能被清除。


1
_context.Users.UpdateProperty(p => p.Id, request.UserId, new UpdateWrapper<User>()
                {
                    Expression = p => p.FcmId,Value = request.FcmId
                });
   await _context.SaveChangesAsync(cancellationToken);

更新属性是一个扩展方法

public static void UpdateProperty<T, T2>(this DbSet<T> set, Expression<Func<T, T2>> idExpression,
            T2 idValue,
            params UpdateWrapper<T>[] updateValues)
            where T : class, new()
        {
            var entity = new T();
            var attach = set.Attach(entity);
            attach.Property(idExpression).IsModified = false;
            attach.Property(idExpression).OriginalValue = idValue;
            foreach (var update in updateValues)
            {
                attach.Property(update.Expression).IsModified = true;
                attach.Property(update.Expression).CurrentValue = update.Value;
            }
        }

更新包装器是一个类

public class UpdateWrapper<T>
    {
        public Expression<Func<T, object>> Expression  { get; set; }
        public object Value { get; set; }
    }

对我来说,这属于“如何把简单的任务复杂化”的范畴。首先,为什么要在原始对象已被跟踪的情况下附加一个新对象?这样做甚至会导致异常。而且,这种方法在乐观并发和可能的验证失败时也会出现问题。 - Gert Arnold
原始数据不被跟踪,甚至不从数据库中获取。在这段代码区域中不处理验证,因为这不是问题的一部分。 - supernerd
在你的示例中,它们被跟踪。创建存根实体时,验证和乐观并发应始终作为警告提到。无论如何,这又是另一个回答,说“设置IsModified = true”。它真的有什么新东西可以展示另一种方法吗?这也不是被问到的。旧问题收集越来越多的噪音是Stack Overflow上相当烦人的问题。 - Gert Arnold
@GertArnold 确实,我正在使用已经在其他答案中提到的 IsModified true。我所提供的新内容是通过扩展方法来完成,并使其更加通用,以便我们可以指定实体的 Id,该 Id 可以是任何数据类型,并且还可以指定要更新哪些属性,而无需从数据库中获取它们。 但如果您觉得这没有帮助,那就由您决定了,有人会想要通过扩展方法或其他方式来实现它,我只是为了帮助他人做出贡献。 - supernerd

0

我一直在寻找同样的问题,最终我找到了解决方案。

using (CString conn = new CString())
{
    USER user = conn.USERs.Find(CMN.CurrentUser.ID);
    user.PASSWORD = txtPass.Text;
    conn.SaveChanges();
}

相信我,它对我来说像魔法一样有效。


-2
public async Task<bool> UpdateDbEntryAsync(TEntity entity, params Expression<Func<TEntity, object>>[] properties)
{
    try
    {
        this.Context.Set<TEntity>().Attach(entity);
        EntityEntry<TEntity> entry = this.Context.Entry(entity);
        entry.State = EntityState.Modified;
        foreach (var property in properties)
            entry.Property(property).IsModified = true;
        await this.Context.SaveChangesAsync();
        return true;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

-8
public void ChangePassword(int userId, string password)
{
  var user = new User{ Id = userId, Password = password };
  using (var db = new DbContextName())
  {
    db.Entry(user).State = EntityState.Added;
    db.SaveChanges();
  }
}

2
这将添加一行新数据。问题是如何更新现有数据。 - Edward Brey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接