存在する場合は行を更新するか、エンティティフレームワークを使用した挿入ロジック


179

Entity Frameworkを使用して「存在する場合は行を更新」ロジックを実装する最も効率的な方法についての提案はありますか?


2
これは、ストアドプロシージャで、データベースエンジンレベルで実行する必要があります。それ以外の場合は、検出/更新/挿入をトランザクションでラップする必要があります。
スティーブンチョン

1
@Stephen:実際、これは私がやったことです。ありがとう。
ジョナサンウッド

ジョナサン、あなたの質問は私にとってとても役に立ちます。なぜストアドプロシージャに切り替えたのですか?
anar khalilov 2014年

2
@Anar:簡単だったし、ずっと効率的だと思う。
ジョナサンウッド

すべてのテーブルにストアドプロシージャを記述する必要がありますか?
tofutim 2018年

回答:


174

アタッチされたオブジェクト(コンテキストの同じインスタンスから読み込まれたオブジェクト)を使用している場合は、次のように使用できます。

if (context.ObjectStateManager.GetObjectStateEntry(myEntity).State == EntityState.Detached)
{
    context.MyEntities.AddObject(myEntity);
}

// Attached object tracks modifications automatically

context.SaveChanges();

オブジェクトのキーに関する知識を使用できる場合は、次のようなものを使用できます。

if (myEntity.Id != 0)
{
    context.MyEntities.Attach(myEntity);
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
    context.MyEntities.AddObject(myEntity);
}

context.SaveChanges();

Idでオブジェクトの存在を判断できない場合は、ルックアップクエリを除外する必要があります。

var id = myEntity.Id;
if (context.MyEntities.Any(e => e.Id == id))
{
    context.MyEntities.Attach(myEntity);
    context.ObjectStateManager.ChangeObjectState(myEntity, EntityState.Modified);
}
else
{
    context.MyEntities.AddObject(myEntity);
}

context.SaveChanges();

ありがとう。私が必要としているように見えます。しばらく気になっていた質問を1つお願いします。通常、私は自分のコンテキストを短いusingブロックに入れます。コンテキストをしばらくメモリに残しても大丈夫ですか?たとえば、Windowsフォームの有効期間中はどうでしょうか。私は通常、データベースの負荷を最小限に抑えるために、データベースオブジェクトをクリーンアップします。EFコンテキストを破棄するのに問題はありませんか?
ジョナサンウッド

これを確認してください:stackoverflow.com/questions/3653009/…オブジェクトコンテキストは可能な限り短くする必要がありますが、winformsまたはwpfの場合、これはコンテキストがプレゼンターの間存続していることを意味します。リンクされた質問には、Winformsでのnhibernateセッションの使用に関するmsdn記事へのリンクが含まれています。同じアプローチをコンテキストに使用できます。
Ladislav Mrnka、2011

1
しかし、オブジェクトのリストを使用してこれを行う必要がある場合...データベースに同じIDの行のリストがあり、それらが存在する場合は置換するか、存在しない場合は挿入したいのですが、どうすればよいですか?ありがとう!
Phoenix_uy

1
この答えは素晴らしいですが、更新時にこの問題が発生しています。同じキーを持つオブジェクトがObjectStateManagerにすでに存在しています。ObjectStateManagerは、同じキーを持つ複数のオブジェクトを追跡できません。
John Zumbrum、2012年

1
更新を行う前にキーを取得するために、既存のオブジェクトをフェッチするときに少し問題があったようです。そのルックアップオブジェクトをデタッチすることは、最初にそれを修正するのに役立ちました。
John Zumbrum、2012年

33

Entity Framework 4.3以降、AddOrUpdate名前空間にメソッドがありますSystem.Data.Entity.Migrations

public static void AddOrUpdate<TEntity>(
    this IDbSet<TEntity> set,
    params TEntity[] entities
)
where TEntity : class

どのDOC

SaveChangesが呼び出されたときに、キーによってエンティティを追加または更新します。データベース用語の「upsert」操作に相当します。この方法は、マイグレーションを使用してデータをシードするときに役立ちます。


@ Smashing1978コメントに回答するため、@ Colinが提供するリンクから関連する部分を貼り付けます

AddOrUpdateの役割は、開発中にデータをシードするときに重複を作成しないようにすることです。

まず、データベースでクエリを実行して、キー(最初のパラメーター)として指定したものと、AddOrUpdateで指定したマップされた列の値(1つまたは複数)が一致するレコードを探します。したがって、これはマッチングには少し緩いですが、設計時間データのシードには完全に適しています。

さらに重要なことに、一致が見つかった場合、更新はすべてを更新し、AddOrUpdateになかったものはすべて無効にします。

とはいえ、外部サービスからデータをプルし、主キーによって既存の値を挿入または更新している状況(およびコンシューマーのローカルデータは読み取り専用です)AddOrUpdateで、現在6か月以上使用されています。まったく問題ありません。


7
System.Data.Entity.Migrations名前空間には、コードベースの移行とその構成に関連するクラスが含まれています。非移行エンティティAddOrUpdatesのリポジトリでこれを使用してはいけない理由はありますか?
Matt Lengenfelder、2015

10
:AddOrUpdate方法で世話をするthedatafarm.com/data-access/...
コリン・

1
AddOrUpdateは使用すべきでない理由をこの記事では説明michaelgmccarthy.com/2016/08/24/...
NolmëのInformatique

11

呼び出し時に魔法が発生SaveChanges()し、現在に依存しEntityStateます。エンティティにがある場合はEntityState.Addedデータベースに追加され、エンティティがある場合はデータベースでEntityState.Modified更新されます。したがってInsertOrUpdate()、次のようにメソッドを実装できます。

public void InsertOrUpdate(Blog blog) 
{ 
    using (var context = new BloggingContext()) 
    { 
        context.Entry(blog).State = blog.BlogId == 0 ? 
                                   EntityState.Added : 
                                   EntityState.Modified; 

        context.SaveChanges(); 
    } 
}

EntityStateの詳細

Id = 0新しいエンティティであるかどうかを確認できない場合は、Ladislav Mrnkaの回答を確認してください。


8

同じコンテキストを使用していて、エンティティを切り離していないことがわかっている場合は、次のような汎用バージョンを作成できます。

public void InsertOrUpdate<T>(T entity, DbContext db) where T : class
{
    if (db.Entry(entity).State == EntityState.Detached)
        db.Set<T>().Add(entity);

    // If an immediate save is needed, can be slow though
    // if iterating through many entities:
    db.SaveChanges(); 
}

db もちろん、クラスフィールドにすることも、メソッドを静的および拡張にすることもできますが、これは基本です。


4

ラディスラフの答えは近いものでしたが、EF6でこれを機能させるには、いくつかの変更を加える必要がありました(データベースファースト)。on AddOrUpdateメソッドでデータコンテキストを拡張しましたが、これまでのところ、これはデタッチされたオブジェクトでうまく機能しているようです。

using System.Data.Entity;

[....]

public partial class MyDBEntities {

  public void AddOrUpdate(MyDBEntities ctx, DbSet set, Object obj, long ID) {
      if (ID != 0) {
          set.Attach(obj);
          ctx.Entry(obj).State = EntityState.Modified;
      }
      else {
          set.Add(obj);
      }
  }
[....]

AddOrUpdateは、System.Data.Entity.Migrationsの拡張メソッドとしても存在するため、もし私があなたなら、同じメソッド名を自分のメソッドに再利用することは避けたいでしょう。
AFract 2018

2

私の意見では、新しくリリースされたEntityGraphOperations for Entity Framework Code Firstを使用すると、グラフ内のすべてのエンティティの状態を定義するための反復的なコードを作成する手間を省くことができます。私はこの製品の作者です。そして私はそれをgithubcode-project段階的なデモンストレーションを含み、サンプルプロジェクトがダウンロードできるようになっています)nugetで公開しました

それはされます自動的にエンティティの状態を設定しますAddedModified。また、エンティティが存在しない場合は、削除する必要があるエンティティを手動で選択します。

例:

Personオブジェクトを取得したとしましょう。Person多くの電話とドキュメントがあり、配偶者がいる可能性があります。

public class Person
{
     public int Id { get; set; }
     public string FirstName { get; set; }
     public string LastName { get; set; }
     public string MiddleName { get; set; }
     public int Age { get; set; }
     public int DocumentId {get; set;}

     public virtual ICollection<Phone> Phones { get; set; }
     public virtual Document Document { get; set; }
     public virtual PersonSpouse PersonSpouse { get; set; }
}

グラフに含まれるすべてのエンティティの状態を確認したい。

context.InsertOrUpdateGraph(person)
       .After(entity =>
       {
            // Delete missing phones.
            entity.HasCollection(p => p.Phones)
               .DeleteMissingEntities();

            // Delete if spouse is not exist anymore.
            entity.HasNavigationalProperty(m => m.PersonSpouse)
                  .DeleteIfNull();
       });

また、ご存知のとおり、電話エンティティの状態を定義するときに、一意のキープロパティが役割を果たす可能性があります。そのような特別な目的のためにExtendedEntityTypeConfiguration<>、から継承するクラスがありEntityTypeConfiguration<>ます。このような特別な構成を使用する場合は、マッピングクラスをからExtendedEntityTypeConfiguration<>ではなく、から継承する必要がありますEntityTypeConfiguration<>。例えば:

public class PhoneMap: ExtendedEntityTypeConfiguration<Phone>
    {
        public PhoneMap()
        {
             // Primary Key
             this.HasKey(m => m.Id);
              
             // Unique keys
             this.HasUniqueKey(m => new { m.Prefix, m.Digits });
        }
    }

それで全部です。


2

挿入し、両方を更新

public void InsertUpdateData()
{
//Here TestEntities is the class which is given from "Save entity connection setting in web.config"
TestEntities context = new TestEntities();

var query = from data in context.Employee
            orderby data.name
            select data;

foreach (Employee details in query)
{
    if (details.id == 1)
    {
        //Assign the new values to name whose id is 1
        details.name = "Sanjay";
        details. Surname="Desai";
        details.address=" Desiwadi";
    }
    else if(query==null)
    {
        details.name="Sharad";
        details.surname=" Chougale ";
        details.address=" Gargoti";
    }
}

//Save the changes back to database.
context.SaveChanges();
}

私はこのアプローチを使用しましたが、(最初またはデフォルトの後で)(query == null)かどうかをチェックしました
Patrick

2

Anyで既存の行を確認します。

    public static void insertOrUpdateCustomer(Customer customer)
    {
        using (var db = getDb())
        {

            db.Entry(customer).State = !db.Customer.Any(f => f.CustomerId == customer.CustomerId) ? EntityState.Added : EntityState.Modified;
            db.SaveChanges();

        }

    }

1

@LadislavMrnka回答の代替。これはEntity Framework 6.2.0の場合です。

DbSet更新または作成する必要がある特定のアイテムがある場合:

var name = getNameFromService();

var current = _dbContext.Names.Find(name.BusinessSystemId, name.NameNo);
if (current == null)
{
    _dbContext.Names.Add(name);
}
else
{
    _dbContext.Entry(current).CurrentValues.SetValues(name);
}
_dbContext.SaveChanges();

ただし、これはDbSet単一の主キーまたは複合主キーを持つジェネリックにも使用できます。

var allNames = NameApiService.GetAllNames();
GenericAddOrUpdate(allNames, "BusinessSystemId", "NameNo");

public virtual void GenericAddOrUpdate<T>(IEnumerable<T> values, params string[] keyValues) where T : class
{
    foreach (var value in values)
    {
        try
        {
            var keyList = new List<object>();

            //Get key values from T entity based on keyValues property
            foreach (var keyValue in keyValues)
            {
                var propertyInfo = value.GetType().GetProperty(keyValue);
                var propertyValue = propertyInfo.GetValue(value);
                keyList.Add(propertyValue);
            }

            GenericAddOrUpdateDbSet(keyList, value);
            //Only use this when debugging to catch save exceptions
            //_dbContext.SaveChanges();
        }
        catch
        {
            throw;
        }
    }
    _dbContext.SaveChanges();
}

public virtual void GenericAddOrUpdateDbSet<T>(List<object> keyList, T value) where T : class
{
    //Get a DbSet of T type
    var someDbSet = Set(typeof(T));

    //Check if any value exists with the key values
    var current = someDbSet.Find(keyList.ToArray());
    if (current == null)
    {
        someDbSet.Add(value);
    }
    else
    {
        Entry(current).CurrentValues.SetValues(value);
    }
}

-1

修正済み

public static void InsertOrUpdateRange<T, T2>(this T entity, List<T2> updateEntity) 
        where T : class
        where T2 : class
        {
            foreach(var e in updateEntity)
            {
                context.Set<T2>().InsertOrUpdate(e);
            }
        }


        public static void InsertOrUpdate<T, T2>(this T entity, T2 updateEntity) 
        where T : class
        where T2 : class
        {
            if (context.Entry(updateEntity).State == EntityState.Detached)
            {
                if (context.Set<T2>().Any(t => t == updateEntity))
                {
                   context.Set<T2>().Update(updateEntity); 
                }
                else
                {
                    context.Set<T2>().Add(updateEntity);
                }

            }
            context.SaveChanges();
        }

2
別の回答を投稿する代わりに編集を使用してください
Suraj Rao
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.