Entity Frameworkで複数の行を削除するにはどうすればよいですか(foreachなし)


305

Entity Frameworkを使用して、テーブルからいくつかのアイテムを削除しています。外部キー/親オブジェクトがないため、OnDeleteCascadeではこれを処理できません。

今私はこれをやっています:

var widgets = context.Widgets
    .Where(w => w.WidgetId == widgetId);

foreach (Widget widget in widgets)
{
    context.Widgets.DeleteObject(widget);
}
context.SaveChanges();

それは機能しますが、foreachは私を悩ませます。EF4を使用していますが、SQLを実行したくありません。私は何も逃していないことを確認したいだけです-これはそれで十分ですよね?拡張メソッドまたはヘルパーを使用して抽象化できますが、どこかでforeachを実行する予定ですよね?


1
受け入れられた回答を再確認することができます。
エリックJ.

1
パフォーマンスを維持したい場合は、ここで私の答えを確認することをお勧めします。stackoverflow.com
Adi

回答:


49

SQLを実行したくない場合は、ループでDeleteObjectを直接呼び出すのが今日できる最善の方法です。

ただし、ここで説明するアプローチを使用すると、SQLを実行し、拡張メソッドを介してSQLを完全に汎用にすることができます。

その答えは3.5でしたが。4.0の場合、StoreConnectionにドロップダウンする代わりに、新しいExecuteStoreCommand APIを内部で使用するでしょう。


ExecuteStoreCommandは適切な方法ではありません。DeleteAllSubmitはlinq to sqlでは機能しますが、エンティティフレームワークでは機能しません。エンティティフレームワークにも同じオプションが必要です。
Hiral

653

EntityFramework 6では、でこれが少し簡単になりました.RemoveRange()

例:

db.People.RemoveRange(db.People.Where(x => x.State == "CA"));
db.SaveChanges();

31
それがまさに私たちに必要なことです...十分に広い範囲で使用する場合を除いて、メモリ不足の例外が発生します!RemoveRangeの重要なポイントは、処理をデータベースに渡すことだと思いましたが、そうではないようです。
Samer Adra 2014

これは、削除済み状態をすべてのエンティティに設定するよりもWAAAYYYY速いです!
Jerther 2014

54
確かにこの答えは簡単ですが、パフォーマンスの点ではそれほど良くないかもしれません。どうして?これは、foreachループで削除することと同じです。最初にすべての行をフェッチし、次に1つずつ削除します。「DetectChangesが一度呼び出されてからエンティティが削除され、再度呼び出されることはありません」という節約だけが保存されます。同じです。生成されたsqlを確認するためにツールを使用してみてください。
Anshul Nigam 2014

6
十分に広い範囲については、.Take(10000)のようなものを試して、RemoveRange(...)。Count()== 0になるまでループします。–
Eric J.

20
問題は、RemoveRange入力パラメーターがIEnumerableであるため、削除を実行するとすべてのエンティティが列挙され、エンティティごとに1つのDELETEクエリが実行されることです。
bubi

74

これは最高ですよね?拡張メソッドまたはヘルパーを使用して抽象化できますが、どこかでforeachを実行する予定ですよね?

ええ、はい。ただし、2ライナーにすることができます。

context.Widgets.Where(w => w.WidgetId == widgetId)
               .ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();

76
あなたは目的に反するToList()を行っています。それは元のソリューションとどのように違うのですか?
lahsrah、2011年

3
コンテキストオブジェクトにRemoveメソッドしかないので問題があります。
2013

2
100万行(または数百行)が予想される場合、これは明らかに適切なソリューションではありません。ただし、行が数行しかないことが確実にわかっている場合、このソリューションは適切であり、完全に機能します。はい、それはDBへの数回のラウンドトリップを伴いますが、私の意見では、SQLの呼び出しに関連する失われた抽象化は、メリットを直接上回ります。
Yogster

Entity Frameworkは、その名前が示すように、エンティティレベルのデータで最適に機能します。バルクデータ操作は、古き良きストアドプロシージャで処理するのが最適です。パフォーマンスに関しては、これらは群を抜いて最良のオプションであり、ループを必要とするすべてのEFロジックに勝ります。
ペースマン、2015

72
using (var context = new DatabaseEntities())
{
    context.ExecuteStoreCommand("DELETE FROM YOURTABLE WHERE CustomerID = {0}", customerId);
}

しかし、IDのリストを使用してこれをどのように行うことができますか?このソリューションは「リスト」をうまく処理しません。
JesseNewman19 2016

11
@ JesseNewman19すでにIDのリストがある場合は、を使用しWHERE IN ({0})、2番目の引数はにする必要がありますString.Join(",", idList)
Langdon

@Langdonは、SQLにコマンドを送信するため、機能しません。WHEREIN( "1、2、3")。整数のリストではなく文字列を渡したため、データベースはエラーをスローします。
JesseNewman19

LINQでそのようなステートメントを生成したいと思います。私が見つけた最も近いものはlibでした。EntityFramework.Extended
Jaider 2016

を使用している場合は、すでに形成されているSQL文字列を使用してコマンドに渡すString.Join必要がある場合がありstring.Formatます。リストに整数しかない限り、インジェクション攻撃のリスクはありません。この質問を確認してください:配列をストア実行コマンドに渡すにはどうすればよいですか?
アンドリュー、

50

私はそれがかなり遅いことを知っていますが、誰かが簡単な解決策を必要とする場合、それと一緒にwhere句を追加することもできます:

public static void DeleteWhere<T>(this DbContext db, Expression<Func<T, bool>> filter) where T : class
{
    string selectSql = db.Set<T>().Where(filter).ToString();
    string fromWhere = selectSql.Substring(selectSql.IndexOf("FROM"));
    string deleteSql = "DELETE [Extent1] " + fromWhere;
    db.Database.ExecuteSqlCommand(deleteSql);
}

注:MSSQL2008でテスト済みです。

更新:

上記の解決策は、EFがパラメーター付きのSQLステートメントを生成する場合は機能しないため、EF5 の更新を次に示します。

public static void DeleteWhere<T>(this DbContext db, Expression<Func<T, bool>> filter) where T : class
{
    var query = db.Set<T>().Where(filter);

    string selectSql = query.ToString();
    string deleteSql = "DELETE [Extent1] " + selectSql.Substring(selectSql.IndexOf("FROM"));

    var internalQuery = query.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(field => field.Name == "_internalQuery").Select(field => field.GetValue(query)).First();
    var objectQuery = internalQuery.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(field => field.Name == "_objectQuery").Select(field => field.GetValue(internalQuery)).First() as ObjectQuery;
    var parameters = objectQuery.Parameters.Select(p => new SqlParameter(p.Name, p.Value)).ToArray();

    db.Database.ExecuteSqlCommand(deleteSql, parameters);
}

少し反射が必要ですが、うまく機能します。


DbContextとは何ですか?自動生成されたエンティティフレームワークコンテキストを想定していますか?Set <T>というメソッドはありません。
ステルスラビ

@Stealth:ええ、それはあなたのEFデータコンテキストです。私は最初にコードを使用しますが、自動生成されたコンテキストは同じでなければなりません。誤って入力したステートメントで申し訳ありません。それはSet <T>()である必要があります(私の会社はコードを貼り付けることができなかったため、インターネットアクセスを拒否し、手動で入力する必要がありました...)、コードを更新しました:)
Thanh Nguyen

3
これが実際に質問に答える唯一の答えです!他のすべての答えは、信じられないほど、個々のアイテムを1つずつ削除します。
ロックラン

これは最も正しい答えのように感じます。非常に一般的な方法で削除を許可し、C#ではなくデータベースに作業を適切にオフロードします。
JesseNewman19 2016

1
それほど技術的でないプログラマーにとっては、この優れた一般的なソリューションを実装する方法についてもう少し詳しく説明したかったのです。次のコメントに続く...
jdnew18

30

EF5を使用するすべてのユーザーに対して、次の拡張ライブラリを使用できます:https : //github.com/loresoft/EntityFramework.Extended

context.Widgets.Delete(w => w.WidgetId == widgetId);

3
大きなテーブルでパフォーマンスの問題があり、私の状況では使用できません。
トーマス2014年

@Tomas発行されたパフォーマンスの種類は何ですか。問題はどのくらい深刻で、テーブルはどのくらい大きかったですか?他の誰かがそれを確認できますか?
Anestis Kivranoglou 2016

それはそこにある代替品と比べて本当に速い
Jaider

Delete()EF6のエンティティで機能を確認できません。
dotNET

context.Widgets.Where(w => w.WidgetId == widgetId).Delete();EntityFramework.Extendedの新しい方法です
Peter Kerr

11

それを削除するためだけにサーバーから何かをプルバックする必要があるのはまだ狂気のようですが、少なくともIDのみを取得することは、完全なエンティティーをプルダウンするよりもはるかに効率的です。

var ids = from w in context.Widgets where w.WidgetId == widgetId select w.Id;
context.Widgets.RemoveRange(from id in ids.AsEnumerable() select new Widget { Id = id });

注意してください。スタブWidgetオブジェクトには初期化されたIdプロパティしかないため、Entity Frameworkのエンティティ検証に失敗する可能性があります。これを回避する方法は、context.Configuration.ValidateOnSaveEnabled = false(少なくともEF6では)を使用することです。これにより、Entity Framework自体の検証が無効になりますが、データベース自体の検証はもちろん実行されます。
サミーS.

@SammyS。私はそれを経験したことがないので、詳細について話すことはできませんが、EFがとにかく行を削除しているときに検証に悩まされるのは奇妙に思えます。
エドワードブレイ

あなたは絶対的に正しいです。エンティティをロードせずdeleteupdateingするための同様の回避策とを混同しました。
サミーS.

10

EF 6.1

public void DeleteWhere<TEntity>(Expression<Func<TEntity, bool>> predicate = null) 
where TEntity : class
{
    var dbSet = context.Set<TEntity>();
    if (predicate != null)
        dbSet.RemoveRange(dbSet.Where(predicate));
    else
        dbSet.RemoveRange(dbSet);

    context.SaveChanges();
} 

使用法:

// Delete where condition is met.
DeleteWhere<MyEntity>(d => d.Name == "Something");

Or:

// delete all from entity
DeleteWhere<MyEntity>();

7
これは、db.People.RemoveRange(db.People.Where(x => x.State == "CA"));と実質的に同じです。db.SaveChanges(); したがって、パフォーマンスは向上しません。
ReinierDG 2017

4

EF 4.1の場合

var objectContext = (myEntities as IObjectContextAdapter).ObjectContext;
objectContext.ExecuteStoreCommand("delete from [myTable];");

1
これは機能しますが、Entity Frameworkを使用する全体のポイントは、データベースと対話するオブジェクト指向の方法を持っていることです。これはSQLクエリを直接実行しているだけです。
Arturo TorresSánchez15年

4

EntityFramework.ExtendedまたはZ.EntityFramework.Plus.EF6のような拡張ライブラリを使用できます。EF5、6、またはCoreで使用できます。これらのライブラリは、削除または更新する必要があり、LINQを使用する場合に優れたパフォーマンスを発揮します。削除の例(source plus):

ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2)) .Delete();

または(ソース拡張

context.Users.Where(u => u.FirstName == "firstname") .Delete();

これらはネイティブSQLステートメントを使用するため、パフォーマンスは優れています。


バルクSQLオペレーションジェネレーターに600 $ +を支払います。マジ?
nicolay.anykienko 2018

@ nicolay.anykienko私が使用したとき、このライブラリは無料でした。支払いが必要な他の操作があります。支払いが必要かどうかはわかりません
UUHHIVS

3

削除する最も簡単な方法は、ストアドプロシージャを使用することです。名前の変更は正しく処理され、コンパイラエラーが発生するため、動的SQLではなくデータベースプロジェクトのストアドプロシージャを使用します。動的SQLは、削除または名前変更されたテーブルを参照して、ランタイムエラーを引き起こす可能性があります。

この例では、ListとListItemsという2つのテーブルがあります。特定のリストのすべてのListItemを削除する高速な方法が必要です。

CREATE TABLE [act].[Lists]
(
    [Id] INT NOT NULL PRIMARY KEY IDENTITY, 
    [Name] NVARCHAR(50) NOT NULL
)
GO
CREATE UNIQUE INDEX [IU_Name] ON [act].[Lists] ([Name])
GO
CREATE TABLE [act].[ListItems]
(
    [Id] INT NOT NULL IDENTITY, 
    [ListId] INT NOT NULL, 
    [Item] NVARCHAR(100) NOT NULL, 
    CONSTRAINT PK_ListItems_Id PRIMARY KEY NONCLUSTERED (Id),
    CONSTRAINT [FK_ListItems_Lists] FOREIGN KEY ([ListId]) REFERENCES [act].[Lists]([Id]) ON DELETE CASCADE
)
go
CREATE UNIQUE CLUSTERED INDEX IX_ListItems_Item 
ON [act].[ListItems] ([ListId], [Item]); 
GO

CREATE PROCEDURE [act].[DeleteAllItemsInList]
    @listId int
AS
    DELETE FROM act.ListItems where ListId = @listId
RETURN 0

アイテムを削除し、拡張機能を使用してエンティティフレームワークを更新する興味深い部分です。

public static class ListExtension
{
    public static void DeleteAllListItems(this List list, ActDbContext db)
    {
        if (list.Id > 0)
        {
            var listIdParameter = new SqlParameter("ListId", list.Id);
            db.Database.ExecuteSqlCommand("[act].[DeleteAllItemsInList] @ListId", listIdParameter);
        }
        foreach (var listItem in list.ListItems.ToList())
        {
            db.Entry(listItem).State = EntityState.Detached;
        }
    }
}

メインコードは次のように使用できます

[TestMethod]
public void DeleteAllItemsInListAfterSavingToDatabase()
{
    using (var db = new ActDbContext())
    {
        var listName = "TestList";
        // Clean up
        var listInDb = db.Lists.Where(r => r.Name == listName).FirstOrDefault();
        if (listInDb != null)
        {
            db.Lists.Remove(listInDb);
            db.SaveChanges();
        }

        // Test
        var list = new List() { Name = listName };
        list.ListItems.Add(new ListItem() { Item = "Item 1" });
        list.ListItems.Add(new ListItem() { Item = "Item 2" });
        db.Lists.Add(list);
        db.SaveChanges();
        listInDb = db.Lists.Find(list.Id);
        Assert.AreEqual(2, list.ListItems.Count);
        list.DeleteAllListItems(db);
        db.SaveChanges();
        listInDb = db.Lists.Find(list.Id);
        Assert.AreEqual(0, list.ListItems.Count);
    }
}

ストアドプロシージャを使用し、それを使用法コードを使用して拡張機能として実装する良い例をありがとう。
グレン・ガーソン、2015

3

テーブルのすべての行を削除する場合は、sqlコマンドを実行できます

using (var context = new DataDb())
{
     context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");
}

TRUNCATE TABLE(Transact-SQL)個々の行の削除をログに記録せずに、テーブルからすべての行を削除します。TRUNCATE TABLEは、WHERE句がないDELETEステートメントに似ています。ただし、TRUNCATE TABLEの方が高速で、システムおよびトランザクションログリソースの使用量も少なくなります。


3
またtruncate table、FOREIGN KEY制約によって参照されるテーブルでは実行できないことにも言及する必要があります。(それ自体を参照する外部キーを持つテーブルを切り捨てることができます。)MSDNドキュメント
ブロードバンド

2

UUHHIVSはバッチ削除のための非常にエレガントで高速な方法ですが、注意して使用する必要があります。

  • トランザクションの自動生成:そのクエリはトランザクションに含まれます
  • データベースコンテキストの独立性:その実行は何の関係もありません context.SaveChanges()

これらの問題は、トランザクションを制御することで回避できます。次のコードは、トランザクション方式で一括削除と一括挿入を行う方法を示しています。

var repo = DataAccess.EntityRepository;
var existingData = repo.All.Where(x => x.ParentId == parentId);  

TransactionScope scope = null;
try
{
    // this starts the outer transaction 
    using (scope = new TransactionScope(TransactionScopeOption.Required))
    {
        // this starts and commits an inner transaction
        existingData.Delete();

        // var toInsert = ... 

        // this relies on EntityFramework.BulkInsert library
        repo.BulkInsert(toInsert);

        // any other context changes can be performed

        // this starts and commit an inner transaction
        DataAccess.SaveChanges();

        // this commit the outer transaction
        scope.Complete();
    }
}
catch (Exception exc)
{
    // this also rollbacks any pending transactions
    scope?.Dispose();
}

2

エンティティフレームワークコア

3.1 3.0 2.2 2.1 2.0 1.1 1.0

using (YourContext context = new YourContext ())
{
    var widgets = context.Widgets.Where(w => w.WidgetId == widgetId);
    context.Widgets.RemoveRange(widgets);
    context.SaveChanges();
}

まとめ

指定されたエンティティのコレクションをセットの基礎となるコンテキストから削除します。各エンティティはDeleted状態になり、SaveChangesが呼び出されたときにデータベースから削除されます。

備考

System.Data.Entity.Infrastructure.DbContextConfiguration.AutoDetectChangesEnabledがtrue(デフォルト)に設定されている場合、DetectChangesが呼び出されてからエンティティが削除され、再度呼び出されることはありません。つまり、状況によっては、RemoveRangeを複数回呼び出した場合よりも、RemoveRangeの方がパフォーマンスが大幅に向上する場合があります。「追加済み」状態のコンテキストにエンティティが存在する場合、このメソッドにより、エンティティがコンテキストから切り離されることに注意してください。これは、追加されたエンティティはデータベースに存在しないと想定されているため、削除しようとしても意味がありません。


1

次のようにSQLクエリを直接実行できます。

    private int DeleteData()
{
    using (var ctx = new MyEntities(this.ConnectionString))
    {
        if (ctx != null)
        {

            //Delete command
            return ctx.ExecuteStoreCommand("DELETE FROM ALARM WHERE AlarmID > 100");

        }
    }
    return 0;
}

選択のために使用することがあります

using (var context = new MyContext()) 
{ 
    var blogs = context.MyTable.SqlQuery("SELECT * FROM dbo.MyTable").ToList(); 
}

EFが削除条件のマッピングを適切にサポートしていないことを考えると、これはおそらくジョブを完了するための最善の策です。
トニーO'Hagan

1

DeleteAllOnSubmit()メソッドを使用して、varではなく汎用リストで結果を渡すこともできます。このようにして、foreachは1行のコードに削減されます。

List<Widgets> widgetList = context.Widgets
              .Where(w => w.WidgetId == widgetId).ToList<Widgets>();

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

それでもおそらく内部的にループを使用します。


3
a varが何かを誤解しているようです。
freedomn-m 2017

1

Thanhの答えは私にとって最も効果的でした。1回のサーバー旅行ですべてのレコードを削除しました。私は実際に拡張メソッドを呼び出すのに苦労したので、私と共有すると思いました(EF 6):

MVCプロジェクトのヘルパークラスに拡張メソッドを追加し、名前を「RemoveWhere」に変更しました。コントローラにdbContextを挿入しますが、を実行することもできますusing

// make a list of items to delete or just use conditionals against fields
var idsToFilter = dbContext.Products
    .Where(p => p.IsExpired)
    .Select(p => p.ProductId)
    .ToList();

// build the expression
Expression<Func<Product, bool>> deleteList = 
    (a) => idsToFilter.Contains(a.ProductId);

// Run the extension method (make sure you have `using namespace` at the top)
dbContext.RemoveWhere(deleteList);

これにより、グループの単一の削除ステートメントが生成されました。


0

EF 6。=>

var assignmentAddedContent = dbHazirBot.tbl_AssignmentAddedContent.Where(a =>
a.HazirBot_CategoryAssignmentID == categoryAssignment.HazirBot_CategoryAssignmentID);
dbHazirBot.tbl_AssignmentAddedContent.RemoveRange(assignmentAddedContent);
dbHazirBot.SaveChanges();

0

ベスト: in EF6 => .RemoveRange()

例:

db.Table.RemoveRange(db.Table.Where(x => Field == "Something"));

14
これはカイルの答えとどう違うのですか?

-1

動作する「お気に入りのコード」の回答を参照してください

これが私がそれをどのように使用したかです:

     // Delete all rows from the WebLog table via the EF database context object
    // using a where clause that returns an IEnumerable typed list WebLog class 
    public IEnumerable<WebLog> DeleteAllWebLogEntries()
    {
        IEnumerable<WebLog> myEntities = context.WebLog.Where(e => e.WebLog_ID > 0);
        context.WebLog.RemoveRange(myEntities);
        context.SaveChanges();

        return myEntities;
    }

1
あなたの答えはuser1308743の答えとどのように異なりますか?
セルゲイベレゾフスキー2014

私は単に実例を共有していました。私がここにたどり着く手助けをするために何ができるか。
Brian Quinn

-3

EF 6.2ではこれは完全に機能し、エンティティを最初にロードせずに削除をデータベースに直接送信します。

context.Widgets.Where(predicate).Delete();

固定述語を使用すると、非常に簡単です。

context.Widgets.Where(w => w.WidgetId == widgetId).Delete();

そして、動的な述語が必要な場合は、LINQKit(Nugetパッケージが利用可能)を見てください。私の場合、次のようなものがうまく機能します。

Expression<Func<Widget, bool>> predicate = PredicateBuilder.New<Widget>(x => x.UserID == userID);
if (somePropertyValue != null)
{
    predicate = predicate.And(w => w.SomeProperty == somePropertyValue);
}
context.Widgets.Where(predicate).Delete();

1
Raw EF 6.2では、これは不可能です。たぶんあなたは使っZ.EntityFramework.Plusているか似たようなものですか?(entityframework.net/batch-delete
サミーS.

最初のものは生のEF 6.2で、機能します。2つ目は、前述したように、LINQKitを使用する方法です。
ウラジミール

1
うーん、この方法が見つかりません。このメソッドが存在するクラスと名前空間を確認できますか?
サミーS.

第三に(Delete()メソッドは本質的に存在しません)。
合計なし
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.