EF 4.1 Вставка / Обновление Logic Best Practices - PullRequest
1 голос
/ 28 марта 2012

Я вставляю много данных, обернутых в транзакцию (например, 2 миллиона + строк), используя 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();
}

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

Ответы [ 2 ]

2 голосов
/ 28 марта 2012

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

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

Сначала метод Bulk Insert в вашем 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();
}
1 голос
/ 28 марта 2012

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

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

Это помечает ВСЕ свойства какизменен - ​​независимо от того, действительно ли они изменились или нет - и отправит ОБНОВЛЕНИЕ для каждого столбца в базу данных.

Второй параметр, который помечает только действительно измененные свойства как измененные и отправляет только ОБНОВЛЕНИЕ для измененныхколонки:

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

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

Наилучшим вариантом, когда дело доходит до производительности обновлений, являются прокси отслеживания изменений .Это будет означать, что вам нужно пометить КАЖДОЕ свойство в вашем классе сущности как 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;

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...