Je rencontre une InvalidCastException que je ne comprends pas, liée à la bibliothèque Entity Framework Plus et à sa méthode d'extension IncludeFilter en particulier.
Pour résumer, j'ai 3 entités: Project, Test et TestRun: - chaque projet a une collection de tests; - chaque Test a une collection de TestRuns. J'ai une classe ProjectService qui implémente une méthode pour récupérer un projet de la base de données, avec des options pour sélectionner les inclusions souhaitées. Voici le code de cette méthode (je l'ai réduit au plus petit morceau de code qui produit toujours la même exception, plus un autre morceau qui fonctionne pour comparaison):
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;
}
L'implémentation de la propriété IsArchived (dans la classe Test) est la suivante:
[NotMapped]
public virtual bool IsArchived
{
get { return ArchivingDate.HasValue; }
set { ArchivingDate = value ? System.DateTime.Now : (System.DateTime?)null; }
}
Et l'endroit où j'obtiens réellement l'exception InvalidCastException (provenant de l'appel SingleOrDefault):
Project project = NewQuery(includes).SingleOrDefault(p => p.Id == projectId);
Le message d'exception complet est:
System.InvalidCastException: 'Impossible de convertir un objet de type' System.String 'en type' System.Int32 '.'
Et la trace de la pile lorsque l'erreur se produit:
à System.Data.SqlClient.SqlBuffer.get_Int32 () à System.Data.SqlClient.SqlDataReader.GetInt32 (Int32 i) à lambda_method (Closure, DbDataReader) à Microsoft.EntityFrameworkCore.Storage.Internal.TypedRelationalValeurBalerBuff 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 fonctionnement, Func3 verifySucceeded) at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable
1.Enumerator.MoveNext () sur System.Linq.Lookup2.CreateForJoin(IEnumerable
source2.CreateForJoin(IEnumerable
1, Func2 keySelector, IEqualityComparer
1 comparer) sur System.Linq.Enumerable.Join TOuter, TInner, TKey, TResult] (IEnumerable1 outer, IEnumerable
1 interne, Func2 outerKeySelector, Func
2 innerKeySelector, Func3 resultSelector, IEqualityComparer
1 comparateur) + MoveNext () à System.Linq.Enum erable.GroupJoinIterator [TOuter, TInner, TKey, TResult] (IEnumerable1 outer, IEnumerable
1 interne, Func2 outerKeySelector, Func
2 innerKeySelector, Func3 resultSelector, IEqualityComparer
1 comparateur) + MoveNext () at System.Linq.Enumerable.SelectMany TSource, TCollection, TResult] (IEnumerable1 source, Func
2 collectionSelector, Func3 resultSelector)+MoveNext() at System.Linq.Enumerable.SelectEnumerableIterator
2.MoveNext () sur Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider._ , TIn] (IEnumerable1 results, QueryContext queryContext, IList
1 entityTrackingInfos, IList1 entityAccessors)+MoveNext() at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor
1.EnumeratorExceptionIntercepture.RoveFileTrame.MoveTrame.1.SetResult(IEnumerator
énumérateur1.SetResult(IEnumerator
1) chez Z.EntityFramework.Plus.QueryFutureEnumerable1.SetResult(DbDataReader reader) at Z.EntityFramework.Plus.QueryFutureBatch.ExecuteQueries() at Z.EntityFramework.Plus.QueryFutureValue
1.get_Value () sur Z.EntityFramework.Plus.QueryIncludeFilterProvider1.Execute[TResult](Expression expression) at System.Linq.Queryable.SingleOrDefault[TSource](IQueryable
1) prédicat) à Tresse.Service.Impl.ProjectService.Get (Int32 projectId, ProjectIncludeOptions inclut)
La chose qui me dérange le plus est que je n'obtiens aucune exception lorsque j'utilise la partie ProjectIncludeOptions.DOMAINS, qui est apparemment implémentée exactement de la même manière (la propriété IsArchived est également identique sur les objets TestDomain).
De plus, toutes mes entités (Project, Test, TestRun et TestDomain) ont des propriétés DateTime , et elles semblent jouer un rôle dans ce problème. En effet, si je marque toutes les propriétés DateTime de Test et TestRun comme [NotMapped]
(tout en conservant tout le code dans ProjectService comme indiqué ci-dessus), alors l'exception disparaît! Si je laisse une seule propriété DateTime mappée (quelle qu'elle soit), l'exception est déclenchée. Cependant, ils ne semblent pas causer de problème avec TestDomain et le code ci-dessus.
Est-ce que cela a un sens pour vous?
J'ai réussi à contourner cette situation en supprimant simplement la clause WHERE à l'intérieur du IncludeFilter (car ce n'est pas critique pour mon projet), mais je serais heureux de comprendre au moins ce qui se passe et d'avoir une solution. :)
Je ne sais pas si c'est exactement le problème, mais le but de IncludeFilter
est de générer une requête et de faire le filtrage côté base de données.
Cependant, la propriété IsArchived
n'est pas mappée. Cela signifie qu'il est impossible de créer une requête qui sera exécutée du côté de la base de données (cela pourrait être possible dans EF Core 2.x en raison de l'évaluation côté client).
Assurez-vous que la partie filtrage peut être entièrement effectuée dans la base de données.
Cela semble possible en utilisant directement la propriété ArchivingDate
.