Запросы, которые извлекают наборы данных, редко требуют полной информации от соответствующих объектов. Вы можете уменьшить это и отфильтровать данные, проецируя сущности в структуру модели представления, которая отображает, какие данные вам действительно нужны.
using (var ctx = new DBContext)
{
var persons = ctx.Persons
.Select(x => new PersonViewModel
{
PersonId = x.PersonId,
Name = x.Name,
TypeName = x.Type.Name,
RecentQueries = x.Queries
.OrderByDecending(q => q.QueryDate)
.Select(q => new QueryViewModel
{
QueryId = q.QueryId,
Name = q.Name,
LocationName = q.Location.Name
// ... etc.
}).Take(2).ToList()
}).ToList();
//...
}
Этот подход помогает создавать более эффективные запросы, используя только те данные, которые вам нужны. Извлечение полных графов сущностей - это скорее запрос для каждого элемента, например, когда вы применяете обновление к человеку и связанным с ним сущностям, а не пытаетесь отозвать всех людей со всеми или подмножество их связанных сущностей.
Изменить: Пример жизненного цикла некоторых данных между сервером и представлением:
Часть 1 ... Список группы людей. Это сводные данные, достаточное количество данных для удовлетворения представления сводного списка.
public IEnumerable<PersonSummaryViewModel> GetPeople(/*criteria*/)
{
using (var ctx = new DBContext)
{
var people = ctx.Persons
.Where( /* based on criteria */ )
.Select(x => new PersonSummaryViewModel
{
PersonId = x.PersonId,
Name = x.Name,
TypeName = x.Type.Name,
RecentQueries = x.Queries
.OrderByDecending(q => q.QueryDate)
.Select(q => new QuerySummaryViewModel
{
QueryId = q.QueryId,
Name = q.Name,
LocationName = q.Location.Name
// ... etc.
}).Take(2).ToList()
}).ToList();
//...
return people;
}
}
Часть 2. Получение подробной модели человека. Как только пользователь выберет человека, мы захотим получить больше данных. Эта модель представления может иметь намного больше полей и отношений, чтобы удовлетворить представление. Но даже здесь мы можем захотеть исключить данные, которые обычно не просматриваются (например, в расширяемых областях или вкладках), которые могут быть отозваны по требованию с помощью вызовов Ajax, если / когда пользователь хочет.
public PersonDetailViewModel> GetPerson(int personId)
{
using (var ctx = new DBContext)
{
var person = ctx.Persons
.Select(x => new PersonDetailViewModel
{
PersonId = x.PersonId,
Name = x.Name,
TypeName = x.Type.Name,
// ... Can load all visible properties and initial, important related data...
}).Single(x => x.PersonId == personId);
//...
return person;
}
}
Часть 3: пример, обновление персоны
public void UpdatePerson(UpdatePersonViewModel updatePersonModel)
{
if (updatePersonModel == null)
throw new ArgumentNullException("updatePersionModel");
// TODO: Validate updateModel, ensure everything is Ok, not tampered/invalid.
using (var ctx = new DBContext)
{
var person = ctx.Persons
// can .Include() any related entities that can be updated here...
.Where( x => x.PersonId == updatePersonModel.PersonId )
.Single();
person.Name = updatePersonModel.Name;
// etc.
ctx.SaveChanges();
}
}
Часть 4: Пример, добавление запроса.
public void AddQuery(int personId, AddQueryViewModel queryModel)
{
if (queryModel == null)
throw new ArgumentNullException("queryModel");
// TODO: Validate queryModel, ensure everything is Ok, not tampered/invalid.
using (var ctx = new DBContext)
{
var person = ctx.Persons
.Include( x => x.Queries )
.Where( x => x.PersonId == personId )
.Single();
// TODO: Maybe check for duplicate query already on Person, etc.
var query = new Query
{
// copy data from view model
};
person.Queries.Add(query);
ctx.SaveChanges();
}
}
Модели представления (или DTO) относятся только к передаче данных между сервер и клиент. Когда мы возвращаем данные на сервер и хотим обновить сущности, мы загружаем эти сущности. Используя эти модели представлений, мы уменьшаем объем данных, передаваемых между клиентом и сервером (только необходимые поля, а не целые графы сущностей), что означает более быстрый код и меньшее количество данных по сети. Мы не раскрываем больше о нашей структуре сущностей, чем необходимо знать клиенту, и мы не рискуем просто перезаписать данные, возвращаемые чем-то вроде Attach
+ EntityState.Modified
+ SaveChanges
, где данные могут быть устаревшими (кто-то иначе изменено, так как эта копия была взята), или возвращающиеся данные могли быть подделаны. Просмотр моделей имеет единственное назначение. Мы могли бы использовать PersonDetailViewModel в операции типа UpdatePerson, но наше обновление может применяться только к нескольким выбранным свойствам, поэтому накладываются накладные расходы на отправку всего на сервер, и нет никаких подразумеваемых ограничений на то, что должно и не должно быть разрешено обновлять. , (Особенно если вы используете Automapper или тому подобное, чтобы помочь копировать поля между сущностями и представлениями моделей)
В тех случаях, когда мы просто хотим взаимодействовать с некоторыми данными сущностей, не отправляя их обратно клиенту, мы нам не нужно просматривать модели / DTO, мы можем просто выборочно извлекать поля из всего графа сущностей, используя анонимные типы. Например, если я просто хочу проверить, есть ли у человека определенные критерии соответствия, и обобщить имена запросов, которые соответствуют:
var queryNames = ctx.Persons
.Where(x => x.PersonId == personId)
.SelectMany(x => Queries.Select( q => new
{
q.QueryId,
q.Name
}).ToList();
var message = string.Join(", ",
queryNames.Select(x => string.Format("{0} ({1})", x.Name, x.QueryId)));
Просто как простой пример с анонимным типом для возврата структуры, которую можно использовать по дальнейшему коду без необходимости просмотра модели / DTO. Мы не передаем данные сущности обратно, а, возможно, просто проверяем значения, чтобы определить ход действий, или составляем что-то вроде строки сообщения.