Entity Frameworkを使用して「存在する場合は行を更新」ロジックを実装する最も効率的な方法についての提案はありますか?
Entity Frameworkを使用して「存在する場合は行を更新」ロジックを実装する最も効率的な方法についての提案はありますか?
回答:
アタッチされたオブジェクト(コンテキストの同じインスタンスから読み込まれたオブジェクト)を使用している場合は、次のように使用できます。
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();
using
ブロックに入れます。コンテキストをしばらくメモリに残しても大丈夫ですか?たとえば、Windowsフォームの有効期間中はどうでしょうか。私は通常、データベースの負荷を最小限に抑えるために、データベースオブジェクトをクリーンアップします。EFコンテキストを破棄するのに問題はありませんか?
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か月以上使用されています。まったく問題ありません。
呼び出し時に魔法が発生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();
}
}
Id = 0
新しいエンティティであるかどうかを確認できない場合は、Ladislav Mrnkaの回答を確認してください。
同じコンテキストを使用していて、エンティティを切り離していないことがわかっている場合は、次のような汎用バージョンを作成できます。
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
もちろん、クラスフィールドにすることも、メソッドを静的および拡張にすることもできますが、これは基本です。
ラディスラフの答えは近いものでしたが、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);
}
}
[....]
私の意見では、新しくリリースされたEntityGraphOperations for Entity Framework Code Firstを使用すると、グラフ内のすべてのエンティティの状態を定義するための反復的なコードを作成する手間を省くことができます。私はこの製品の作者です。そして私はそれをgithub、code-project(段階的なデモンストレーションを含み、サンプルプロジェクトがダウンロードできるようになっています)とnugetで公開しました。
それはされます自動的にエンティティの状態を設定しますAdded
かModified
。また、エンティティが存在しない場合は、削除する必要があるエンティティを手動で選択します。
例:
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 });
}
}
それで全部です。
挿入し、両方を更新
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();
}
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();
}
}
@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);
}
}
修正済み
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();
}