Одна из проблем заключается в том, что коллекция IQueryable действительна только до тех пор, пока ваш DbSet действителен. Как только ваш DbContext будет удален, ваша тщательно заполненная коллекция станет бесполезной.
Так что вам нужно подумать о другом методе восстановления запроса, чем тот, который использует DbSet<Person>
Хотя на первый взгляд они кажутся одинаковыми, между IEnumerable
и IQueryable
есть разница. В Enumerable есть все, чтобы перечислять полученную последовательность.
С другой стороны, Queryable содержит Expression и Provider. Провайдер знает, где можно получить данные. Обычно это база данных, но это также может быть CSV-файл или другие элементы, из которых можно получить последовательности. Поставщик должен интерпретировать выражение и перевести его в формат, понятный базе данных, обычно SQL.
При объединении операторов linq в один большой оператор linq доступ к базе данных не осуществляется. Изменено только выражение.
Как только вы вызываете GetEnumerator () и MoveNext () (обычно с помощью ForEach, ToList () или аналогичного), выражение отправляется поставщику, который преобразует его в формат запроса, который база данных понимает, и выполняет запрос. Результатом запроса является последовательность Enumerable, поэтому вызываются Getenumerator () и MoveNext () результата запроса провайдера.
Поскольку ваш IQueryable содержит этого провайдера, вы не можете больше перечислять его после удаления провайдера.
При использовании платформы сущностей DbSet содержит Провайдера. В провайдере находится информация базы данных, хранящаяся в DbContext. После удаления DbContext вы больше не сможете использовать IQueryable:
IQueryable<Person> query = null;
using (var dbContext = new MyDbcontext())
{
query = dbContext.Persons.Where(person => person.Age > 20);
}
foreach (var person in query)
{
// expect exception: the DbContext is already Disposed
}
Таким образом, вы не можете поместить Провайдера в свою коллекцию или возможные запросы. Тем не менее, вы могли вспомнить выражение. Единственное, что вам требуется от вашего выражения, это то, что оно возвращает человека. Вам также нужна функция, которая использует это выражение и QueryaProvider for Persons для преобразования его в IQueryable.
Давайте создадим для этого универсальную функцию, чтобы ее можно было использовать для любого типа, а не только для Persons:
static IQueryable<TSource> ToQueryable<TSource>(this IQueryProvider provider,
Expression expression)
{
return provider.CreateQuery(expression);
}
// хорошо, давайте добавим также следующее:
static IQueryable<Tsource> ToQueryable<TSource>(this DbContext dbContext,
Expression expression)
{
return dbContext.Set<TSource>.Provider.ToQueryable<TSource>(expression);
}
Для получения справки о функциях расширения см. Демистифицированные функции расширения
Теперь только после того, как вы создадите свою коллекцию выражений. Для быстрого поиска сделайте словарь:
enum PersonQuery
{
ByFirstname,
ByAge,
ByHasChildWithAgeOver,
Skip,
}
public IReadOnlyDictionary<PersonQuery, Expression> CreateExpressions()
{
Dictionary<PersonQuery, Expression> dict = new Dictionary<PersonQuery, Expression>();
using (var dbContext = new MyDbContext())
{
IQueryable<Person> queryByFirstName = dbContext.Persons
.Where(...);
dict.Add(PersonQuery.ByfirstName, queryByFirstName.Expression);
... // etc for the other queries
}
return dict.
}
Использование:
IReadOnlyCollection<Person> PerformQuery(PersonQuery queryId)
{
using (var dbContext = new MyDbContext())
{
// get the Expression from the dictionary:
var expression = this.QueryDictionary[queryId];
// translate to IQueryable:
var query = dbContext.ToQueryable<Person>(expression);
// perform the query:
return query.ToList();
// because all items are fetched by now, the DbContext may be Disposed
}
}