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

时间:2010-09-04 12:56:59

标签: sql entity-framework-4 entity field dbcontext

这是表格

用户

UserId
UserName
Password
EmailAddress

和代码..

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

17 个答案:

答案 0 :(得分:329)

Ladislav的答案更新为使用DbContext(在EF 4.1中引入):

public void ChangePassword(int userId, string password)
{
  var user = new User() { Id = userId, Password = password };
  using (var db = new MyEfContextName())
  {
    db.Users.Attach(user);
    db.Entry(user).Property(x => x.Password).IsModified = true;
    db.SaveChanges();
  }
}

答案 1 :(得分:52)

您可以告诉EF哪些属性必须以这种方式更新:

public void ChangePassword(int userId, string password)
{
  var user = new User { Id = userId, Password = password };
  using (var context = new ObjectContext(ConnectionString))
  {
    var users = context.CreateObjectSet<User>();
    users.Attach(user);
    context.ObjectStateManager.GetObjectStateEntry(user)
      .SetModifiedProperty("Password");
    context.SaveChanges();
  }
}

答案 2 :(得分:15)

您基本上有两种选择:

  • 一路走EF方式,在这种情况下,你会的
    • 根据提供的userId加载对象 - 加载整个对象
    • 更新password字段
    • 使用上下文的.SaveChanges()方法
    • 保存对象

在这种情况下,由EF如何详细处理。我只测试了这个,在这种情况下我只更改一个对象的单个字段,EF创建的几乎就是你手动创建的东西 - 如:

`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`

因此,EF非常聪明,可以确定哪些列确实已更改,并且它将创建一个T-SQL语句来处理实际上必要的更新。

  • 在T-SQL代码中定义一个完全符合您需要的存储过程(只更新给定Password的{​​{1}}列,而不是其他任何内容 - 基本上执行UserId)并在EF模型中为该存储过程创建函数导入,并调用此函数而不是执行上述步骤

答案 3 :(得分:10)

我正在使用这个:

实体:

public class Thing 
{
    [Key]
    public int Id { get; set; }
    public string Info { get; set; }
    public string OtherStuff { get; set; }
}

的DbContext:

public class MyDataContext : DbContext
{
    public DbSet<Thing > Things { get; set; }
}

访问者代码:

MyDataContext ctx = new MyDataContext();

// FIRST create a blank object
Thing thing = ctx.Things.Create();

// SECOND set the ID
thing.Id = id;

// THIRD attach the thing (id is not marked as modified)
db.Things.Attach(thing); 

// FOURTH set the fields you want updated.
thing.OtherStuff = "only want this field updated.";

// FIFTH save that thing
db.SaveChanges();

答案 4 :(得分:8)

在搜索此问题的解决方案时,我通过Patrick Desjardins' blog找到了GONeale答案的变体:

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
  DatabaseContext.Entry(entity).State = EntityState.Unchanged;
  foreach (var property in properties)
  {
    var propertyName = ExpressionHelper.GetExpressionText(property);
    DatabaseContext.Entry(entity).Property(propertyName).IsModified = true;
  }
  return DatabaseContext.SaveChangesWithoutValidation();
}
  

正如您所看到的,它将第二个参数作为a的表达式   功能。这将通过在Lambda中指定来使用此方法   表达要更新的属性。

...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);

(这里也给出了一个类似的解决方案:https://stackoverflow.com/a/5749469/2115384

我目前在我自己的代码中使用的方法,扩展为处理类型为ExpressionType.Convert的(Linq)表达式。 在我的情况下这是必要的,例如Guid和其他对象属性。那些被“包裹”在Convert()中,因此不由System.Web.Mvc.ExpressionHelper.GetExpressionText处理。

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
    DbEntityEntry<T> entry = dataContext.Entry(entity);
    entry.State = EntityState.Unchanged;
    foreach (var property in properties)
    {
        string propertyName = "";
        Expression 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);
        }
        entry.Property(propertyName).IsModified = true;
    }

    dataContext.Configuration.ValidateOnSaveEnabled = false;
    return dataContext.SaveChanges();
}

答案 5 :(得分:6)

我在这里比赛迟到了,但这就是我这样做的原因,我花了一些时间寻找一个令我满意的解决方案;这只会为更改的字段生成UPDATE语句,因为您通过“白名单”概念明确定义了它们,这样可以更安全地阻止Web表单注入。

我的ISession数据存储库的摘录:

public bool Update<T>(T item, params string[] changedPropertyNames) where T 
  : class, new()
{
    _context.Set<T>().Attach(item);
    foreach (var propertyName in changedPropertyNames)
    {
        // If we can't find the property, this line wil throw an exception, 
        //which is good as we want to know about it
        _context.Entry(item).Property(propertyName).IsModified = true;
    }
    return true;
}

如果您愿意,可以将其包装在try..catch中,但我个人希望我的调用者知道此方案中的异常。

它将以类似这种方式调用(对我来说,这是通过ASP.NET Web API):

if (!session.Update(franchiseViewModel.Franchise, new[]
    {
      "Name",
      "StartDate"
  }))
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));

答案 6 :(得分:6)

在Entity Framework Core中,Attach返回条目,因此您只需要:

var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();

答案 7 :(得分:3)

我知道这是一个老线程,但我也在寻找类似的解决方案,并决定采用@ Doku所提供的解决方案。我正在评论回答@Imran Rizvi提出的问题,我跟着@Doku-so链接显示了类似的实现。 @Imran Rizvi的问题是他使用提供的解决方案“无法将Lambda表达式转换为Type”表达式&gt; []'因为它不是委托类型'。我想对@ 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的解决方案,在这篇文章和链接的帖子没有人回答使用错误问题。

答案 8 :(得分:3)

实体框架通过DbContext跟踪您从数据库查询的对象的更改。例如,如果您的DbContext实例名称是dbContext

public void ChangePassword(int userId, string password){
     var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
     user.password = password;
     dbContext.SaveChanges();
}

答案 9 :(得分: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

查找this answer

买者

您不会知道该属性是否被故意清除为空或它只是没有任何值。换句话说,属性值只能替换为其他值但不能清除。

答案 10 :(得分:1)

在EntityFramework Core 2.x中,不需要Attach

 // get a tracked entity
 var entity = context.User.Find(userId);
 entity.someProp = someValue;
 // other property changes might come here
 context.SaveChanges();

在SQL Server中对此进行了分析并对其进行了分析:

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [User] SET [someProp] = @p0
WHERE [UserId] = @p1;
SELECT @@ROWCOUNT;

',N'@p1 int,@p0 bit',@p1=1223424,@p0=1

查找确保已加载的实体不会触发SELECT,并且会根据需要自动附加该实体(来自文档):

    ///     Finds an entity with the given primary key values. If an entity with the given primary key values
    ///     is being tracked by the context, then it is returned immediately without making a request to the
    ///     database. Otherwise, a query is made to the database for an entity with the given primary key values
    ///     and this entity, if found, is attached to the context and returned. If no entity is found, then
    ///     null is returned.

答案 11 :(得分:1)

我一直在寻找相同的东西,最后我找到了解决方法

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

相信我,它就像魅力一样对我有用。

答案 12 :(得分:0)

_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;
            }
        }

Update Wrapper是一个类

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

答案 13 :(得分:0)

这就是我所使用的,使用自定义InjectNonNull(obj dest,obj src)使其完全灵活

[HttpPost]
public async Task<IActionResult> Post( [FromQuery]Models.Currency currency ) {
  if ( ModelState.IsValid ) {
    // find existing object by Key
    Models.Currency currencyDest = context.Currencies.Find( currency.Id ); 

    context.Currencies.Attach( currencyDest );

    // update only not null fields
    InjectNonNull( currencyDest, currency );

    // save
    await context.SaveChangesAsync( );
  }  
  return Ok();
}

// Custom method
public static T InjectNonNull<T>( T dest, T src ) {
  foreach ( var propertyPair in PropertyLister<T, T>.PropertyMap ) {
    var fromValue = propertyPair.Item2.GetValue( src, null );
    if ( fromValue != null && propertyPair.Item1.CanWrite ) {
       propertyPair.Item1.SetValue( dest, fromValue, null );
    }
  }
  return dest;
}

答案 14 :(得分:0)

结合几点建议我提出以下建议:

    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;

答案 15 :(得分:-1)

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;
    }
}

答案 16 :(得分:-7)

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