Entity Frameworkを使用して1つのフィールドのみを更新する方法


188

こちらがテーブルです

ユーザー

UserId
UserName
Password
EmailAddress

とコード。

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

26
とはPassword、ハッシュ化されたパスワードを意味しますよね?:-)
Edward Brey 2016年

回答:


368

DbContextを使用するように更新されたLadislavの回答(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
このコードを機能させるには、db.Configuration.ValidateOnSaveEnabled = false;を追加する必要がありました。db.SaveChanges()の前?
ジェイクドリュー

3
どの名前空間を含めて使用するdb.Entry(user).Property(x => x.Password).IsModified = true;db.Entry(user).Property("Password").IsModified = true;
Johan

5
この方法では、テーブルにタイムスタンプフィールドがある場合にOptimisticConcurencyExceptionがスローされます。
Maksim Vi。

9
私はあなたが使用している場合ということに言及する価値があると思うdb.Configuration.ValidateOnSaveEnabled = false;あなたが更新しているフィールドを検証しておきたいかもしれません:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Ziul

2
更新中に提供しない必須フィールドがテーブルにある場合は、ValidateOnSaveEnabledをfalseに設定する必要があります
Sal

54

この方法で更新する必要があるプロパティを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();
  }
}

ObjectStateManagerはDBContextでは使用できません
LoxLox

16

基本的に2つのオプションがあります。

  • EFの方法で最後まで行ってください。その場合は、
    • 指定されたものに基づいてオブジェクトをロードしますuserId-オブジェクト全体がロードされます
    • passwordフィールドを更新する
    • コンテキストの.SaveChanges()メソッドを使用してオブジェクトを保存します

この場合、これを詳細に処理する方法はEF次第です。私はこれをテストしました。オブジェクトの単一のフィールドのみを変更する場合、EFが作成するものは、手動で作成したものとほぼ同じです。

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

そのため、EFはどの列が実際に変更されたかを把握するのに十分スマートであり、実際に必要な更新だけを処理するT-SQLステートメントを作成します。

  • T-SQLコードで必要なものを正確に実行するストアドプロシージャを定義し(指定されたPassword列だけを更新し、UserIdそれ以外は何もしない-基本的には実行UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserIdします)、EFモデルでそのストアドプロシージャの関数インポートを作成し、これを呼び出します上記の手順を実行する代わりに機能する

1
@ marc-s実際には、オブジェクト全体をロードする必要はありません!
Arvand 2017

13

Entity Framework CoreではAttachエントリを返すため、必要なのは次のとおりです。

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

12

私はこれを使っています:

エンティティ:

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

1
これを実行するとエンティティ検証エラーが発生しますが、見た目はかっこいいです。
devlord 2013

この方法では機能しません!!!:多分あなたはそれを使う方法についてもっと詳細を与える必要があります!!! -これはエラーです:「タイプ「Domain.Job」のエンティティをアタッチできませんでした。同じタイプの別のエンティティがすでに同じ主キー値を持っているためです。これは、「アタッチ」メソッドを使用するか、エンティティの状態を設定するときに発生する可能性があります。グラフ内のエンティティにキー値の競合がある場合、「変更なし」または「変更」に変更します。これは、一部のエンティティが新しく、まだデータベース生成のキー値を受け取っていないことが原因である可能性があります。」
Lucian Bumb

パーフェクト!私の答えをチェックして、あらゆるモデルの柔軟なアプローチを確認してください!
kryp

10

この問題の解決策を探しているときに、パトリックデジャルダンのブログで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();
}

ご覧のとおり、2番目のパラメーターとして関数の式を受け取ります。これにより、更新するプロパティをLambda式で指定することにより、このメソッドを使用できるようになります。

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

(多少似たソリューションもここにあります:https : //stackoverflow.com/a/5749469/2115384

自分のコード現在使用しているメソッドは、タイプの(Linq)式も処理できるように拡張されていますExpressionType.Convertこれは、私の場合、たとえば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();
}

1
これを使用すると、次のエラーが発生します。ラムダ式をタイプ 'Expression <Func <RequestDetail、object >> []'に変換できません。デリゲートタイプではないためです
Imran Rizvi

@ImranRizvi、あなたは単にパラメータを更新する必要があります。public int型の更新(Tエンティティ、paramsは式<のFunc <T、オブジェクト>> []プロパティ)NOTE表現の前にキーワードのparams
dalcam

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

2
だからあなたのより良い解決策はエリザとは何ですか?(ASP.NET MVCのUpdateModelコマンドに必要なホワイトリストのように)更新を許可するプロパティを明示的に指定する必要があります。これにより、ハッカーフォームインジェクションが発生せず、更新が許可されていないフィールドを更新できなくなります。ただし、誰かが文字列配列をある種のラムダ式パラメーターに変換し、Update<T>
それで


1
@Elisa string []の代わりにFunc <T、List <object >>を使用することで改善できます
Spongebob Comrade

でも、ゲーム後半に、そしておそらくこれははるかに最近の構文ですが、var entity=_context.Set<T>().Attach(item);続くentity.Property(propertyName).IsModified = true;動作するはずループインチ
Auspex 2018年

4

エンティティフレームワークは、DbContextを介してデータベースからクエリしたオブジェクトの変更を追跡します。たとえば、DbContextインスタンス名がdbContextである場合

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

この場合、ビューはどのように見えるでしょうか?
エマヌエラコルタ2017年

変更されたパスワードでユーザーオブジェクト全体を保存するため、これは誤りです。
18年

それは本当ですが、Userオブジェクトの残りの部分は以前のコンテキストと同じです。異なる可能性があるのはパスワードだけなので、基本的にはパスワードを更新するだけです。
Tomislav3008

3

私はこれが古いスレッドであることを知っていますが、同様の解決策も探していましたが、@ Doku-soが提供する解決策を採用することにしました。@Imran Rizviの質問に答えるためにコメントしています。同様の実装を示す@ Doku-soリンクをたどりました。@Imran Rizviの質問は、提供されたソリューションを使用してエラーが発生していたことです。@ Doku-soのソリューションに小さな変更を加えて、他の誰かがこの投稿に遭遇し、@ Doku-soのソリューションを使用することにした場合に備えて、このエラーを修正したいと思いました。

問題は、Updateメソッドの2番目の引数です。

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

提供された構文を使用してこのメ​​ソッドを呼び出すには...

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

そのため、2番目のラゲットの前に「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のソリューションをそのまま使用することはできず、この投稿とリンクされた投稿の両方で誰も使用エラーの質問に答えていません。


プログラムが行を渡しentityEntry.State = EntityState.Unchanged;てパラメーターのすべての更新された値をentityEntry元に戻すときに、私はあなたの解決策に取り組んでいたので、変更は保存されません、あなたはそれを手伝ってくれますか、ありがとう
sairfan

2

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

Findは、既にロードされているエンティティが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.

1

いくつかの提案を組み合わせて、以下を提案します。

    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;

このメソッドを他のクラスで利用できるようにする方法は、拡張メソッドのようなものですか?
Velkumar 2016

、この.NET COREは、チュートリアル、彼らはMVCで特定のプロパティを更新する(新しい)EFコアを使用してのベストプラクティスを示しています。「TryUpdateModelAsync」を探します。
Guy

1
@ガイ素晴らしい。けれども、...もっとマイクロソフトの「ベストプラクティス」はどのような彼らのツールのビルド以外の何かをすることで、一度
Auspexの

これは良い解決策です。
Timothy Macharia

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

バリューインジェクターV2

調べる この回答を

警告

プロパティが意図的にnullにクリアされているかどうか、または値がなかったかどうかはわかりません。つまり、プロパティ値は別の値でのみ置換でき、クリアすることはできません。


0

私は同じものを探していましたが、最終的に解決策を見つけました

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

それが魅力のように私のために働くと私を信じてください。


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

-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
これにより、新しい行が追加されます。問題は、既存のものを更新する方法です。
エドワードブレイ2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.