2012-03-28 3 views
1

Я вставляю много данных, заключенных в транзакцию (например, 2 миллиона + строки) за один раз, используя EF 4.1. Теперь я хотел бы добавить логику UPDATE. Имейте в виду, что отслеживание изменений отключено с учетом объема данных. С верхней части моей головы, я бы что-то вроде этого:EF 4.1 Рекомендации по вставке/обновлению логики

// Obviously simplified code... 
public void AddOrUpdate(Foo foo) 
{ 
    if(!db.Foos.Any(x => someEqualityTest(foo))) 
    { 
     db.Foos.Add(foo); 
    } 

    else 
    { 
     var f = db.Foos.First(x => someEqualityTest(foo)); 
     f = foo; 
    } 

    db.SaveChanges(); 
} 

Любые идеи о том, как возможно, чтобы улучшить это?

+0

Можете ли вы пролить немного больше света на то, как проверить, если два экземпляра Foo равны? Является ли это простым сопоставлением идентификаторов? –

+0

Может быть, я просто пытался обобщить специфику и сосредоточиться на более крупных логических аспектах решения – Didaxis

ответ

2

Я бы сохранил вставки отдельно от обновлений.

Для вставок я бы рекомендовал использовать SqlBulkCopy для вставки всех записей, которые еще не существуют, и это будет способ быстрее.

Во-первых, Bulk метод вставки в вашем DbContext:

public class YourDbContext : DbContext 
{ 
    public void BulkInsert<T>(string tableName, IList<T> list) 
    { 
     using (var bulkCopy = new SqlBulkCopy(base.Database.Connection)) 
     { 
      bulkCopy.BatchSize = list.Count; 
      bulkCopy.DestinationTableName = tableName; 

      var table = new DataTable(); 
      var props = TypeDescriptor.GetProperties(typeof(T)) 
          // Dirty hack to make sure we only have system 
          // data types (i.e. filter out the 
          // relationships/collections) 
          .Cast<PropertyDescriptor>() 
          .Where(p => "System" == p.PropertyType.Namespace) 
          .ToArray(); 

      foreach (var prop in props) 
      { 
       bulkCopy.ColumnMappings.Add(prop.Name, prop.Name); 

       var type = Nullable.GetUnderlyingType(prop.PropertyType) 
          ?? prop.PropertyType; 

       table.Columns.Add(prop.Name, type); 
      } 

      var values = new object[props.Length]; 
      foreach (var item in list) 
      { 
       for (var i = 0; i < values.Length; i++) 
       { 
        values[i] = props[i].GetValue(item); 
       } 

       table.Rows.Add(values); 
      } 

      bulkCopy.WriteToServer(table); 
     } 
    } 
} 

Затем для вставки/обновления:

public void AddOrUpdate(IList<Foo> foos) 
{ 
    var foosToUpdate = db.Foos.Where(x => foos.Contains(x)).ToList(); 

    var foosToInsert = foos.Except(foosToUpdate).ToList(); 

    foreach (var foo in foosToUpdate) 
    { 
     var f = db.Foos.First(x => someEqualityTest(x)); 

     // update the existing foo `f` with values from `foo` 
    } 

    // Insert the new Foos to the table named "Foos" 
    db.BulkInsert("Foos", foosToinsert); 

    db.SaveChanges(); 
} 
+0

Спасибо, я создал решение на основе провайдера, и действительно, в текущем провайдере используется SqlBulkCopy (только вставки), и да, это намного быстрее! Я пересматриваю поставщика EF, потому что теперь мне нужна логика вставки/обновления. Но вы предоставляете очень умное решение, мне оно нравится! – Didaxis

+0

переключатель ответ. Я думаю, что ваше решение - правильный подход здесь – Didaxis

+0

Спасибо! Рад, что смог помочь. –

1

Вашего обновление ...

var f = db.Foos.First(x => someEqualityTest(foo)); 
f = foo; 

... не будет работать, потому что вы не изменяете заряженную и прикрепленную объект f на всех, вы просто перезаписать переменную f с обособленным объектом foo. Прикрепленный объект все еще находится в контексте, но после загрузки он не был изменен, и у вас больше нет переменной, которая указывает на нее. SaveChanges ничего не сделает в этом случае.

В «стандартные опции» Вы находитесь:

var f = db.Foos.First(x => someEqualityTest(foo)); 
db.Entry(f).State = EntityState.Modified; 

или просто

db.Entry(foo).State = EntityState.Modified; 
// attaches as Modified, no need to load f 

Это помечает все свойства, измененные - независимо от того, действительно ли они изменены или нет - и будет посылать UPDATE для каждого столбца в базе данных.

Второй вариант, который только будет отмечать действительно измененные свойства, как модифицированное и только послать UPDATE для измененных столбцов:

var f = db.Foos.First(x => someEqualityTest(foo)); 
db.Entry(f).CurrentValues.SetValues(foo); 

Теперь, с 2 миллионов объектов для обновления вы не имеете «стандарт », и возможно, что оба варианта, особенно второй, который, вероятно, использует отражение внутри, чтобы сопоставить имена свойств источника и целевого объекта, слишком медленны.

Лучшим вариантом при обновлении является Change Tracking Proxies. Это означало бы, что вам нужно пометить КАЖДОЕ свойство в классе сущности как virtual (а не только свойства навигации, но также и скалярные свойства) и что вы не отключите создание прокси-серверов отслеживания изменений (по умолчанию оно включено).

Когда вы загружаете свой объект f из базы данных, EF создаст тогда динамический прокси-объект (полученный из вашей сущности), похожий на ленивые прокси-серверы загрузки, который имеет код, введенный в каждый набор свойств, чтобы поддерживать флаг, если свойство имеет были изменены или нет.

Отслеживание изменений, предоставляемое прокси, намного быстрее, чем отслеживание изменений на основе моментального снимка (что происходит в SaveChanges или DetectChanges).

Я не уверен, хотя если два упомянутых выше варианта быстрее, если вы используете прокси-серверы отслеживания изменений. Вполне возможно, что вам нужно вручную задание свойств, чтобы получить лучшую производительность:

var f = db.Foos.First(x => someEqualityTest(foo)); 
f.Property1 = foo.Property1; 
f.Property2 = foo.Property2; 
// ... 
f.PropertyN = foo.PropertyN; 

В моем опыте в аналогичной ситуации обновления с несколькими тысяч объектов нет реальной альтернативы для изменения отслеживания проксите в отношении производительности.

+0

Отличный, продуманный ответ! Просто то, что я искал! – Didaxis

+0

@ErOx: BTW: Вы действительно ввели 2 миллиона объектов с одним вызовом 'SaveChanges' в конце? Я только что взял старое измерение всего за полмиллиона объектов, и у меня возникло исключение из памяти, когда я вызываю 'SaveChanges' только один раз: http://stackoverflow.com/a/5942176/270591 – Slauma

+0

good catch - Нет, я перерабатываю контекст каждый так много дополнений, очень похожих на тот другой вопрос, с которым вы связались. Так что да, приведенный выше код довольно упрощен – Didaxis

Смежные вопросы