首页 文章

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

提问于
浏览
163

这是 table

Users

UserId
UserName
Password
EmailAddress

和代码..

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

15 回答

  • 8

    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

    您可以告诉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();
      }
    }
    
  • 15

    你基本上有两个选择:

    • 一路走EF方式,在这种情况下,你会的

    • 根据提供的 userId 加载对象 - 整个对象被加载

    • 更新 password 字段

    • 使用上下文的 .SaveChanges() 方法保存对象

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

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

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

    • 您在T-SQL代码中定义了一个完全符合您需要的存储过程(只需为给定的 UserId 更新 Password 列,而不执行任何其他操作 - 基本上执行 UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId )并在EF模型中为该存储过程创建函数导入并且您调用此函数而不是执行上述步骤
  • 6

    我正在使用这个:

    实体:

    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();
    
  • 325

    在寻找这个问题的解决方案时,我通过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();
    }
    

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

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

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

    The method I am currently using in my own code ,扩展为处理 ExpressionType.Convert 类型的(Linq)表达式 . 在我的情况下这是必要的,例如使用 Guid 和其他对象属性 . 这些在Convert()中是'wrapped',因此不由 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();
    }
    
  • 0

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

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

    我在这里比赛迟到了,但这就是我这样做的原因,我花了一些时间寻找一个我满意的解决方案;这只会为更改的字段生成 UPDATE 语句,因为您通过"white list"概念明确定义了它们,这样可以更安全地防止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));
    
  • -7

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

    或者如果你没有't want to change the method signature then to call the Update method you need to add the ' 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();
        }
    

    Usage:

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

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

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

    我使用 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

    警告

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

  • 0

    在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.
    
  • 3

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

    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;
    
  • 10

    我一直在寻找,最后我找到了解决方案

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

    相信我这对我来说就像一个魅力 .

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

相关问题