エンティティフレームワークを使用して1つのフィールドのみを更新する方法は?
-
30-09-2019 - |
質問
これがテーブルです
ユーザー
UserId
UserName
Password
EmailAddress
とコード..
public void ChangePassword(int userId, string password){
//code to update the password..
}
解決
LADISLAVの回答は、DBContext(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();
}
}
他のヒント
この方法で更新する必要があるプロパティを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();
}
}
基本的に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モデルのストアドプロシージャの関数インポートを作成し、この関数を呼び出す
私はこれを使用しています:
実在物:
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();
この問題の解決策を探している間、私は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番目のパラメーターとして関数の式を式にします。これにより、更新するプロパティをラムダ式で指定することにより、この方法を使用できます。"
...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();
}
エンティティフレームワークコアでは、 Attach
エントリを返すので、必要なのは次のとおりです。
var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
私はここでゲームに遅れていますが、これが私がそれをしている方法です。これはAnを生成します 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;
}
これは、あなたが望んでいれば、試してみることができますが、このシナリオの例外について知っていると私は個人的に発信者が好きです。
それはこのようなもので呼ばれます(私にとっては、これはASP.NET Web APIを介していました):
if (!session.Update(franchiseViewModel.Franchise, new[]
{
"Name",
"StartDate"
}))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
これは古いスレッドであることは知っていますが、同様のソリューションも探していて、 @doku-sosのソリューションを使用することにしました。 @imran rizviが尋ねた質問に答えるためにコメントしています。同様の実装を示す @doku-soリンクに従いました。 @imran Rizviの質問は、提供されたソリューションを使用してエラーが発生しているということでした。 @doku-soのソリューションに行った小さな変更を提供したかったのですが、他の誰かがこの投稿に出くわし、 @doku-soのソリューションを使用することにした場合に備えてこのエラーを修正しました。
問題は、更新方法の2番目の引数です。
public int Update(T entity, Expression<Func<T, object>>[] properties).
提供された構文を使用してこの方法を呼び出すために...
Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
そのように、2番目のアリューガメントの前に「パラメージ」キーワードを追加する必要があります。
public int Update(T entity, params Expression<Func<T, object>>[] properties)
または、メソッド署名を変更したくない場合は、更新メソッドを呼び出すには、 'を追加する必要があります。新着'キーワード、配列のサイズを指定し、最後に各プロパティのコレクションオブジェクト初期化の構文を使用して、以下に示すように更新します。
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のソリューションをそのまま使用することはできません。この投稿とリンクされた投稿の両方で、使用エラーの質問に誰も答えませんでした。
エンティティフレームワークは、dbcontextを介してデータベースから照会したオブジェクトの変更を追跡します。たとえば、dbcontextインスタンス名がdbcontextです
public void ChangePassword(int userId, string password){
var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
user.password = password;
dbContext.SaveChanges();
}
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
既にロードされたエンティティが選択をトリガーせず、必要に応じて(ドキュメントから)エンティティを自動的に添付することを確認することを検索します。
/// 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.
私が使う 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);
Value Injecter V2
見上げる この答え
警告
プロパティが意図的にnullにクリアされているのか、それとも価値がなかったのかはわかりません。言い換えれば、プロパティ値は別の値にのみ置き換えることができますが、クリアされていません。
私は同じを探していました、そして最後に私は解決策を見つけました
using (CString conn = new CString())
{
USER user = conn.USERs.Find(CMN.CurrentUser.ID);
user.PASSWORD = txtPass.Text;
conn.SaveChanges();
}
それは魅力のように私のために働くと私を信じてください。
いくつかの提案を組み合わせて、私は以下を提案します:
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;
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;
}
}
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();
}
}