2011-12-16 15 views
5

Tôi biết điều này là có thể trong LINQ-to-SQL, và tôi đã nhìn thấy các bit và miếng dẫn tôi tin rằng nó có thể trong EF. Có một phần mở rộng ra có thể làm một cái gì đó như thế này:Mã EF Đầu tiên Xóa Lô Từ IQueryable <T>?

var peopleQuery = Context.People.Where(p => p.Name == "Jim"); 

peopleQuery.DeleteBatch(); 

ở đâu DeleteBatch chỉ chọn ngoài các peopleQuery và tạo ra một câu lệnh SQL duy nhất để xóa tất cả các hồ sơ thích hợp, sau đó thực hiện truy vấn trực tiếp thay vì đánh dấu tất cả những các thực thể để xóa và làm cho chúng từng cái một. Tôi nghĩ rằng tôi tìm thấy một cái gì đó như thế trong mã dưới đây, nhưng nó không thành công ngay lập tức bởi vì dụ không thể được đúc vào ObjectSet. Có ai biết cách sửa lỗi này để làm việc với EF Code First không? Hoặc biết bất cứ nơi nào có một ví dụ về việc này đang được thực hiện?

public static IQueryable<T> DeleteBatch<T>(this IQueryable<T> instance) where T : class 
{ 
    ObjectSet<T> query = instance as ObjectSet<T>; 
    ObjectContext context = query.Context; 

    string sqlClause = GetClause<T>(instance); 
    context.ExecuteStoreCommand("DELETE {0}", sqlClause); 

    return instance; 
} 

public static string GetClause<T>(this IQueryable<T> clause) where T : class 
{ 
    string snippet = "FROM [dbo].["; 

    string sql = ((ObjectQuery<T>)clause).ToTraceString(); 
    string sqlFirstPart = sql.Substring(sql.IndexOf(snippet)); 

    sqlFirstPart = sqlFirstPart.Replace("AS [Extent1]", ""); 
    sqlFirstPart = sqlFirstPart.Replace("[Extent1].", ""); 

    return sqlFirstPart; 
} 

Trả lời

5

Khuôn khổ đối tượng không hỗ trợ hoạt động hàng loạt. Tôi thích cách mã giải quyết vấn đề nhưng thậm chí nó thực hiện chính xác những gì bạn muốn (nhưng đối với API ObjectContext), đó là một giải pháp sai.

Tại sao giải pháp sai?

Chỉ hoạt động trong một số trường hợp. Nó chắc chắn sẽ không hoạt động trong bất kỳ giải pháp lập bản đồ nâng cao nào mà thực thể được ánh xạ tới nhiều bảng (phân tách thực thể, kế thừa TPT). Tôi gần như chắc chắn rằng bạn có thể tìm thấy một tình huống khác mà nó sẽ không hoạt động do sự phức tạp của truy vấn.

Nó giữ bối cảnh và cơ sở dữ liệu không nhất quán. Đây là một vấn đề của bất kỳ SQL được thực hiện đối với DB nhưng trong trường hợp này SQL bị ẩn và một lập trình viên khác sử dụng mã của bạn có thể bỏ qua nó. Nếu bạn xóa bất kỳ bản ghi nào trong cùng một thời gian được tải vào cá thể ngữ cảnh, thực thể sẽ không được đánh dấu là đã xóa và bị xóa khỏi ngữ cảnh (trừ khi bạn thêm mã đó vào phương thức DeleteBatch của mình - điều này sẽ đặc biệt phức tạp nếu cho nhiều thực thể (tách bảng)).

Vấn đề quan trọng nhất là sửa đổi truy vấn SQL được tạo ra EF và các giả định bạn đang thực hiện trên truy vấn đó. Bạn đang mong đợi rằng EF sẽ đặt tên cho bảng đầu tiên được sử dụng trong truy vấn là Extent1. Có nó thực sự sử dụng tên đó ngay bây giờ nhưng nó là nội bộ EF thực hiện. Nó có thể thay đổi trong bất kỳ cập nhật nhỏ của EF. Xây dựng logic tùy chỉnh xung quanh nội bộ của bất kỳ API nào được coi là thực tiễn không tốt.

Kết quả là bạn đã phải làm việc với truy vấn trên cấp SQL để bạn có thể gọi truy vấn SQL trực tiếp như @mreyeros đã trình bày và tránh rủi ro trong giải pháp này. Bạn sẽ phải đối phó với tên thật của các bảng và cột nhưng đó là một cái gì đó bạn có thể kiểm soát (bản đồ của bạn có thể xác định chúng).

Nếu bạn không cân nhắc những rủi ro này là đáng kể, bạn có thể thực hiện thay đổi nhỏ đối với mã để làm cho nó làm việc trong DbContext API:

public static class DbContextExtensions 
{ 
    public static void DeleteBatch<T>(this DbContext context, IQueryable<T> query) where T : class 
    { 
     string sqlClause = GetClause<T>(query); 
     context.Database.ExecuteSqlCommand(String.Format("DELETE {0}", sqlClause)); 
    } 

    private static string GetClause<T>(IQueryable<T> clause) where T : class 
    { 
     string snippet = "FROM [dbo].["; 

     string sql = clause.ToString(); 
     string sqlFirstPart = sql.Substring(sql.IndexOf(snippet)); 

     sqlFirstPart = sqlFirstPart.Replace("AS [Extent1]", ""); 
     sqlFirstPart = sqlFirstPart.Replace("[Extent1].", ""); 

     return sqlFirstPart; 
    } 
} 

Bây giờ bạn sẽ gọi hàng loạt xóa theo cách này:

context.DeleteBatch(context.People.Where(p => p.Name == "Jim")); 
+0

Tôi đồng ý với bạn 100% trên tất cả các điểm của bạn, nhưng đó là một nguy cơ chấp nhận được trong dự án của chúng tôi xem xét việc thực hiện nghiêm túc hit thực hiện làm nó theo cách chính thức. Cảm ơn vì sự trả lời! – Ocelot20

1

Tôi không tin rằng các hoạt động hàng loạt, chẳng hạn như xóa được hỗ trợ bởi EF. Bạn có thể thực hiện truy vấn thô:

context.Database.ExecuteSqlCommand("delete from dbo.tbl_Users where isActive = 0"); 
+0

Tôi biết rằng chúng hiện không được hỗ trợ, nhưng tôi hy vọng rằng tôi có thể xây dựng lệnh xóa đó trực tiếp từ biểu thức như tôi đã có thể trong quá khứ. – Ocelot20

1

Trong trường hợp bất kỳ ai khác đang tìm kiếm chức năng này, tôi đã sử dụng một số nhận xét của Ladislav để cải thiện ví dụ của anh ấy. Như ông đã nói, với giải pháp ban đầu, khi bạn gọi SaveChanges(), nếu ngữ cảnh đã theo dõi một trong những thực thể bạn đã xóa, nó sẽ gọi đó là xóa riêng.Điều này không sửa đổi bất kỳ bản ghi nào và EF coi đó là vấn đề tương tranh và đưa ra một ngoại lệ. Phương thức bên dưới chậm hơn bản gốc vì nó phải truy vấn đầu tiên cho các mục cần xóa, nhưng nó sẽ không viết một truy vấn xóa cho từng thực thể đã xóa, đó là lợi ích hiệu suất thực sự. Nó phá hủy tất cả các thực thể được truy vấn, vì vậy nếu bất kỳ thực thể nào trong số chúng đã được theo dõi, nó sẽ biết không xóa chúng nữa.

public static void DeleteBatch<T>(this DbContext context, IQueryable<T> query) where T : LcmpTableBase 
{ 
    IEnumerable<T> toDelete = query.ToList(); 

    context.Database.ExecuteSqlCommand(GetDeleteCommand(query)); 

    var toRemove = context.ChangeTracker.Entries<T>().Where(t => t.State == EntityState.Deleted).ToList(); 

    foreach (var t in toRemove) 
     t.State = EntityState.Detached; 
} 

Tôi cũng thay đổi phần này để sử dụng cụm từ thông dụng vì tôi nhận thấy có khoảng trắng chưa xác định gần phần TỪ. Tôi cũng để lại "[Extent1]" trong đó vì truy vấn DELETE được viết theo cách ban đầu không thể xử lý truy vấn bằng INNER JOINS:

public static string GetDeleteCommand<T>(this IQueryable<T> clause) where T : class 
{ 
    string sql = clause.ToString(); 

    Match match = Regex.Match(sql, @"FROM\s*\[dbo\].", RegexOptions.IgnoreCase); 

    return string.Format("DELETE [Extent1] {0}", sql.Substring(match.Index)); 
} 
Các vấn đề liên quan