Entity Framework 5レコードの更新


870

私はASP.NET MVC3環境のEntity Framework 5内でレコードを編集/更新するさまざまな方法を調査してきましたが、これまでのところ、必要なすべてのボックスにチェックを付ける方法はありません。その理由を説明します。

私は長所と短所に言及する3つの方法を見つけました。

方法1-元のレコードを読み込み、各プロパティを更新する

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    original.BusinessEntityId = updatedUser.BusinessEntityId;
    original.Email = updatedUser.Email;
    original.EmployeeId = updatedUser.EmployeeId;
    original.Forename = updatedUser.Forename;
    original.Surname = updatedUser.Surname;
    original.Telephone = updatedUser.Telephone;
    original.Title = updatedUser.Title;
    original.Fax = updatedUser.Fax;
    original.ASPNetUserId = updatedUser.ASPNetUserId;
    db.SaveChanges();
}    

長所

  • 変更するプロパティを指定できます
  • ビューはすべてのプロパティを含む必要はありません

短所

  • データベースを2回クエリしてオリジナルをロードし、更新する

方法2-元のレコードを読み込み、変更された値を設定する

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();
}

長所

  • 変更されたプロパティのみがデータベースに送信されます

短所

  • ビューにはすべてのプロパティを含める必要があります
  • データベースを2回クエリしてオリジナルをロードし、更新する

方法3-更新されたレコードを添付し、状態をEntityState.Modifiedに設定します

db.Users.Attach(updatedUser);
db.Entry(updatedUser).State = EntityState.Modified;
db.SaveChanges();

長所

  • 更新するデータベースに対する1 xクエリ

短所

  • 変更するプロパティを指定できません
  • ビューにはすべてのプロパティが含まれている必要があります

質問

皆さんへの私の質問。この一連の目標を達成できるクリーンな方法はありますか?

  • 変更するプロパティを指定できます
  • ビューにすべてのプロパティ(パスワードなど)を含める必要はありません。
  • 更新するデータベースに対する1 xクエリ

これはごくわずかなことですが、簡単な解決策がない可能性があることを理解しています。そうでない場合は1つが優先されます;-)


13
ViewModelsと優れたマッピングエンジンを使用しますか?「更新するプロパティ」のみを取得して、ビューにデータを入力します(その後更新します)。更新のための2つのクエリはまだあります(オリジナルを取得して更新する)が、これを「Con」とは呼びません。それがあなたの唯一のパフォーマンス問題であるなら、あなたは幸せな人です;)
RaphaëlAlthaus

@RaphaëlAlthausに感謝します、非常に有効なポイントです。これは可能ですが、いくつかのテーブルに対してCRUD操作を作成する必要があるため、モデルを直接操作して、各モデルにn-1 ViewModelを作成する手間を省く方法を探しています。
Stokedout 2013年

3
さて、私の現在のプロジェクト(多くのエンティティも)では、ViewModelsでの作業に時間を費やすことになると考えて、Modelsでの作業から始めました。これからViewModelに移動します。(無視できないほどの)インフラストラクチャの作業が開始されたので、保守がはるかに明確になり、保守しやすくなりました。そして、(そのような悪質な「非表示フィールド」や事柄について恐れる必要はありません)、より安全
ラファエルAlthaus

1
そして、DropDownListsに入力する(ひどい)ViewBagはもうありません(ほとんどすべてのCRU(D)ビューに少なくとも1つのDropDownListがあります...)
RaphaëlAlthaus

私はあなたが正しいと思う、ViewModelsを見落とそうとする私の悪いこと。はい、ViewBagは時々少し汚れているようです。私は通常、Dino Espositoのブログに従ってさらに一歩進んで、InputModelsも作成しています。モデルあたり2つの追加モデルを意味します-doh ;-)
Stokedout 2013年

回答:


681

あなたは探している:

db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();

59
こんにちは@Ladislav Mrnka、すべてのプロパティを一度に更新したい場合、以下のコードを使用できますか?db.Departments.Attach(department); db.Entry(department).State = EntityState.Modified; db.SaveChanges();
Foyzul Karim 2013

23
@フォイサル:はい、できます。
Ladislav Mrnka 2013年

5
このアプローチの問題の1つは、深刻なPITAであるdb.Entry()をモックできないことです。EFは他の場所でかなり良いモックストーリーを持っています-(私が知る限り)彼らがここに持っていないことはかなり迷惑です。
ケン・スミス

23
@Foysal Doing context.Entry(entity).State = EntityState.Modifiedだけで、アタッチを実行する必要はありません。変更されたときに自動的に添付されます...
HelloWorld

4
@ Sandman4は、他のすべてのプロパティが存在し、現在の値に設定される必要があることを意味します。一部のアプリケーション設計では、これは現実的ではありません。
Dan Esparza 14年

176

私は受け入れられた答えが本当に好きです。これに取り組む別の方法もあると思います。ビューに含めたくないプロパティの非常に短いリストがあるとしましょう。そのため、エンティティを更新すると、それらは省略されます。これら2つのフィールドがパスワードとSSNであるとしましょう。

db.Users.Attach(updatedUser);

var entry = db.Entry(updatedUser);
entry.State = EntityState.Modified;

entry.Property(e => e.Password).IsModified = false;
entry.Property(e => e.SSN).IsModified = false;   

db.SaveChanges();   

この例では、UsersテーブルとViewに新しいフィールドを追加した後、基本的にビジネスロジックをそのままにしておくことができます。


それでも、IsModifiedをfalseに設定しても、SSNプロパティの値を指定しないと、モデルルールに照らしてプロパティが検証されるため、エラーが発生します。したがって、プロパティがNOT NULLとしてマークされている場合、null以外の値を設定しないと失敗します。
RolandoCC 2014

これらのフィールドはフォームに含まれないため、エラーは発生しません。あなたは間違いなく更新しないフィールドを省き、それを添付して戻されたフォームを使用してデータベースからエントリーを取得し、それらのフィールドが変更されていないことをエントリーに伝えます。モデルの検証は、コンテキストではなくModelStateで制御されます。この例では、既存のユーザー、つまり「updatedUser」を参照しています。SSNが必須フィールドである場合、最初に作成されたときにそこにあったはずです。
smd 2014

4
私が正しく理解していれば、 "updatedUser"はFirstOrDefault()などが既に設定されているオブジェクトのインスタンスなので、変更したプロパティのみを更新し、その他をISModified = falseに設定しています。これは正常に動作します。しかし、私がしようとしているのは、更新前にFirstOrDefault()を作成せずに、最初にオブジェクトを設定せずにオブジェクトを更新することです。これは、必要なすべてのフィールドに値を指定しなかった場合にエラーが発生した場合です。これらのプロパティにISModified = falseを設定したとしてもです。entry.Property(e => e.columnA).IsModified = false; この行がないと、ColumnAは失敗します。
RolandoCC 2014年

あなたが説明しているのは、新しいエンティティを作成することです。これは更新にのみ適用されます。
smd 2014年

1
RolandoCC、配置db.Configuration.ValidateOnSaveEnabled = false; db.SaveChanges();の前
Wilky、2014年

28
foreach(PropertyInfo propertyInfo in original.GetType().GetProperties()) {
    if (propertyInfo.GetValue(updatedUser, null) == null)
        propertyInfo.SetValue(updatedUser, propertyInfo.GetValue(original, null), null);
}
db.Entry(original).CurrentValues.SetValues(updatedUser);
db.SaveChanges();

これは本当にいい解決策のようです-混乱や大騒ぎはありません。プロパティを手動で指定する必要はなく、すべてのOPの箇条書きが考慮されます-これに投票がない理由はありますか?
nocarrier 2014

しかし、そうではありません。最大の「短所」の1つであり、データベースに複数のヒットがあります。あなたはまだこの答えでオリジナルをロードする必要があります。
smd 2014

1
@smdなぜデータベースに複数回ヒットするのですか?SetValues()を使用してその効果がない限り、そうなるとは思わないが、それは本当だとは思えない。
国会

@議会私はそれを書いたとき、私は眠っていたに違いないと思います。謝罪。実際の問題は、意図されたnull値を上書きすることです。更新されたユーザーが何かへの参照を失った場合、それをクリアするつもりであったとしても、それを元の値に置き換えるのは適切ではありません。
smd 2015年

22

Scaffoldingによって生成された更新メソッドに類似した追加の更新メソッドをリポジトリの基本クラスに追加しました。オブジェクト全体を「変更済み」に設定する代わりに、一連の個別のプロパティを設定します。(Tはクラスのジェネリックパラメーターです。)

public void Update(T obj, params Expression<Func<T, object>>[] propertiesToUpdate)
{
    Context.Set<T>().Attach(obj);

    foreach (var p in propertiesToUpdate)
    {
        Context.Entry(obj).Property(p).IsModified = true;
    }
}

次に、たとえば次のように呼び出します。

public void UpdatePasswordAndEmail(long userId, string password, string email)
{
    var user = new User {UserId = userId, Password = password, Email = email};

    Update(user, u => u.Password, u => u.Email);

    Save();
}

データベースへの1回の旅行が好きです。ただし、プロパティのセットの繰り返しを避けるために、ビューモデルでこれを行う方がおそらく良いでしょう。ビューモデルバリデーターの検証メッセージをドメインプロジェクトに持ち込まないようにする方法がわからないため、まだそれを行っていません。


Aha ...ビューモデル用の個別のプロジェクトとビューモデルで動作するリポジトリ用の個別のプロジェクト。
Ian Warburton

11
public interface IRepository
{
    void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class;
}

public class Repository : DbContext, IRepository
{
    public void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class
    {
        Set<T>().Attach(obj);
        propertiesToUpdate.ToList().ForEach(p => Entry(obj).Property(p).IsModified = true);
        SaveChanges();
    }
}

なぜDbContext.Attach(obj);だけではないのですか?DbContext.Entry(obj).State = EntityState.Modified;
nelsontruran

これsetは、更新ステートメントの一部を制御します。
Tanveer Badar

4

オプションのリストに追加するだけです。また、データベースからオブジェクトを取得し、Auto Mapperなどの自動マッピングツールを使用して、 変更するレコードの部分を更新することもできます。


3

ユースケースに応じて、上記のすべてのソリューションが適用されます。これは私が通常それを行う方法です:

サーバー側のコード(たとえば、バッチプロセス)の場合、通常はエンティティを読み込んで、動的プロキシを操作します。通常、バッチプロセスでは、サービスの実行時にデータをロードする必要があります。時間を節約するために、findメソッドを使用する代わりに、データをバッチロードしようとしています。プロセスに応じて、オプティミスティックまたはペシミスティック同時実行制御を使用します(プレーンSQLステートメントで一部のレコードをロックする必要がある並列実行シナリオを除いて、常にオプティミスティックを使用しますが、これはまれです)。コードとシナリオによっては、影響をほぼゼロにすることができます。

クライアント側のシナリオでは、いくつかのオプションがあります

  1. ビューモデルを使用します。モデルにはプロパティUpdateStatus(unmodified-inserted-updated-deleted)が必要です。ユーザーのアクション(挿入、更新、削除)に応じて、この列に正しい値を設定するのはクライアントの責任です。サーバーは、データベースに元の値を照会するか、クライアントが変更された行とともに元の値をサーバーに送信する必要があります。サーバーは元の値をアタッチし、各行のUpdateStatus列を使用して、新しい値の処理方法を決定する必要があります。このシナリオでは、常に楽観的同時実行を使用します。これは、挿入-更新-削除ステートメントのみを実行し、選択は実行しませんが、グラフを調べてエンティティを更新するには、いくつかの巧妙なコードが必要になる場合があります(シナリオ-アプリケーションによって異なります)。マッパーは役立ちますが、CRUDロジックを処理しません

  2. (1で説明したように)この複雑さのほとんどを隠すbreeze.jsのようなライブラリを使用して、ユースケースに合わせてみてください。

それが役に立てば幸い

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.