Làm cách nào để cập nhật chỉ một trường bằng Entity Framework?


188

Đây là cái bàn

Người dùng

UserId
UserName
Password
EmailAddress

và mã ..

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

26
Bởi Password, bạn có nghĩa là mật khẩu băm, phải không? :-)
Edward Brey

Câu trả lời:


368

Câu trả lời của Ladislav được cập nhật để sử dụng DbContext (được giới thiệu trong 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();
  }
}

55
Tôi chỉ có thể làm cho mã này hoạt động bằng cách thêm db.Configuration.ValidateOnSaveEnables = false; trước db.SaveChanges ()?
Jake đã vẽ

3
Không gian tên nào sẽ bao gồm để sử dụng db.Entry(user).Property(x => x.Password).IsModified = true;và không sử dụngdb.Entry(user).Property("Password").IsModified = true;
Johan

5
Cách tiếp cận này đưa ra một OptimisticConcurencyException khi bảng có trường dấu thời gian.
Maksim Vi.

9
Tôi nghĩ điều đáng nói là nếu bạn đang sử dụng, db.Configuration.ValidateOnSaveEnabled = false;bạn có thể muốn tiếp tục xác thực lĩnh vực bạn đang cập nhật:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Ziul

2
Bạn cần đặt ValidateOnSaveEnables thành false nếu bạn có các trường bắt buộc trong bảng mà bạn không cung cấp trong quá trình cập nhật của mình
Sal

54

Bạn có thể nói với EF những thuộc tính nào phải được cập nhật theo cách này:

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

ObjectStateManager không khả dụng cho DBContext
LoxLox

16

Về cơ bản, bạn có hai tùy chọn:

  • đi theo con đường của EF tất cả các cách, trong trường hợp đó, bạn sẽ
    • tải đối tượng dựa trên userIdcung cấp - toàn bộ đối tượng được tải
    • cập nhật các passwordlĩnh vực
    • lưu lại đối tượng bằng .SaveChanges()phương thức của bối cảnh

Trong trường hợp này, tùy thuộc vào cách xử lý vấn đề này một cách chi tiết. Tôi mới thử nghiệm điều này và trong trường hợp tôi chỉ thay đổi một trường duy nhất của một đối tượng, những gì EF tạo ra cũng giống như những gì bạn tạo bằng tay - cũng giống như:

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

Vì vậy, EF đủ thông minh để tìm ra những cột nào thực sự đã thay đổi và nó sẽ tạo ra một câu lệnh T-SQL để xử lý chỉ những cập nhật thực sự cần thiết.

  • bạn xác định một thủ tục được lưu trữ thực hiện chính xác những gì bạn cần, trong mã T-SQL (chỉ cần cập nhật Passwordcột cho đã cho UserIdvà không có gì khác - về cơ bản thực thi UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId) và bạn tạo một hàm nhập cho thủ tục được lưu trữ đó trong mô hình EF của bạn và bạn gọi đây là chức năng thay vì thực hiện các bước được nêu ở trên

1
@ marc-s Thật ra bạn không phải tải toàn bộ đối tượng!
Arvand

13

Trong Entity Framework Core, Attachtrả về mục nhập, vì vậy tất cả những gì bạn cần là:

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

12

Tôi đang sử dụng cái này:

thực thể:

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

mã truy cập:

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

1
Tôi nhận được lỗi xác thực thực thể khi tôi thử điều này, nhưng nó chắc chắn trông rất tuyệt.
chúa tể

Không hoạt động phương pháp này !!!: có lẽ bạn cần cung cấp thêm chi tiết về cách sử dụng nó !!! - đây là lỗi: "Việc đính kèm một thực thể loại 'Domain.Job' không thành công vì một thực thể khác cùng loại đã có cùng giá trị khóa chính. Điều này có thể xảy ra khi sử dụng phương thức 'Đính kèm' hoặc đặt trạng thái của thực thể thành 'Không thay đổi' hoặc 'Đã sửa đổi' nếu bất kỳ thực thể nào trong biểu đồ có các giá trị khóa xung đột. Điều này có thể là do một số thực thể mới và chưa nhận được các giá trị khóa do cơ sở dữ liệu tạo ra. "
Lucian Bumb

Nước hoa! Kiểm tra câu trả lời của tôi để xem cách tiếp cận linh hoạt cho bất kỳ mô hình!
kryp

10

Trong khi tìm kiếm giải pháp cho vấn đề này, tôi đã tìm thấy một biến thể trên câu trả lời của GONeale thông qua blog của Patrick Desjardins :

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

" Như bạn có thể thấy, nó lấy tham số thứ hai của nó là biểu thức của hàm. Điều này sẽ cho phép sử dụng phương thức này bằng cách chỉ định trong biểu thức Lambda thuộc tính nào cần cập nhật. "

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

(Một giải pháp tương tự cũng được đưa ra ở đây: https://stackoverflow.com/a/5749469/2115384 )

Phương thức tôi hiện đang sử dụng trong mã của riêng tôi , được mở rộng để xử lý Biểu thức loại (Linq) ExpressionType.Convert. Điều này là cần thiết trong trường hợp của tôi, ví dụ với Guidvà các thuộc tính đối tượng khác. Những cái đó được 'bọc' trong một Convert () và do đó không được xử lý bởi 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();
}

1
Khi tôi sử dụng, nó gây ra lỗi sau, Không thể chuyển đổi biểu thức Lambda thành Loại 'Biểu thức <Func <RequestDetail, đối tượng >> []' vì đây không phải là loại đại biểu
Imran Rizvi

@ImranRizvi, bạn chỉ cần cập nhật các tham số thành: public int Update (thực thể T, params Biểu thức <Func <T, object >> [] property) LƯU Ý từ khóa params trước biểu thức
dalcam

6

Tôi đến trễ trò chơi ở đây, nhưng đây là cách tôi đang thực hiện nó, tôi đã dành một lúc để tìm kiếm một giải pháp mà tôi đã bị bão hòa; điều này tạo ra một UPDATEcâu lệnh CHỈ cho các trường được thay đổi, vì bạn xác định rõ ràng chúng là gì thông qua khái niệm "danh sách trắng" an toàn hơn để ngăn chặn việc tiêm mẫu web.

Một đoạn trích từ kho dữ liệu ISession của tôi:

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

Điều này có thể được gói trong một thử..catch nếu bạn muốn, nhưng cá nhân tôi thích người gọi của tôi để biết về các ngoại lệ trong kịch bản này.

Nó sẽ được gọi theo cách tương tự như thế này (đối với tôi, điều này là thông qua API Web ASP.NET):

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

2
Vậy giải pháp tốt hơn của bạn là gì Elisa? Bạn nên nói rõ các thuộc tính nào bạn cho phép cập nhật (giống như danh sách trắng cần thiết cho UpdateModellệnh của ASP.NET MVC ), theo cách đó bạn đảm bảo việc tiêm mẫu của hacker không thể xảy ra và họ không thể cập nhật các trường mà họ không được phép cập nhật. Tuy nhiên, nếu ai đó có thể chuyển đổi mảng chuỗi thành một loại tham số biểu thức lambda và làm việc với nó trong Update<T>, tuyệt vời
GONeale

3
@GONeale - Chỉ cần đi qua. Ai đó đã bẻ khóa nó bằng Lambdas!
David Spence

1
@Elisa Nó có thể được cải thiện bằng cách sử dụng Func <T, List <object >> thay vì chuỗi []
SpPal Comrade

Ngay cả sau này cho trò chơi, và có lẽ đây là cú pháp gần đây hơn nhiều, nhưng var entity=_context.Set<T>().Attach(item);theo sau entity.Property(propertyName).IsModified = true;trong vòng lặp sẽ hoạt động.
Auspex

4

Khung thực thể theo dõi các thay đổi của bạn trên các đối tượng mà bạn đã truy vấn từ cơ sở dữ liệu thông qua DbContext. Ví dụ: nếu tên đối tượng DbContext của bạn là dbContext

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

Và quan điểm nên như thế nào trong trường hợp này?
Emanuela Colta

Điều này là sai vì nó sẽ lưu toàn bộ đối tượng Người dùng với mật khẩu đã thay đổi.
đặc biệt

Điều đó là đúng, nhưng phần còn lại của đối tượng Người dùng sẽ giống như trước đây trong bối cảnh, điều duy nhất có thể khác là mật khẩu nên về cơ bản nó chỉ cập nhật mật khẩu.
Tomislav3008

3

Tôi biết đây là một chủ đề cũ nhưng tôi cũng đang tìm kiếm một giải pháp tương tự và quyết định đi theo giải pháp @ Doku-so cung cấp. Tôi đang bình luận để trả lời câu hỏi của @Imran Rizvi, tôi đã theo liên kết @ Doku-so cho thấy cách thực hiện tương tự. Câu hỏi của @Imran Rizvi là anh ta đã gặp lỗi khi sử dụng giải pháp được cung cấp 'Không thể chuyển đổi biểu thức Lambda thành Loại' Biểu thức> [] 'vì đó không phải là loại đại biểu'. Tôi muốn cung cấp một sửa đổi nhỏ mà tôi đã thực hiện cho giải pháp của @ Doku-so để khắc phục lỗi này trong trường hợp có ai khác đi qua bài đăng này và quyết định sử dụng giải pháp của @ Doku-so.

Vấn đề là đối số thứ hai trong phương thức Cập nhật,

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

Để gọi phương thức này bằng cú pháp được cung cấp ...

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

Bạn phải thêm từ khóa 'params' trước phần thứ hai.

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

hoặc nếu bạn không muốn thay đổi chữ ký phương thức thì để gọi phương thức Cập nhật, bạn cần thêm từ khóa ' mới ', chỉ định kích thước của mảng, sau đó sử dụng cú pháp khởi tạo đối tượng bộ sưu tập cho từng thuộc tính để cập nhật như đã thấy phía dưới.

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

Trong ví dụ của @ Doku-so, anh ấy đang chỉ định một mảng Biểu thức để bạn phải chuyển các thuộc tính để cập nhật trong một mảng, vì mảng đó bạn cũng phải chỉ định kích thước của mảng. Để tránh điều này, bạn cũng có thể thay đổi đối số biểu thức để sử dụng IEnumerable thay vì một mảng.

Đây là cách tôi thực hiện giải pháp @ 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();
    }

Sử dụng:

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

@ Doku-vì vậy cung cấp một cách tiếp cận tuyệt vời bằng cách sử dụng chung chung, tôi đã sử dụng khái niệm này để giải quyết vấn đề của mình nhưng bạn không thể sử dụng giải pháp của @ Doku-so và trong cả bài đăng này và bài đăng được liên kết không ai trả lời các câu hỏi lỗi sử dụng.


Tôi đã làm việc với giải pháp của bạn khi chương trình vượt qua entityEntry.State = EntityState.Unchanged;tất cả các giá trị được cập nhật trong tham số entityEntryđược hoàn nguyên, vì vậy không có thay đổi nào được lưu, bạn có thể vui lòng giúp đỡ về nó không, cảm ơn
sairfan

2

Trong EntityFramework Core 2.x không cần Attach:

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

Đã thử điều này trong SQL Server và định hình nó:

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

Tìm đảm bảo rằng các thực thể đã được tải không kích hoạt CHỌN và cũng tự động đính kèm thực thể nếu cần (từ các tài liệu):

    ///     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.

1

Kết hợp một số gợi ý tôi đề xuất như sau:

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

gọi bằng

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

Hoặc bằng cách

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

Hoặc bằng cách

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

Làm thế nào để làm cho phương thức này có sẵn cho lớp khác, có thể giống như phương thức mở rộng?
Velkumar

trong hướng dẫn .NET CORE này, họ cho thấy cách thực hành tốt nhất bằng cách sử dụng (lõi) mới để cập nhật các thuộc tính cụ thể trong MVC. hãy tìm 'TryUpdateModelAsync'.
Chàng trai

1
@Guy Tuyệt vời. Mặc dù vậy, một lần nữa, "cách thực hành tốt nhất" của Microsoft là làm một cái gì đó khác với những gì công cụ của họ xây dựng ...
Auspex

Đây là một giải pháp tốt.
Timothy Macharia

1

Tôi sử dụng ValueInjecternuget để tiêm Binding Model vào cơ sở dữ liệu Thực thể bằng cách sử dụng như sau:

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

Lưu ý việc sử dụng quy ước tùy chỉnh không cập nhật Thuộc tính nếu chúng không có trong máy chủ.

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

Sử dụng:

target.InjectFrom<NoNullsInjection>(source);

Giá trị tiêm V2

Tra cứu câu trả lời này

Hãy cẩn thận

Bạn sẽ không biết liệu tài sản có bị xóa một cách cố ý hay không HOẶC nó không có bất kỳ giá trị nào. Nói cách khác, giá trị tài sản chỉ có thể được thay thế bằng giá trị khác nhưng không bị xóa.


0

Tôi đã tìm kiếm tương tự và cuối cùng tôi đã tìm thấy giải pháp

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

tin tôi đi, nó làm việc cho tôi như một cơ duyên


0

Đây là những gì tôi sử dụng, sử dụng tùy chỉnh MethNonNull (obj Dest, obj src), nó làm cho nó hoàn toàn linh hoạt

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

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

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

1
Điều này sẽ thêm một hàng mới. Câu hỏi là làm thế nào để cập nhật một cái hiện có.
Edward Brey
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.