Entity Framework kullanarak yalnızca bir alan nasıl güncellenir?


189

İşte masa

Kullanıcılar

UserId
UserName
Password
EmailAddress

ve kod ..

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

26
By Password, doğru, karma şifreyi demek? :-)
Edward Brey

Yanıtlar:


370

Ladislav'ın cevabı DbContext'i kullanmak üzere güncellendi (EF 4.1'de tanıtıldı):

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
Bu kodu yalnızca db.Configuration.ValidateOnSaveEnabled = false ekleyerek çalıştırabildim. önce db.SaveChanges ()?
Jake Drew

3
Hangi ad alanının kullanılacağı db.Entry(user).Property(x => x.Password).IsModified = true;ve kullanılamayacağıdb.Entry(user).Property("Password").IsModified = true;
Johan

5
Tablonun bir zaman damgası alanı olduğunda bu yaklaşım bir OptimisticConcurencyException oluşturur.
Maksim Vi.

9
Eğer kullanıyorsanız db.Configuration.ValidateOnSaveEnabled = false;, güncellediğiniz alanı doğrulamaya devam etmek isteyebilirsiniz bahsetmeye değer olduğunu düşünüyorumif (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Ziul

2
Tablonuzda güncelleştirme sırasında sağlamadığınız alanları doldurduysanız ValidateOnSaveEnabled öğesini false olarak ayarlamanız gerekir
Sal

54

EF'e hangi özelliklerin bu şekilde güncellenmesi gerektiğini söyleyebilirsiniz:

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 DBContext
LoxLox

17

Temel olarak iki seçeneğiniz vardır:

  • EF yolunu sonuna kadar giderseniz, bu durumda,
    • nesneyi verilene göre yükleyin userId- tüm nesne yüklenir
    • passwordalanı güncelle
    • bağlamın .SaveChanges()yöntemini kullanarak nesneyi geri kaydetme

Bu durumda, bunun nasıl ele alınacağı EF'ye bağlıdır. Bunu sadece test ettim ve sadece bir nesnenin tek bir alanını değiştirdiğimde, EF'in oluşturduğu şey, manuel olarak da oluşturacağınız şeydir - şöyle bir şey:

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

EF, gerçekten hangi sütunların değiştiğini anlayacak kadar akıllıdır ve sadece gerekli olan güncellemeleri işlemek için bir T-SQL ifadesi oluşturur.

  • T-SQL kodunda tam olarak ihtiyacınız olanı yapan bir saklı yordam tanımlarsınız ( Passwordverilen sütunu güncelleyin UserIdve başka bir şey yapmayın - temel olarakUPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId ) ve EF modelinizde bu saklı yordam için bir işlev içe aktarma oluşturursunuz ve bunu çağırırsınız. yukarıda özetlenen adımları yerine

1
@ marc-s Aslında tüm nesneyi yüklemek zorunda değilsiniz!
Arvand

14

Entity Framework Core'da Attachgirişi döndürür, böylece ihtiyacınız olan tek şey:

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

12

Bunu kullanıyorum:

varlık:

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

erişimci kodu:

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
Bunu denediğimde varlık doğrulama hataları alıyorum, ama kesinlikle harika görünüyor.
devlord

Bu yöntem işe yaramaz !!!: belki nasıl kullanılacağı hakkında daha fazla ayrıntı vermeniz gerekir !!! - bu hata: "Aynı türden başka bir varlık aynı birincil anahtar değerine sahip olduğundan, 'Domain.Job' türündeki bir varlık eklenemedi. Bu, 'Ekle' yöntemini kullanırken veya bir varlığın durumunu ayarlarken olabilir grafikteki herhangi bir öğenin çakışan anahtar değerleri varsa "Değiştirilmedi" veya "Değiştirildi" olarak değişir. Bunun nedeni, bazı varlıkların yeni olması ve veritabanı tarafından oluşturulan anahtar değerlerini henüz almamış olması olabilir. "
Lucian Bumb

Mükemmelsin! Herhangi bir model için esnek yaklaşımı görmek için cevabımı kontrol edin!
19'da krip

10

Bu soruna bir çözüm ararken, Patrick Desjardins'in blogu aracılığıyla GONeale'ın cevabında bir varyasyon buldum :

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

" Gördüğünüz gibi, ikinci parametresi olarak bir işlevin ifadesini alır. Bu, hangi yöntemi güncelleyeceğini Lambda ifadesinde belirterek bu yöntemi kullanmanıza izin verir. "

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

(Burada biraz benzer bir çözüm de verilmiştir: https://stackoverflow.com/a/5749469/2115384 )

Şu anda kendi kodumda kullandığım yöntem , (Linq) türündeki ifadeleri de işleyecek şekilde genişletildi ExpressionType.Convert. Bu benim durumumda, örneğin Guidve diğer nesne özellikleri ile gerekliydi . Bunlar bir Convert () içine 'sarılmıştır' ve bu nedenle tarafından ele alınmamıştır 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
Bunu kullandığımda bana aşağıdaki hatayı veriyor, Lambda ifadesi 'İfade <Func <RequestDetail, nesne >> []' türüne dönüştürülemiyor çünkü bir delege türü değil
Imran Rizvi

@ImranRizvi, parametreleri şu şekilde güncellemeniz gerekir: public int Güncelleme (T varlığı, params İfade <Func <T, nesne >> [] özellikler)
İfadeden

6

Burada oyuna geç kaldım, ama ben böyle yapıyorum, memnun kaldım bir çözüm aramak için biraz zaman harcadım; UPDATEweb form enjeksiyonunu yine de önlemek için daha güvenli bir "beyaz liste" kavramı aracılığıyla açık bir şekilde tanımladığınız için, bu SADECE değiştirilen alanlar için bir ifade üretir .

ISession veri havuzumdan bir alıntı:

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

İsterseniz bu bir try..catch sarılmış olabilir, ama ben şahsen bu senaryodaki istisnalar hakkında bilmek benim arayan gibi.

Bu tarzda bir şey olarak adlandırılır (benim için bu bir ASP.NET Web API'sı yoluyla oldu):

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

2
Peki Elisa senin daha iyi çözümün ne? Hangi özelliklerin (ASP.NET MVC'nin UpdateModelkomutu için gerekli beyaz liste gibi) güncellenmesine izin verdiğinizi açıkça belirtmelisiniz , bu şekilde bilgisayar korsanı form enjeksiyonunun gerçekleşememesini ve güncellenmesine izin verilmeyen alanları güncelleyememelerini sağlarsınız. Bununla birlikte, biri dize dizisini bir çeşit lambda ifadeleri parametresine dönüştürebilir ve onunla çalışabilirse Update<T>, harika
GONeale

3
@GONeale - Sadece geçiyor. Birisi Lambdas kullanarak çatladı!
David Spence

1
@Elisa [] yerine Func <T, List <object>> kullanılarak geliştirilebilir
Spongebob Comrade

Hatta daha sonra oyuna, ve belki de bu çok daha yeni bir sözdizimi, ancak döngüde var entity=_context.Set<T>().Attach(item);takip entity.Property(propertyName).IsModified = true;etmelidir.
Auspex

4

Varlık çerçevesi, veritabanından DbContext aracılığıyla sorguladığınız nesnelerdeki değişikliklerinizi izler. Örneğin, DbContext örnek adı dbContext ise

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

Ve bu durumda görüş nasıl görünmeli?
Emanuela Colta

Bu yanlıştır çünkü tüm User nesnesini değiştirilmiş şifre ile kaydeder.
amuliar

bu doğrudur, ancak User nesnesinin geri kalanı daha önce bağlamda olduğu gibi olacaktır, muhtemelen farklı olacak tek şey paroladır, bu yüzden aslında sadece parolayı günceller.
Tomislav3008

3

Bu eski bir iplik olduğunu biliyorum ama ben de benzer bir çözüm arıyordum ve sağlanan çözüm ile gitmeye karar verdi @ Doku-so sağlanan. @ İmran Rizvi tarafından sorulan soruya cevap vermek için yorum yapıyorum, benzer bir uygulama gösteren @ Doku-so linkini takip ettim. @Imran Rizvi'nin sorusu, 'Lambda ifadesi' İfade> [] 'türüne dönüştürülemiyor çünkü bir delege türü olmadığı için' sağlanan çözümü kullanarak bir hata alıyordu. @ Doku-so'nun çözümünde, başka birinin bu yazıya rastlaması ve @ Doku-so'nun çözümünü kullanmaya karar vermesi durumunda bu hatayı düzelten küçük bir değişiklik sunmak istedim.

Sorun, Güncelleme yöntemindeki ikinci argüman,

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

Bu yöntemi sağlanan sözdizimini kullanarak çağırmak için ...

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

İkinci parametrenin önüne 'params' anahtar kelimesini eklemelisiniz.

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

veya yöntem imzasını değiştirmek istemiyorsanız, Güncelleme yöntemini çağırmak için ' yeni ' anahtar kelimesini eklemeniz , dizinin boyutunu belirtmeniz ve ardından her bir özellik için görüldüğü gibi güncellenmesi için koleksiyon nesnesi başlatıcı sözdizimini kullanmanız gerekir. altında.

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

@ Doku-so örneğinde, bir dizi ifadeyi belirtiyor, bu nedenle bir dizide güncellenecek özellikleri geçirmeniz gerekiyor, çünkü dizi nedeniyle dizinin boyutunu da belirtmelisiniz. Bundan kaçınmak için, ifade bağımsız değişkenini dizi yerine IEnumerable kullanacak şekilde değiştirebilirsiniz.

İşte @ Doku-so'nun çözümünü uygulamam.

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

Kullanımı:

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

@ Doku-so jenerik kullanarak serin bir yaklaşım sağladı, ben sorunumu çözmek için kavram kullandım ama sadece Doku-so'nun çözümünü olduğu gibi kullanamazsınız ve hem bu yazı hem de bağlantılı yazıda hiç kimse kullanım hatası sorularını cevaplamadı.


Program geçtiği zaman çözümünüz üzerinde çalışıyordum, entityEntry.State = EntityState.Unchanged;parametredeki tüm güncellenmiş değerler entityEntrygeri
dönüyor

3

EntityFramework Core 2.x'te şunlara gerek yoktur Attach:

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

Bunu SQL Server'da denedim ve profil oluşturma:

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

Find, önceden yüklenmiş varlıkların SELECT tetiklememesini ve ayrıca gerekirse varlığı otomatik olarak (dokümanlardan) eklemesini sağlar:

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

Birkaç öneriyi birleştirerek aşağıdakileri öneriyorum:

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

tarafından çağrıldı

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

Veya tarafından

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

Veya tarafından

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

Bu yöntemi diğer sınıflar için nasıl kullanılabilir hale getirebiliriz, uzantı yöntemi gibi olabilir mi?
Velkumar

içinde bu .NET CORE öğretici onlar MVC güncelleme belirli özelliklerine (yeni) EF Çekirdek kullanarak en iyi uygulamaları göstermektedir. 'TryUpdateModelAsync' araması yapın.
Guy

1
@Guy Awesome. Yine de, Microsoft'un "en iyi uygulaması", araçlarının oluşturduklarından başka bir şey
yapmaktır

Bu iyi bir çözüm.
Timothy Macharia

1

Kullandığım ValueInjecteraşağıdaki kullanarak veritabanı Şahsiyeti meydana Modeli Bağlama enjekte etmek 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();
}

Sunucudan boşsa Özellikler'i güncellemeyen özel kuralın kullanımına dikkat edin.

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

Kullanımı:

target.InjectFrom<NoNullsInjection>(source);

Değer Enjektörü V2

Yukarı Bak Bu yanıtı

Uyarı

Özelliği kasıtlı olarak null için temizlenmiş olup olmadığını bilmiyorsunuz VEYA sadece herhangi bir değeri yoktu. Başka bir deyişle, özellik değeri yalnızca başka bir değerle değiştirilebilir, ancak silinemez.


0

Ben de aynısını arıyordum ve sonunda çözümü buldum

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

inan bana bir cazibe gibi benim için çalışıyor.


0

Bunu kullandığım, özel InjectNonNull (obj dest, obj src) kullanarak tamamen esnek kılıyor

[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
Bu yeni bir satır ekleyecektir. Soru, mevcut olanın nasıl güncelleneceği.
Edward Brey
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.