web-dev-qa-db-ja.com

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

これが表

ユーザー

UserId
UserName
Password
EmailAddress

そしてコード.

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

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

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

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

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

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

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

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

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

私はこれを使用しています:

エンティティ:

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

この問題の解決策を探している間に、 Patrick Desjardinsのブログ で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();
}
8
Doku-so

私はここでゲームに遅れていますが、これは私がそれをしている方法です、私は満足した解決策を探してしばらく過ごしました。これは、変更されるフィールドに対してのみ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));
6
GONeale

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

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

私はこれが古いスレッドであることを知っていますが、同様のソリューションを探していたので、@ Doku-soが提供するソリューションを使用することにしました。 @Imran Rizviが尋ねた質問に答えるためにコメントしています。同様の実装を示す@ Doku-soリンクをたどりました。 @Imran Rizviの質問は、提供されたソリューション「Lambda式を型 'Expression> []に変換できません」はデリゲート型ではないため、エラーが発生することでした。 @ 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のソリューションをそのまま使用することはできず、この投稿とリンクされた投稿の両方で使用エラーの質問に誰も答えませんでした。

3
null

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

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

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
Alexei

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

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

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

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 ORに意図的にクリアされているかどうかはわかりません。値がありません。言い換えると、プロパティ値は別の値にのみ置き換えることができ、クリアすることはできません。

1
Korayem

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

    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;
0
Guy