IT

Entity Framework에서 여러 행을 삭제하는 방법 (foreach 제외)

lottoking 2020. 3. 19. 08:27
반응형

Entity Framework에서 여러 행을 삭제하는 방법 (foreach 제외)


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를 수행 할 것입니까?


루프에서 DeleteObject를 직접 호출하여 SQL을 실행하지 않으려면 오늘 최선을 다하십시오.

그러나 여기에 설명 된 접근 방식을 사용하여 SQL을 실행하고 확장 방법을 통해 완전히 범용으로 만들 수 있습니다 .

그 대답은 3.5였습니다. 4.0의 경우 아마도 StoreConnection으로 드롭 다운하는 대신 새로운 ExecuteStoreCommand API를 사용합니다.


EntityFramework 6을 사용하면이 작업을보다 쉽게 ​​수행 할 수 있습니다 .RemoveRange().

예:

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

이것이 얻는 것만 큼 좋습니다. 확장 방법이나 도우미로 추상화 할 수는 있지만 어딘가에서 foreach를 수행 할 것입니까?

네, 두 개의 라이너로 만들 수 있다는 점을 제외하고는 :

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

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

나는 그것이 늦다는 것을 알고 있지만 누군가가 간단한 해결책이 필요한 경우 멋진 점은 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가 parameters로 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);
}

약간의 반사가 필요하지만 잘 작동합니다.


EF5를 사용하는 사람은 다음 확장 라이브러리를 사용할 수 있습니다. https://github.com/loresoft/EntityFramework.Extended

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

서버를 삭제하기 위해 서버에서 무언가를 가져와야하는 것은 여전히 ​​미치게 보이지만 적어도 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 });

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>();

EF 4.1의 경우

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

삭제하는 가장 빠른 방법은 저장 프로 시저를 사용하는 것입니다. 이름 바꾸기가 올바르게 처리되고 컴파일러 오류가 있기 때문에 동적 SQL보다 데이터베이스 프로젝트에서 저장 프로 시저를 선호합니다. 동적 SQL은 삭제되거나 이름이 바뀐 테이블을 참조하여 런타임 오류를 일으킬 수 있습니다.

이 예제에서는 두 개의 List 및 ListItems 테이블이 있습니다. 주어진 목록의 모든 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);
    }
}

테이블의 모든 행을 삭제하려면 sql 명령을 실행할 수 있습니다

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

TRUNCATE TABLE (Transact-SQL) 개별 행 삭제를 기록하지 않고 테이블에서 모든 행을 제거합니다. TRUNCATE TABLE은 WHERE 절이없는 DELETE 문과 유사합니다. 그러나 TRUNCATE TABLE은 더 빠르며 더 적은 시스템 및 트랜잭션 로그 리소스를 사용합니다.


EntityFramework.Extended 또는 Z.EntityFramework.Plus.EF6과 같은 확장 라이브러리를 사용하여 EF 5, 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 문을 사용하므로 성능이 뛰어납니다.


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();
}

다음과 같이 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(); 
}

var가 아닌 일반 목록에 결과를 전달 하여 DeleteAllOnSubmit () 메서드를 사용할 수도 있습니다 . 이렇게하면 foreach가 한 줄의 코드로 줄어 듭니다.

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

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

그래도 여전히 내부적으로 루프를 사용합니다.


EF 6. =>

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

베스트 : in EF6 => .RemoveRange()

예:

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

작동하는 '좋아하는 코드 비트'답변보기

내가 사용한 방법은 다음과 같습니다.

     // 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;
    }

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();

참고 URL : https://stackoverflow.com/questions/2519866/how-do-i-delete-multiple-rows-in-entity-framework-without-foreach

반응형