Реализация репозитория
Неправильная реализация

репозиторий
public List GetEntity()
{
return db.db_entities.ToList()
}

Представим, что этот метод мы будем использовать в менеджере следующим образом:
GetEntity().Where(e => e.some_field = ‘some value’).ToList()
Все LINQ выражения, указанные до первого ToList будут выполнены на стороне базы c использованием Linq to entities, т.е. в данном случае из базы будет запрошена вся таблица, затем из коллекции в памяти будут выбраны некоторые отфильтрованные данные.

Правильная реализация
public IQueryable GetEntity()
{
return db.db_entities;
}
При такой реализации условие Where будет выполнено на стороне базы. Но при этом надо понимать, что в этом случае мы не сможем использовать сложные условия, допустим
HashSet ids;
GetEntity().Where(e => ids.Contains(e.id))
Sql server не знает, что такое HashSet и не сможет преобразовать этот запрос в sql запрос. В таких случаях придется загружать данные в память вызовом ToList и фильтровать обычным LINQ.

Использование Include
Неправильная реализация
var entities = db.db_entities.ToList();
var res = entities.Where(e => e.relatedProperty.name == ‘some value’);

Допустим, entities содержит 500 записей. В данном случае для загрузки relatedProperty для каждой записи будет создан отдельный запрос в базу.

Правильная реализация
var entities = db.db_entities.Include(e => e.relatedProperty).ToList();
var res = entities.Where(e => e.relatedProperty.name == ‘some value’);

Так все связанные записи будут загружены в первом же запросе.

Использование ObjectContext
Неправильная реализация
репозиторий
public static List GetEntity(int id)
{
Entities db = new Entities();
return db.db_entities.FirstOrDefault(e => e.id == id);
}

Допустим, этот код используется следующим образом.

var res = GetList();
return res.Select(d => new { d.id, entity = Manager.GetEntity(d.entityID)};

В этом случае для каждой записи из res будет выполнен не только отдельный запрос в базу, но и создано отдельное подключение к базе при создании экземпляра ObjectContext (db). Sql Server разрешает ограниченное число подключений, точнее у него есть пул подключений, и когда сервер запрашивает новое подключение, а все подключения в пуле уже заняты, сервер будет ждать, пока одно из них освободится, чтобы его занять. В идеале на один http request должно быть создано одно подключение к базе.

Правильная реализация

public class Manager
{
private readonly Entities db;
public Repository()
{
db = new Entities();
}
public List GetEntity(int id)
{
return db.db_entities.FirstOrDefault(e => e.id == id);
}
}

var res = GetList();
var mng = new Manager();
return res.Select(d => new { d.id, entity = mng.GetEntity(d.entityID)};

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

Еще было бы неплохо все связанные записи достать одним запросом, если это возможно. Например, реализовать метод, который по списку id достанет все записи из Entites.

public List GetEntities(int[] ids)
{
return db.entities.Where(e => ids.Contains(e.id)).ToList();
}

И использовать следующим образом.
var res = GetList();
var mng = new Manager();
var entities = mng.GetEntities(res.Select(r => r.entityID).ToString());
return res.Select(d => new { d.id, entity = entities.FirstOrDefault(d.entityID)};

Этот же способ можно использовать, если необходимо достать несколько записей из одной таблицы, но условие извлечение очень сложное и потребует проверить данные из многих связанных таблиц. В этом случае вместо того, чтобы в один запрос включать и условие, и непосредственно выборку данных, можно первым запросом извлечь только id нужных записей, вторым запросом извлечь сами записи по id. Эти два запросы выполнятся быстрее, чем один, т.к. в случае одного запроса EntityFramework будет тратить огромное количество времени на маппинг данных из связанных таблиц в POCO объекты, которые возможно в дальнейшем не будут использоваться. А если они необходимо, то во втором запросе всегда можно использовать Include.

Использование кеширования
Неправильная реализация
public db_entities GetEntity(int ID)
{
List entities;
if (CacheManager.Cache[key] != null)
{
entities = CacheManager.Cache[key] as List;
}
else
{
entities = db.entities.ToList();
CacheManager.CacheData(key, entities);
}
return entities.FirstOrDefault();
}

Здесь сделан расчет на то, что коллекция будет по большей части в кеше, запись будет чаще выбираться из кешированной коллекции. Но нельзя делать кеш основным хранилищем данных и складывать туда все данные, которые могут быть запрошены. Кеш может использоваться только для хранения редко меняющихся данных небольшого размера – например, таблица настроек. System.Web.Caching.Cache, который в конечном итоге используется для хранения данных, имеет ограничение на использование памяти, и если достингут лимит, некоторые данные будут удаляться из кеша, если кешировать большое количество данных, то есть вероятность, что они все время будут вытесняться другими данными. Плюс к тому поиск в большом кеше занимает большое количество времени.

Правильная реализация
public db_entities GetEntity(int ID)
{
return db.db_entities.FirstOrDefault();
}

Если вам понравилась статья, помогите, пожалуйста с распространением этого материала в Сети.

Добавить комментарий

Ваш e-mail не будет опубликован.