2013-08-23 4 views
4

J'ai une méthode qui prend un IOrderedQueryable et d'expression < Func < T, V >> qui utilise comme un filtre et la page des enregistrements d'une base de données SQL.Convert expression <Func<T, V>> à l'expression <Func <T, Nullable <V> >>

var query = contexBills.AsNoTracking().Where(x => x.Complete==true).OrderBy(x => x.BillID); 

var reader = new BulkReader<Bill>(query, x => x.BillId, 10000); 

le lecteur en vrac est largement utilisé à travers le code à la page d'importants volumes de dossiers et de les traiter par lots et est défini comme celui-ci

public BulkReader(IOrderedQueryable<T> queryable, Expression<Func<T, Object>> selector, int blockSize = 1000) 

Pour la radiomessagerie d'optimisation commence à la valeur minimale trouvée dans la table et se termine à la valeur maximale. Comme il y a plusieurs millions d'enregistrements par mois dans la base de données en utilisant une approche Skip(). Take() se réduit à environ 13 secondes par page lorsque vous atteignez les millions dans la table et que le traitement des mois entiers peut prendre plusieurs heures. Etant donné qu'il y a très peu d'enregistrements dans l'ensemble qui sont marqués comme complets == false, il suffit de sélectionner les enregistrements> = [Page Start] ET < [Page End] fonctionne très rapidement à environ un million d'enregistrements par minute. Dans certains cas, vous traitez légèrement moins que blockSize transmis mais tous les enregistrements entre min et max sont traités. Au fur et à mesure que les mois avancent, la valeur minimale augmente, en supposant que 0 en tant que minimum gaspille beaucoup d'appels SQL qui ne renvoient rien du tout.

Alors ce que je dois obtenir ces valeurs est

var min = queryable.Select(selector).DefaultIfEmpty(0).Min(); 
var max = queryable.Select(selector).DefaultIfEmpty(0).Max(); 

qui produit SQL qui ressemble à ceci

SELECT 
[GroupBy1].[A1] AS [C1] 
FROM (SELECT 
MIN([Join1].[A1]) AS [A1] 
FROM (SELECT 
    CASE WHEN ([Project1].[C1] IS NULL) THEN 0 ELSE [Project1].[PrintSummaryID] END AS [A1] 
    FROM (SELECT 1 AS X) AS [SingleRowTable1] 
    LEFT OUTER JOIN (SELECT 
     [Extent1].[PrintSummaryID] AS [PrintSummaryID], 
     cast(1 as tinyint) AS [C1] 
     FROM [dbo].[tblPrintSummary] AS [Extent1]) AS [Project1] ON 1 = 1 
) AS [Join1] 
) AS [GroupBy1] 
GO 

Si je tends le code (comme un test) pour faire des appels comme celui-ci

var min = queryable.Min(x =>(int?)x.BillID) ?? 0; 
var max = queryable.Max(x =>(int?)x.BillID) ?? 0; 

puis le SQL produit est

SELECT 
[GroupBy1].[A1] AS [C1] 
FROM (SELECT 
MIN([Extent1].[PrintSummaryID]) AS [A1] 
FROM [dbo].[tblPrintSummary] AS [Extent1] 
) AS [GroupBy1] 
GO 

La même chose peut être atteint en déclarant ce qui suit à la place:

Expression<Func<Bill, int?>> selector2 = x => x.BillID; 

Ce qui donne l'avantage de l'exécution SQL plus simple et plus rapide et permet le code de devenir:

var min = queryable.Select(selector2).Min() ?? 0; 
var max = queryable.Select(selector2).Max() ?? 0; 

Prendre la Une approche consistant à redéfinir explicitement tous les sélecteurs et à leur fournir des substitutions signifierait une duplication et un recodage significatifs dans l'ensemble de l'application

Comment puis-je prendre un sélecteur d'origine et faire une conversion à l'équivalent de la version Nullable génériquement plutôt que d'avoir à coder explicitement chacun d'eux.

var selector2 = selector.NullableExpression(); 

je voudrais pour cela comme une méthode d'extension NullableExpression() sur l'expression < Func < T, V >> afin que je retourne un ExpressionExpression < Func < T, Nullable < V >>> et que comme je pourrais l'utiliser dans d'autres endroits tout au long de mon code.

Je suis aux prises avec la façon dont je peux convertir le V en Nullable ou en V? dans une expression.

Répondre

5

Assez simple, vraiment. L'astuce consiste à jouer avec le corps de l'expression source, tout en réutilisant ses paramètres.

public static Expression<Func<T, V?>> ToNullableExpression<T, V> 
    (this Expression<Func<T, V>> source) where V : struct 
{ 
    if(source == null) 
     throw new ArgumentNullException("source"); 

    var body = Expression.Convert(source.Body, typeof(V?)); 
    var parameters = source.Parameters; 

    return Expression.Lambda<Func<T, V?>>(body, parameters); 
} 
+0

travaillé comme un charme ... Merci beaucoup ... –

+0

j'utiliser ExpressionVisitor moi-même, car dans certains cas, vous pourriez avoir des problèmes avec EntityFramework, si l'expression source est pas tout à fait transitoire. C'est à dire que si le résultat et l'entrée sont liés à EntityFramework, une exception sera throw. – Aron

+0

Vous pouvez également utiliser Intercepteur de requête de David Fowl pour convertir dynamiquement '.Min (prédicat) ?? 0' à '.Min (predicate.ToNullableExpression())' à Execute(). – Aron

Questions connexes