2013-08-07 38 views
17

Tôi đang sử dụng ASP.NET MVC4 với Entity Framework Code Đầu tiên. Tôi có một bảng gọi là "người dùng", với khóa chính "UserId". Bảng này có thể có hơn 200.000 mục nhập.Bỏ qua chèn khóa trùng lặp với Entity Framework

Tôi cần phải chèn thêm 50 người dùng khác. Tôi có thể làm điều này như

foreach(User user in NewUsers){ 
    context.Add(user); 
} 
dbcontext.SaveChanges(); 

Vấn đề là, một hoặc nhiều người dùng mới có thể đã tồn tại trong DB. Nếu tôi thêm chúng và sau đó cố gắng để tiết kiệm, nó ném một lỗi và không ai trong số những cái hợp lệ được thêm vào. Tôi có thể sửa đổi mã để làm điều này:

foreach(User user in NewUsers){ 
    if(dbcontext.Users.FirstOrDefault(u => u.UserId) == null) 
    { 
     dbcontext.Users.Add(user); 
    } 
} 
dbcontext.SaveChanges(); 

sẽ hoạt động. Vấn đề là, sau đó nó phải chạy một truy vấn 50 lần trên một bảng nhập 200.000+. Vì vậy, câu hỏi của tôi là, phương pháp hiệu quả nhất để chèn những người dùng này, bỏ qua bất kỳ bản sao nào?

+2

lẽ 'context.AddOrUpdate (người sử dụng); 'là những gì bạn muốn –

+1

@OO Nó không thực sự người dùng, đó là dữ liệu khác nhau đang được lấy từ API. API có thể hoặc không thể cung cấp cùng một dữ liệu trong nhiều cuộc gọi tuần tự. Tôi chỉ sử dụng "Người dùng" vì đó là ví dụ đầu tiên tôi nghĩ đến. – Jordan

+1

Tôi gặp vấn đề tương tự và không tìm được giải pháp phù hợp. Có các trường hợp hợp lệ cho điều này, chẳng hạn như khi thực hiện nhập hàng loạt từ CSV nơi địa chỉ email cần phải là duy nhất trong cơ sở dữ liệu hiện có. Đọc tất cả các khóa hiện có vào bộ nhớ có vẻ không tốt cho hiệu suất, không phải là thêm từng mục riêng biệt. Có vẻ như những gì là cần thiết là một cái gì đó giống như INSERT IGNORE. – acarlon

Trả lời

6

Bạn có thể làm điều này:

var newUserIDs = NewUsers.Select(u => u.UserId).Distinct().ToArray(); 
var usersInDb = dbcontext.Users.Where(u => newUserIDs.Contains(u.UserId)) 
           .Select(u => u.UserId).ToArray(); 
var usersNotInDb = NewUsers.Where(u => !usersInDb.Contains(u.UserId)); 
foreach(User user in usersNotInDb){ 
    context.Add(user); 
} 

dbcontext.SaveChanges(); 

này sẽ thực hiện một truy vấn trong cơ sở dữ liệu của bạn để tìm người mà đã tồn tại, sau đó lọc chúng ra khỏi NewUsers thiết lập của bạn.

+0

Đối với các bộ sưu tập lớn, sẽ tốt hơn nếu sử dụng HashSets thay vì .ToArray()? – tbmsu

+1

@tbmsu Đối với các cuộc gọi khung thực thể, nó có thể sẽ không có bất kỳ hiệu ứng hiệu năng nào bởi vì 'Contains' thực sự được dịch sang mệnh đề SQL IN' vì vậy nó thực sự phụ thuộc vào hiệu năng DB nhiều hơn bất kỳ thứ gì khác. Cũng lưu ý rằng đối với các tập dữ liệu * lớn *, điều này có thể sẽ không hoạt động chút nào vì có giới hạn về số lượng giá trị bạn có thể sử dụng trong mệnh đề 'IN' ([ref] (http://stackoverflow.com/questions)/1069415/giới hạn-on-the-nơi-col-in-condition)). –

2

Vì đây là khóa chính của bạn, các tùy chọn của bạn bị giới hạn. Nếu đây không phải là khóa chính của bạn và chỉ là một chỉ mục duy nhất, giả sử SQL Server, bạn có thể thiết lập khóa duy nhất của mình để bỏ qua các bản sao.

Những gì tôi có thể đề nghị là chỉ cần quấn một thử/nắm bắt xung quanh Thêm và ăn ngoại lệ nếu ngoại lệ là lỗi khóa trùng lặp.

Bạn cũng có thể thấy đối tượng của mình có hỗ trợ phương thức AddOrUpdate() hay không. Tôi biết điều này được hỗ trợ trong triển khai Code First. Tôi tin rằng trong trường hợp này, nó sẽ làm một bổ sung vào một mới hoặc cập nhật nếu hàng tồn tại. Tuy nhiên, điều này vẫn có thể liên quan đến một chuyến đi đến DB để xem liệu người dùng đã tồn tại chưa biết để có thực hiện thêm hoặc cập nhật hay không. Và, trong một số trường hợp, bạn có thể không muốn thực sự cập nhật.

Tôi nghĩ nếu đó là tôi, tôi sẽ đi đến tuyến đường Thử/Bắt.

+0

Vì vậy, bạn đang nói đặt SaveChanges() với một thử/bắt xung quanh nó trong vòng lặp for. Điều này sẽ không thêm rất nhiều chi phí từ việc có 50 cuộc gọi SaveChanges() khác nhau và giao dịch cooresponding của họ và viết hoạt động? – Jordan

+0

@Jordan - Về giao dịch, chỉ nên có một giao dịch. Bạn không nên có nhiều giao dịch xảy ra ở đây. Về chi phí, có, sẽ có một số. Tuy nhiên, nó sẽ lớn hơn làm cho 50 (hoặc bất kỳ) cuộc gọi riêng biệt để xem nếu mỗi người dùng tồn tại đầu tiên? Cách tốt nhất để tìm ra là kiểm tra và xem. Cá nhân, tôi nghĩ rằng tôi muốn lấy chi phí trong bộ nhớ của việc sử dụng Try/Catch hơn là thực hiện các cuộc gọi DB lặp đi lặp lại. Nhưng nó sẽ phụ thuộc vào số lượng người dùng bạn thường nhập cùng một lúc. –

3

Bạn có thể lọc ra những người dùng hiện tại với một truy vấn

foreach(User user in NewUsers.Where(us => !dbcontext.Users.Any(u => u.userId == us.userId))) 
{ 
    dbcontext.Users.Add(user); 
} 
dbcontext.SaveChanges(); 

EDIT:

Như đã chỉ ra trong các ý kiến ​​đề nghị trên sẽ gây ra một cuộc gọi sql cho mỗi phần tử trong bộ sưu tập NewUsers. Tôi có thể xác nhận rằng với SQL Server Profiler.

Một intresting kết quả của hồ sơ là sql hơi lạ được tạo ra bởi EF cho từng hạng mục (tên mẫu là khác nhau hơn trong OP, nhưng truy vấn là như nhau):

exec sp_executesql N'SELECT 
CASE WHEN (EXISTS (SELECT 
    1 AS [C1] 
    FROM [dbo].[EventGroup] AS [Extent1] 
    WHERE [Extent1].[EventGroupID] = @p__linq__0 
)) THEN cast(1 as bit) WHEN (NOT EXISTS (SELECT 
    1 AS [C1] 
    FROM [dbo].[EventGroup] AS [Extent2] 
    WHERE [Extent2].[EventGroupID] = @p__linq__0 
)) THEN cast(0 as bit) END AS [C1] 
FROM (SELECT 1 AS X) AS [SingleRowTable1]',N'@p__linq__0 int',@p__linq__0=10 

Khá một mảnh đẹp mã để thực hiện công việc của một lớp lót đơn giản.

Quan điểm của tôi là viết mã khai báo đẹp và dễ đọc và để trình biên dịch và trình tối ưu hóa thực hiện công việc bẩn thỉu là một thái độ tuyệt vời. Đây là một trong những trường hợp khi kết quả của một phong cách như vậy là đáng ngạc nhiên và bạn phải đi bẩn.

+0

Hoạt động hoàn hảo và chỉ cần chạy một lần (truy vấn nơi chỉ chạy một lần trên toàn bộ bảng, sau đó để lặp qua kết quả). Tôi thích nó. – Jordan

+0

@ p.s.w.g Rất hay, tôi đã chỉnh sửa câu trả lời để sửa lỗi cú pháp mà tôi nhận thấy. – Hari

+0

@Jordan Không, nếu không có 'Người dùng mới' là một bộ sưu tập trong bộ nhớ. Trong trường hợp đó, nó phải lặp qua tất cả các mục và đánh giá 'Bất kỳ' trên mỗi mục. Điều này không thực sự cải thiện trên mã ban đầu của bạn, nó chỉ sắp xếp lại nó theo cách thẩm mỹ hơn. –

0

Phương pháp mở rộng sau đây sẽ cho phép bạn chèn các bản ghi của bất kỳ loại trong khi bỏ qua các bản sao:

public static void AddRangeIgnore(this DbSet dbSet, IEnumerable<object> entities) 
    { 
     var entitiesList = entities.ToList(); 
     var firstEntity = entitiesList.FirstOrDefault(); 

     if (firstEntity == null || !firstEntity.HasKey() || firstEntity.HasIdentityKey()) 
     { 
      dbSet.AddRange(entitiesList); 
      return; 
     } 

     var uniqueEntities = new List<object>(); 

     using (var dbContext = _dataService.CreateDbContext()) 
     { 
      var uniqueDbSet = dbContext.Set(entitiesList.First().GetType()); 

      foreach (object entity in entitiesList) 
      { 
       var keyValues = entity.GetKeyValues(); 
       var existingEntity = uniqueDbSet.Find(keyValues); 

       if (existingEntity == null) 
       { 
        uniqueEntities.Add(entity); 
        uniqueDbSet.Attach(entity); 
       } 
      } 
     } 

     dbSet.AddRange(uniqueEntities); 
    } 

    public static object[] GetKeyValues(this object entity) 
    { 
     using (var dbContext = _dataService.CreateDbContext()) 
     { 
      var entityType = entity.GetType(); 
      dbContext.Set(entityType).Attach(entity); 
      var objectStateEntry = ((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity); 
      var value = objectStateEntry.EntityKey 
             .EntityKeyValues 
             .Select(kv => kv.Value) 
             .ToArray(); 
      return value; 
     } 
    } 

    public static bool HasKey(this object entity) 
    { 
     using (var dbContext = _dataService.CreateDbContext()) 
     { 
      var entityType = entity.GetType(); 
      dbContext.Set(entityType).Attach(entity); 
      var objectStateEntry = ((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity); 
      return objectStateEntry.EntityKey != null; 
     } 
    } 

    public static bool HasIdentityKey(this object entity) 
    { 
     using (var dbContext = _dataService.CreateDbContext()) 
     { 
      var entityType = entity.GetType(); 
      dbContext.Set(entityType).Attach(entity); 
      var objectStateEntry = ((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity); 
      var keyPropertyName = objectStateEntry.EntityKey 
             .EntityKeyValues 
             .Select(kv => kv.Key) 
             .FirstOrDefault(); 

      if (keyPropertyName == null) 
      { 
       return false; 
      } 

      var keyProperty = entityType.GetProperty(keyPropertyName); 
      var attribute = (DatabaseGeneratedAttribute)Attribute.GetCustomAttribute(keyProperty, typeof(DatabaseGeneratedAttribute)); 
      return attribute != null && attribute.DatabaseGeneratedOption == DatabaseGeneratedOption.Identity; 
     } 
    } 
+0

_dataService là gì? – iivel

Các vấn đề liên quan