Bagaimana cara memperbarui hanya satu bidang menggunakan Entity Framework?

190

Ini tabelnya

Pengguna

UserId
UserName
Password
EmailAddress

dan kodenya ..

public void ChangePassword(int userId, string password){
//code to update the password..
}
h3n
sumber
26
Oleh Password, Anda berarti password hash, kan? :-)
Edward Brey

Jawaban:

370

Jawaban Ladislav diperbarui untuk menggunakan DbContext (diperkenalkan pada 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();
  }
}
Stuart
sumber
55
Saya hanya dapat membuat kode ini berfungsi dengan menambahkan db.Configuration.ValidateOnSaveEnabled = false; sebelum db. Simpan Perubahan ()?
Jake Drew
3
Namespace mana yang harus dimasukkan untuk digunakan db.Entry(user).Property(x => x.Password).IsModified = true;dan tidakdb.Entry(user).Property("Password").IsModified = true;
Johan
5
Pendekatan ini melempar OptimisticConcurencyException ketika tabel memiliki bidang stempel waktu.
Maksim Vi.
9
Saya pikir layak disebutkan bahwa jika Anda menggunakan db.Configuration.ValidateOnSaveEnabled = false;Anda mungkin ingin terus memvalidasi bidang yang Anda perbarui:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Ziul
2
Anda perlu mengatur ValidateOnSaveEnabled menjadi false jika Anda memiliki bidang yang diperlukan dalam tabel Anda yang tidak Anda berikan selama pembaruan Anda
Sal
54

Anda dapat memberi tahu EF properti mana yang harus diperbarui dengan cara ini:

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();
  }
}
Ladislav Mrnka
sumber
ObjectStateManager tidak tersedia untuk DBContext
LoxLox
17

Pada dasarnya Anda memiliki dua opsi:

  • lanjutkan EF, jika demikian, Anda akan melakukannya
    • memuat objek berdasarkan yang userIddisediakan - seluruh objek akan dimuat
    • perbarui passwordbidang
    • menyimpan objek kembali menggunakan konteks ini .SaveChanges()metode

Dalam hal ini, tergantung pada EF bagaimana menangani hal ini secara detail. Saya baru saja menguji ini, dan dalam hal ini saya hanya mengubah satu bidang objek, apa yang dibuat EF adalah apa yang Anda buat secara manual juga - sesuatu seperti:

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

Jadi EF cukup pintar untuk mengetahui kolom apa yang memang telah berubah, dan itu akan membuat pernyataan T-SQL untuk menangani hanya pembaruan yang sebenarnya diperlukan.

  • Anda mendefinisikan prosedur tersimpan yang melakukan persis apa yang Anda butuhkan, dalam kode T-SQL (cukup perbarui Passwordkolom untuk yang diberikan UserIddan tidak ada yang lain - pada dasarnya dijalankan UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId) dan Anda membuat impor fungsi untuk prosedur tersimpan itu dalam model EF Anda dan Anda menyebutnya berfungsi alih-alih melakukan langkah-langkah yang diuraikan di atas
marc_s
sumber
1
@ marc-s Sebenarnya Anda tidak perlu memuat seluruh objek!
Arvand
14

Di Entity Framework Core, Attachmengembalikan entri, jadi yang Anda butuhkan adalah:

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

Saya menggunakan ini:

kesatuan:

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

kode accessor:

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();
groggyjava
sumber
1
Saya mendapatkan kesalahan validasi entitas ketika saya mencoba ini, tapi itu pasti terlihat keren.
devlord
Tidak berhasil dengan metode ini !!!: mungkin Anda perlu memberikan detail lebih lanjut cara menggunakannya !!! - ini adalah kesalahan: "Melampirkan entitas tipe 'Domain.Job' gagal karena entitas lain dari tipe yang sama sudah memiliki nilai kunci primer yang sama. Ini dapat terjadi saat menggunakan metode 'Lampirkan' atau pengaturan status suatu entitas menjadi 'Tidak berubah' atau 'Dimodifikasi' jika ada entitas dalam grafik yang memiliki nilai kunci yang bertentangan. Ini mungkin karena beberapa entitas baru dan belum menerima nilai kunci yang dihasilkan basis data. "
Lucian Bumb
Perfec! Periksa jawaban saya untuk melihat pendekatan yang fleksibel untuk model apa pun!
kryp
10

Saat mencari solusi untuk masalah ini, saya menemukan variasi pada jawaban GONeale melalui blog 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();
}

" Seperti yang Anda lihat, parameter kedua merupakan ekspresi fungsi. Parameter ini akan menggunakan metode ini dengan menentukan dalam ekspresi Lambda properti mana yang akan diperbarui. "

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

(Solusi yang agak mirip juga diberikan di sini: https://stackoverflow.com/a/5749469/2115384 )

Metode yang saya gunakan saat ini dalam kode saya sendiri , diperluas untuk menangani juga (Linq) Ekspresi jenis ExpressionType.Convert. Ini diperlukan dalam kasus saya, misalnya dengan Guiddan properti objek lainnya. Itu 'dibungkus' dalam Konversi () dan karenanya tidak ditangani oleh 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();
}
Doku-so
sumber
1
Ketika saya menggunakan ini memberi saya kesalahan berikut, Tidak dapat mengonversi ekspresi Lambda ke Ketik 'Ekspresi <Fungsi <RequestDetail, objek >> []' karena ini bukan tipe delegasi
Imran Rizvi
@ImranRizvi, Anda hanya perlu memperbarui parameter ke: public int Update (entitas T, params Ekspresi <Func <T, object >> [] properti) CATATAN kata kunci params sebelum ekspresi
dalcam
6

Saya terlambat ke permainan di sini, tetapi ini adalah bagaimana saya melakukannya, saya menghabiskan beberapa saat mencari solusi yang membuat saya puas; ini menghasilkan UPDATEpernyataan HANYA untuk bidang yang diubah, saat Anda secara eksplisit mendefinisikan apa itu melalui konsep "daftar putih" yang lebih aman untuk mencegah injeksi formulir web.

Kutipan dari repositori data ISession saya:

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

Ini bisa dibungkus dalam percobaan .. jika Anda menginginkannya, tapi saya pribadi ingin penelepon saya tahu tentang pengecualian dalam skenario ini.

Ini akan dipanggil dengan cara seperti ini (bagi saya, ini melalui ASP.NET Web API):

if (!session.Update(franchiseViewModel.Franchise, new[]
    {
      "Name",
      "StartDate"
  }))
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
GONeale
sumber
2
Jadi solusi yang lebih baik Anda adalah apa yang Elisa? Anda harus secara eksplisit menyatakan properti apa yang Anda izinkan untuk diperbarui (seperti daftar putih yang diperlukan untuk UpdateModelperintah ASP.NET MVC ), dengan cara itu Anda memastikan injeksi form hacker tidak dapat terjadi dan mereka tidak dapat memperbarui bidang yang tidak diizinkan untuk diperbarui. Namun, jika seseorang dapat mengonversi array string menjadi semacam parameter ekspresi lambda dan bekerja dengannya dalam Update<T>, hebat
GONeale
3
@ GONeale - Baru saja lewat. Seseorang memecahkannya menggunakan Lambdas!
David Spence
1
@ Elisa Ini dapat ditingkatkan dengan menggunakan Func <T, List <object>> bukan string []
Spongebob Kamerad
Bahkan kemudian ke permainan, dan mungkin ini sintaks yang jauh lebih baru, tetapi var entity=_context.Set<T>().Attach(item);diikuti oleh entity.Property(propertyName).IsModified = true;dalam lingkaran harus bekerja.
Auspex
4

Kerangka kerja entitas melacak perubahan Anda pada objek yang Anda tanyakan dari database melalui DbContext. Sebagai contoh jika nama instance DbContext Anda adalah dbContext

public void ChangePassword(int userId, string password){
     var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
     user.password = password;
     dbContext.SaveChanges();
}
Bahtiyar Özdere
sumber
Dan bagaimana seharusnya tampilan dalam kasus ini?
Emanuela Colta
Ini salah karena akan menyimpan seluruh objek Pengguna dengan kata sandi yang diubah.
amuliar
itu benar, tetapi sisa objek Pengguna akan sama seperti sebelumnya dalam konteks, satu-satunya hal yang mungkin akan berbeda adalah kata sandi sehingga pada dasarnya hanya memperbarui kata sandi.
Tomislav3008
3

Saya tahu ini adalah utas lama tapi saya juga mencari solusi yang sama dan memutuskan untuk pergi dengan solusi @ Doku-begitu disediakan. Saya berkomentar untuk menjawab pertanyaan yang diajukan oleh @Imran Rizvi, saya mengikuti tautan @ Doku-so yang menunjukkan implementasi serupa. Pertanyaan @Imran Rizvi adalah bahwa ia mendapatkan kesalahan menggunakan solusi yang disediakan 'Tidak dapat mengonversi ekspresi Lambda ke Ketik' Ekspresi> [] 'karena ini bukan tipe delegasi'. Saya ingin menawarkan modifikasi kecil yang saya buat untuk solusi @ Doku-so yang memperbaiki kesalahan ini jika ada orang lain yang menemukan posting ini dan memutuskan untuk menggunakan solusi @ Doku-so.

Masalahnya adalah argumen kedua dalam metode Pembaruan,

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

Untuk memanggil metode ini menggunakan sintaks yang disediakan ...

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

Anda harus menambahkan kata kunci 'params' di depan arugment kedua.

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

atau jika Anda tidak ingin mengubah tanda tangan metode kemudian memanggil metode Perbarui Anda perlu menambahkan kata kunci ' baru ', tentukan ukuran array, lalu akhirnya gunakan sintaks initializer objek koleksi untuk setiap properti untuk memperbarui seperti yang terlihat di bawah.

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

Dalam contoh @ Doku-so, ia menetapkan array Ekspresi sehingga Anda harus meneruskan properti untuk memperbarui dalam array, karena array Anda juga harus menentukan ukuran array. Untuk menghindari ini, Anda juga bisa mengubah argumen ekspresi untuk menggunakan IEnumerable, bukan array.

Inilah implementasi solusi @ Doku-so saya.

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

Pemakaian:

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

@ Doku-so memberikan pendekatan keren menggunakan generik, saya menggunakan konsep untuk menyelesaikan masalah saya tetapi Anda tidak bisa menggunakan solusi @ Doku-so seperti pada posting ini dan posting terkait tidak ada yang menjawab pertanyaan kesalahan penggunaan.

PWS
sumber
Saya sedang mengerjakan solusi Anda ketika program melewati entityEntry.State = EntityState.Unchanged;semua nilai yang diperbarui dalam parameter entityEntrydapatkan kembali, jadi tidak ada perubahan yang disimpan, dapatkah Anda membantu, terima kasih
sairfan
3

Di EntityFramework Core 2.x tidak perlu untuk Attach:

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

Mencoba ini di SQL Server dan memprofilkannya:

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

Temukan memastikan bahwa entitas yang sudah dimuat tidak memicu SELECT dan juga secara otomatis melampirkan entitas jika diperlukan (dari dokumen):

    ///     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.
Alexei
sumber
1

Menggabungkan beberapa saran saya usulkan sebagai berikut:

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

dipanggil oleh

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

Atau oleh

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

Atau oleh

bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;
Orang
sumber
Bagaimana membuat metode ini tersedia untuk kelas lain, mungkin seperti metode ekstensi?
Velkumar
dalam tutorial .NET CORE ini, mereka menunjukkan praktik terbaik menggunakan EF Core (baru) untuk memperbarui properti spesifik di MVC. cari 'TryUpdateModelAsync'.
Guy
1
@Beli Keren. Padahal, sekali lagi "praktik terbaik" Microsoft adalah melakukan sesuatu selain dari apa yang dibuat alat mereka ...
Auspex
Ini solusi yang bagus.
Timothy Macharia
1

Saya menggunakan ValueInjecternuget untuk menyuntikkan Binding Model ke dalam basis data Entity menggunakan berikut:

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

Perhatikan penggunaan konvensi khusus yang tidak memperbarui Properti jika mereka null dari server.

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

Pemakaian:

target.InjectFrom<NoNullsInjection>(source);

Value Injecter V2

Cari jawaban ini

Peringatan

Anda tidak akan tahu apakah properti itu sengaja dihapus nol atau hanya tidak memiliki nilai apa pun. Dengan kata lain, nilai properti hanya dapat diganti dengan nilai lain tetapi tidak dihapus.

Korayem
sumber
0

Saya mencari yang sama dan akhirnya saya menemukan solusinya

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

percayalah itu bekerja untuk saya seperti pesona.

Burhan Ul Haqq Zahir
sumber
0

Inilah yang saya gunakan, menggunakan custom InjectNonNull (obj dest, obj src) yang membuatnya sepenuhnya fleksibel

[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;
}
kryp
sumber
-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;
    }
}
pintu gupta
sumber
-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();
  }
}
Rahul
sumber
1
Ini akan menambah baris baru. Pertanyaannya adalah bagaimana cara memperbarui yang sudah ada.
Edward Brey