Entity Framework
Методы оптимизации в EF
Поиск…
Использование AsNoTracking
Плохой пример:
var location = dbContext.Location
.Where(l => l.Location.ID == location_ID)
.SingleOrDefault();
return location;
Поскольку приведенный выше код просто возвращает объект без изменения или добавления его, мы можем избежать затрат на отслеживание.
Хороший пример:
var location = dbContext.Location.AsNoTracking()
.Where(l => l.Location.ID == location_ID)
.SingleOrDefault();
return location;
Когда мы используем функцию AsNoTracking()
мы явно указываем Entity Framework, что сущности не отслеживаются контекстом. Это может быть особенно полезно при извлечении больших объемов данных из вашего хранилища данных. Если вы хотите вносить изменения в не отслеживаемые объекты, однако, вы должны помнить о том, чтобы присоединить их перед вызовом SaveChanges
.
Загрузка только необходимых данных
Одной из проблем, часто встречающихся в коде, является загрузка всех данных. Это значительно увеличит нагрузку на сервер.
Предположим, у меня есть модель под названием «location», в которой есть 10 полей, но не все поля требуются одновременно. Предположим, мне нужен только параметр «LocationName» этой модели.
Плохой пример
var location = dbContext.Location.AsNoTracking()
.Where(l => l.Location.ID == location_ID)
.SingleOrDefault();
return location.Name;
Хороший пример
var location = dbContext.Location
.Where(l => l.Location.ID == location_ID)
.Select(l => l.LocationName);
.SingleOrDefault();
return location;
Код в «хорошем примере» будет извлекать только «LocationName» и ничего больше.
Обратите внимание, что поскольку в этом примере не AsNoTracking()
ни одно сущность, AsNoTracking()
не требуется. В любом случае ничего не нужно отслеживать.
Получение полей с анонимными типами
var location = dbContext.Location
.Where(l => l.Location.ID == location_ID)
.Select(l => new { Name = l.LocationName, Area = l.LocationArea })
.SingleOrDefault();
return location.Name + " has an area of " + location.Area;
Как и в предыдущем примере, из базы данных будут извлекаться только поля «LocationName» и «LocationArea», анонимный тип может содержать столько значений, которые вы хотите.
Выполнять запросы в базе данных, если это возможно, а не в памяти.
Предположим, мы хотим подсчитать, сколько уездов есть в Техасе:
var counties = dbContext.States.Single(s => s.Code == "tx").Counties.Count();
Запрос верный, но неэффективный. States.Single(…)
загружает состояние из базы данных. Далее, Counties
загружают все 254 окружения со всеми полями во втором запросе. .Count()
затем выполняется в памяти в загруженной коллекции Counties
.
Мы загрузили много данных, которые нам не нужны, и мы можем сделать лучше:
var counties = dbContext.Counties.Count(c => c.State.Code == "tx");
Здесь мы делаем только один запрос, который в SQL преобразуется в счет и соединение. Мы возвращаем только счет из базы данных - мы сохранили возвращаемые строки, поля и создание объектов.
Легко видеть, где выполняется запрос, глядя на тип коллекции: IQueryable<T>
против IEnumerable<T>
.
Выполнять несколько запросов async и параллельно
При использовании асинхронных запросов вы можете выполнять несколько запросов одновременно, но не в том же контексте. Если время выполнения одного запроса составляет 10 с, время для плохого примера будет 20 с, а время для хорошего примера - 10 с.
Плохой пример
IEnumerable<TResult1> result1;
IEnumerable<TResult2> result2;
using(var context = new Context())
{
result1 = await context.Set<TResult1>().ToListAsync().ConfigureAwait(false);
result2 = await context.Set<TResult1>().ToListAsync().ConfigureAwait(false);
}
Хороший пример
public async Task<IEnumerable<TResult>> GetResult<TResult>()
{
using(var context = new Context())
{
return await context.Set<TResult1>().ToListAsync().ConfigureAwait(false);
}
}
IEnumerable<TResult1> result1;
IEnumerable<TResult2> result2;
var result1Task = GetResult<TResult1>();
var result2Task = GetResult<TResult2>();
await Task.WhenAll(result1Task, result2Task).ConfigureAwait(false);
var result1 = result1Task.Result;
var result2 = result2Task.Result;
Отключить отслеживание изменений и создание прокси
Если вы просто хотите получать данные, но ничего не модифицируете, вы можете отключить отслеживание изменений и создание прокси. Это улучшит вашу производительность, а также предотвратит ленивую загрузку.
Плохой пример:
using(var context = new Context())
{
return await context.Set<MyEntity>().ToListAsync().ConfigureAwait(false);
}
Хороший пример:
using(var context = new Context())
{
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ProxyCreationEnabled = false;
return await context.Set<MyEntity>().ToListAsync().ConfigureAwait(false);
}
Особенно часто их отключать от конструктора вашего контекста, особенно если вы хотите, чтобы они были настроены на ваше решение:
public class MyContext : DbContext
{
public MyContext()
: base("MyContext")
{
Configuration.AutoDetectChangesEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
//snip
}
Работа с объектами-заглушками
Предположим, что у нас есть категории Product
и Category
во взаимосвязи «многие-ко-многим»:
public class Product
{
public Product()
{
Categories = new HashSet<Category>();
}
public int ProductId { get; set; }
public string ProductName { get; set; }
public virtual ICollection<Category> Categories { get; private set; }
}
public class Category
{
public Category()
{
Products = new HashSet<Product>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Если мы хотим добавить Category
в Product
, мы должны загрузить продукт и добавить категорию в свои Categories
, например:
Плохой пример:
var product = db.Products.Find(1);
var category = db.Categories.Find(2);
product.Categories.Add(category);
db.SaveChanges();
(где db
- подкласс DbContext
).
Это создает одну запись в таблице соединений между Product
и Category
. Однако эта таблица содержит только два значения Id
. Это пустая трата ресурсов для загрузки двух полных объектов, чтобы создать одну крошечную запись.
Более эффективным способом является использование объектов-заглушек , то есть объектов сущности, созданных в памяти, содержащих только минимальный минимум данных, обычно только значение Id
. Вот как это выглядит:
Хороший пример:
// Create two stub entities
var product = new Product { ProductId = 1 };
var category = new Category { CategoryId = 2 };
// Attach the stub entities to the context
db.Entry(product).State = System.Data.Entity.EntityState.Unchanged;
db.Entry(category).State = System.Data.Entity.EntityState.Unchanged;
product.Categories.Add(category);
db.SaveChanges();
Конечный результат тот же, но он избегает двух обращений к базе данных.
Предотвращение дублирования
Вы хотите проверить, существует ли ассоциация, достаточно дешевого запроса. Например:
var exists = db.Categories.Any(c => c.Id == 1 && c.Products.Any(p => p.Id == 14));
Опять же, это не будет загружать полные объекты в память. Он эффективно запрашивает таблицу соединений и возвращает только логическое значение.