Entity Framework: замена всей коллекции DbSet
У меня есть универсальный класс, который выполняет добавление / обновление объектов типа T. AddOrUpdate()
метод принимает в DbSet
коллекция для действия, а также список элементов для добавления или обновления в DbSet
, ItemExists()
используется для проверки наличия элемента в коллекции. Если это так, мы обновляем. Если нет, мы добавим. Метод по существу сравнивает первичный ключ элемента, переданного с каждым отдельным элементом в таблице, и возвращает true (а также сам объект базы данных), если есть совпадение.
Код отлично работает для таблиц с небольшим количеством записей. Однако для больших таблиц ItemExists()
Метод очень неэффективен. В этом методе используется цикл foreach, который находится внутри другого цикла foreach в методе вызывающей стороны, давая O(n^2).
Более простым способом было бы просто использовать contextDataSet.Contains(item)
, но это бросает исключение, которое говорит Unable to create a constant value of type
что имеет смысл, поскольку EF не может перевести класс в запрос SQL. Так что это не пойдет.
Теперь мой актуальный вопрос: есть ли способ заменить весь DbSet<T>
с IEnumerable<T>
что передается? IEnumerable, который передается, привязан к сетке данных в представлении и, по сути, включает в себя все элементы, поэтому, логически говоря, замена всей коллекции должна быть безопасной. Любая помощь с благодарностью.
Код
public void AddOrUpdate<I, P>(Expression<Func<I, P>> dbSetExpression, IEnumerable<T> itemsToUpdate)
where I : DbContext, new()
where P : DbSet<T>
{
DataFactory.PerformOperation<I>(c =>
{
if (m_primaryKey == null && !TryFindPrimaryKey(c))
{
throw new ArgumentException("Primary key cannot be null.");
}
// Get the table name from expression passed in.
string dbsetName = ((MemberExpression)dbSetExpression.Body).Member.Name;
var propertyInfo = c.GetType().GetProperties().Single(p => p.Name == dbsetName);
// Get the values in the table.
DbSet<T> contextDataSet = propertyInfo.GetValue(c) as DbSet<T>;
foreach (var item in itemsToUpdate)
{
// If the primary key already exists, we're updating. Otherwise we're adding a new entity.
T existingItem;
if (ItemExists(contextDataSet, item, out existingItem) && existingItem != null)
{
c.Entry(existingItem).CurrentValues.SetValues(item);
}
else
{
contextDataSet.Add(item);
}
}
c.SaveChanges();
});
}
private bool ItemExists(DbSet<T> itemInDbSet, T itemInList, out T existingItem)
{
foreach (var dbItem in itemInDbSet)
{
// Get the primary key value in the database.
var dbValue = dbItem.GetType().GetProperties().Single(
p => p.Name == m_primaryKey).GetValue(dbItem);
// Get the primary key value from the item passed in.
var itemValue =
itemInList.GetType().GetProperties().Single(
p => p.Name == m_primaryKey).GetValue(itemInList);
// Compare the two values.
if (dbValue.ToString() == itemValue.ToString())
{
existingItem = dbItem;
return true;
}
}
existingItem = null;
return false;
}