Я приветствую непонятное InvalidCastException, связанное с библиотекой Entity Framework Plus и, в частности, с ее методом расширения IncludeFilter.
Подводя итог, у меня есть 3 объекта: Project, Test и TestRun: - каждый проект имеет набор тестов; - каждый тест имеет коллекцию TestRuns. У меня есть класс ProjectService, который реализует метод для извлечения проекта из базы данных, с возможностью выбора, какие из них являются желательными. Вот код этого метода (я сократил его до наименьшего куска кода, который все еще производит то же исключение, плюс еще один фрагмент, который работает для сравнения):
private IQueryable<Project> NewQuery(ProjectIncludeOptions includes = ProjectIncludeOptions.NONE)
{
IQueryable<Project> query = base.NewQuery();
/* NO PROBLEM HERE: just left it for comparison. */
if (includes.HasFlag(ProjectIncludeOptions.DOMAINS))
{
query =
query
.IncludeFilter(p => p.TestDomains.Where(td => !td.IsArchived).Select(td => td.Children.Where(tdc => !tdc.IsArchived)))
.IncludeFilter(p => p.TestDomains.Where(td => !td.IsArchived).Select(td => td.Parent));
}
/* EXCEPTION CAUSED BY THE CODE BELOW */
if (includes.HasFlag(ProjectIncludeOptions.TESTS))
{
query =
query
/* In the below code, if I remove the Where clause, or use a non-calculated property in it, then the exception disappears. */
.IncludeFilter(p => p.Tests.Where(t => !t.IsArchived).Select(t => t.TestRuns));
}
return query;
}
Реализация свойства IsArchived (в классе Test) выглядит следующим образом:
[NotMapped]
public virtual bool IsArchived
{
get { return ArchivingDate.HasValue; }
set { ArchivingDate = value ? System.DateTime.Now : (System.DateTime?)null; }
}
И место, где я на самом деле получаю InvalidCastException (исходя из вызова SingleOrDefault):
Project project = NewQuery(includes).SingleOrDefault(p => p.Id == projectId);
Полное сообщение об исключении:
System.InvalidCastExceptionÂ: 'Невозможно привести объект типа' System.String 'к типу' System.Int32 '.'
И трассировка стека при возникновении ошибки:
в System.Data.SqlClient.SqlBuffer.get_Int32 () в System.Data.SqlClient.SqlDataReader.GetInt32 (Int32 i) в lambda_method (Closure, DbDataReader) в Microsoft.EntityFrameworkCore data.ReaderReaderReaderReader.Reader.Reader.Reader.Reader.Reader.ReaderReader.Reader.Interize.Inter. Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable
1.Enumerator.BufferlessMoveNext(DbContext _, Boolean buffer) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func
3 операции, Func3 verifySucceeded) at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable
1.Enumerator.MoveNext () в System.Linq.Lookup2.CreateForJoin(IEnumerable
1 source, Func2 keySelector, IEqualityComparer
12 keySelector, IEqualityComparer
) для2 keySelector, IEqualityComparer
в System.Leure. TOuter, TInner, TKey, TResult] (IEnumerable1 outer, IEnumerable
external,1 outer, IEnumerable
1 inner, Func2 outerKeySelector, Func
2 innerKeySelector, Func3 resultSelector, IEqualityComparer
13 resultSelector, IEqualityComparer
) + MoveNext () в System.Linq.Enum erable.GroupJoinIterator [TOuter, TInner, TKey, TResult] (IEnumerable1 outer, IEnumerable
external,1 outer, IEnumerable
1 inner, Func2 outerKeySelector, Func
2 innerKeySelector, Func3 resultSelector, IEqualityComparer
13 resultSelector, IEqualityComparer
) + MoveNext () в System.Labletelect [). TSource, TCollection, TResult] (1 source, Func
IEnumerable1 source, Func
2 collectionSelector, Func3 resultSelector)+MoveNext() at System.Linq.Enumerable.SelectEnumerableIterator
2.MoveNext () в Microsoft.EntityFrameworkCore.Query.Internal.Lntck_Tractor [Train.Train] , TIn] (1 results, QueryContext queryContext, IList
IEnumerable1 results, QueryContext queryContext, IList
1 entityTrackingInfos, IList1 entityAccessors)+MoveNext() at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor
1.EnumeratorExceptionIntereryEra.1.SetResult(IEnumerator
перечислитель1.SetResult(IEnumerator
1) в Z.EntityFramework.Plus.QueryFutureEnumerable1.SetResult(DbDataReader reader) at Z.EntityFramework.Plus.QueryFutureBatch.ExecuteQueries() at Z.EntityFramework.Plus.QueryFutureValue
1.get_Value () в Z.EntityFramework.Plus.QueryIncludeFilterProvider1.Execute[TResult](Expression expression) at System.Linq.Queryable.SingleOrDefault[TSource](IQueryable
источник-11.Execute[TResult](Expression expression) at System.Linq.Queryable.SingleOrDefault[TSource](IQueryable
предикат) в Tresse.Service.Impl.ProjectService.Get (Int32 projectId, включая ProjectIncludeOptions)
Больше всего меня сбивает с толку то, что я не получаю никаких исключений при использовании части ProjectIncludeOptions.DOMAINS, которая, по-видимому, реализована точно так же (свойство IsArchived также идентично для объектов TestDomain).
Более того, все мои объекты (Project, Test, TestRun и TestDomain) имеют свойства DateTime , и они, похоже, играют роль в этой проблеме. Действительно, если я отмечу все свойства DateTime из Test и TestRun как [NotMapped]
(сохраняя весь код в ProjectService, как показано выше), то исключение исчезнет! Если я оставлю только одно сопоставленное свойство DateTime (независимо от того, какое именно), то сработает исключение. Тем не менее, они не вызывают каких-либо проблем с TestDomain и приведенным выше кодом.
Имеет ли это какой-то смысл для любого из вас?
Мне удалось обойти эту ситуацию, просто удалив предложение WHERE внутри IncludeFilter (потому что это не критично для моего проекта), но я был бы рад хотя бы понять, что происходит, и найти решение для него. :)
Я не уверен, что это именно та проблема, но цель IncludeFilter
- создать запрос и выполнить фильтрацию на стороне базы данных.
Однако свойство IsArchived
не отображается. Это означает, что невозможно создать запрос, который будет выполняться на стороне базы данных (это может быть возможно в EF Core 2.x из-за оценки на стороне клиента).
Убедитесь, что фильтрующая часть может быть все сделано в базе данных.
Похоже, что это возможно, используя непосредственно свойство ArchivingDate
.