3 votes

EF Core générant une requête inverse

Il y a 4 tables :

UploadDate

Id 
Description

UploadType

Id 
Description

UploadStatus

Id 
Description

UploadDetail

Id
UploadDateId (FK)
UploadTypeId (FK)
UploadStatusId (FK)
OtherFields..

UplodeDate (données)

1    Aug-2018
2    Sep-2018
3    Oct-2018
4    Nov-2018
5    Dec-2018
6    Jan-2019

UplodeType (données)

1    Partner
2    Retail
3    Customer

UplodeStatus (données)

1    Uploaded
2    Processing
3    Successful

UplodeDetail (données)

Id    UploadDateId     UploadTypeId    UploadStatusId    other fields
1     1                1               3                 ...
2     1                2               3                 ...
3     2                2               3                 ...
4     2                1               3                 ...
5     1                3               3                 ...
6     2                3               2                 ...
7     3                2               1                 ...
8     4                2               1                 ...
9     4                2               3                 ...

Ce que j'essaye de faire, c'est d'obtenir les mois pour lesquels le téléchargement a réussi. tous les types de téléchargement

La requête

var list = await _iContext.UploadDate.Where(e => e.UploadDetails.All(o => o.UploadStatusId == (byte)EnumType.UploadStats.Successful)).Distinct().ToListAsync();

Donc, de UploadDate J'obtiens où toutes les entrées dans UploadDetails ont réussi. Cela devrait me donner Aug-2018 . Mais il donne Dec-2018 y Jan-2019

Je me suis enregistré SQL Profiler et il génère la requête suivante...

SELECT DISTINCT [e].[Id], [e].[Description]
FROM [UploadDate] AS [e]
WHERE NOT EXISTS (
    SELECT 1
    FROM [UploadDetail] AS [o]
    WHERE ([e].[Id] = [o].[UploadDateId]) AND ([o].[UploadStatusId] <> CAST(3 AS tinyint)))

En fait, je filtre tout ce qui est NOT réussi, ce qui, techniquement, est le reverse de ce que je veux qu'il génère, quelque chose comme...

SELECT DISTINCT [e].[Id], [e].[Description]
FROM [UploadDate] AS [e]
WHERE EXISTS (
    SELECT 1
    FROM [UploadDetail] AS [o]
    WHERE ([e].[Id] = [o].[UploadDateId]) AND ([o].[UploadStatusId] = CAST(3 AS tinyint))).

De même, si j'exécute la requête ci-dessus (juste au-dessus, pas celle générée par EF Core j'obtiens Aug-2018 ce qui est le résultat escompté.

Alors, pourquoi EF Core générer la requête à l'inverse de ce que j'ai l'intention d'écrire ? ou ai-je écrit une requête totalement erronée ?

1voto

Ivan Stoev Points 1156

Les deux requêtes renvoient des résultats incorrects.

La requête SQL générée par EF Core renvoie [1, 5, 6].

La requête SQL écrite à la main (qui équivaut à l'utilisation de la fonction Any au lieu de All et aurait été également généré par EF Core si vous le faites) revient [1, 2, 4].

Et le résultat souhaité est [1].

Premièrement, c'est un fait bien connu que

All(condition)

est le même que (équivalent de)

!Any(!condition)

Le deuxième fait est (et on peut facilement le voir) que les deux expressions retournent true lorsque la séquence est vide (n'a pas d'éléments). Ce qui est techniquement correct - tous les éléments (zéro dans ce cas) correspondent à la condition. Ou bien il n'y a aucun élément ne correspondant pas à la condition.

Mais ça ne marche pas dans ton cas parce que ce que tu veux en fait c'est "obtenir les mois pour lesquels le téléchargement existe et Le téléchargement est réussi pour tous types de téléchargement" qui s'exprime comme suit :

.Where(e => e.UploadDetails.Any()
    && e.UploadDetails.All(o => o.UploadStatusId == 3))

ou "existe upload réussi et n'existe pas upload non réussi", exprimé comme :

.Where(e => e.UploadDetails.Any(o => o.UploadStatusId == 3)
    && !e.UploadDetails.Any(o => o.UploadStatusId != 3))

Ces deux conditions produiront le comportement souhaité. Cependant, elles génèrent deux sous-requêtes corrélées pour effectuer la vérification.

Si vous voulez effectuer la vérification avec une seule sous-requête corrélée (ce qui ne garantit pas que la requête sera plus rapide - il faut le mesurer), vous pouvez utiliser l'astuce suivante :

.Where(e => e.UploadDetails.Min(o => o.UploadStatusId == 3 ? 1 : (int?)0) == 1)

Il utilise le fait que Min<int?> les fonctions de retour null lorsque la séquence n'a pas d'éléments. Ceci, plus la logique de condition à l'intérieur, garantit que la séquence retournera 1 uniquement lorsqu'il y a des éléments qui correspondent à la condition et aucun élément qui n'y correspond pas. C'est exactement ce dont nous avons besoin.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X