J'ai un système qui permet de stocker différents critères relatifs aux ventes dans la base de données. Lorsque les critères sont chargés, ils sont utilisés pour générer une requête et renvoyer toutes les ventes applicables. Les objets de critères ressemblent à ceci:Création d'une requête dynamique dans une boucle à l'aide d'arbres d'expression
ReferenceColumn (La colonne dans la table de vente, ils appliquent à)
MinValue (valeur minimale de la colonne de référence doit être)
MaxValue (valeur maximale de la colonne de référence doit être
Une recherche de Ventes est effectuée en utilisant une collection des critères susmentionnés. Les ReferenceColumns du même type sont OR'd ensemble, et les ReferenceColumns de différents types sont ET'd ensemble. Ainsi, par exemple si j'avais trois critères:
ReferenceColumn: 'Prix', MinValue: '10', MaxValue: '20'
ReferenceColumn: 'Prix', MinValue: '80', MaxValue: « 100 '
ReferenceColumn: 'âge', MINVALUE: '2', MaxValue: '3'
la requête doit retourner toutes les ventes où le prix était entre 10-20 ou entre 80-100, mais seulement si les L'âge des ventes est entre 2 et 3 ans.
Je l'ai implémenté en utilisant une chaîne de requête SQL et l'exécution en utilisant .FromSql:
public IEnumerable<Sale> GetByCriteria(ICollection<SaleCriteria> criteria)
{
StringBuilder sb = new StringBuilder("SELECT * FROM Sale");
var referenceFields = criteria.GroupBy(c => c.ReferenceColumn);
// Adding this at the start so we can always append " AND..." to each outer iteration
if (referenceFields.Count() > 0)
{
sb.Append(" WHERE 1 = 1");
}
// AND all iterations here together
foreach (IGrouping<string, SaleCriteria> criteriaGrouping in referenceFields)
{
// So we can always use " OR..."
sb.Append(" AND (1 = 0");
// OR all iterations here together
foreach (SaleCriteria sc in criteriaGrouping)
{
sb.Append($" OR {sc.ReferenceColumn} BETWEEN '{sc.MinValue}' AND '{sc.MaxValue}'");
}
sb.Append(")");
}
return _context.Sale.FromSql(sb.ToString();
}
Et c'est fait fonctionne très bien avec notre base de données, mais il ne joue pas bien avec d'autres collections, particulièrement la Base de données InMemory que nous utilisons pour UnitTesting, j'essaie donc de le réécrire en utilisant des arborescences Expression, que je n'ai jamais utilisées auparavant. Jusqu'à présent, j'ai obtenu ceci:
public IEnumerable<Sale> GetByCriteria(ICollection<SaleCriteria> criteria)
{
var referenceFields = criteria.GroupBy(c => c.ReferenceColumn);
Expression masterExpression = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
List<ParameterExpression> parameters = new List<ParameterExpression>();
// AND these...
foreach (IGrouping<string, SaleCriteria> criteriaGrouping in referenceFields)
{
Expression innerExpression = Expression.Equal(Expression.Constant(1), Expression.Constant(0));
ParameterExpression referenceColumn = Expression.Parameter(typeof(Decimal), criteriaGrouping.Key);
parameters.Add(referenceColumn);
// OR these...
foreach (SaleCriteria sc in criteriaGrouping)
{
Expression low = Expression.Constant(Decimal.Parse(sc.MinValue));
Expression high = Expression.Constant(Decimal.Parse(sc.MaxValue));
Expression rangeExpression = Expression.GreaterThanOrEqual(referenceColumn, low);
rangeExpression = Expression.AndAlso(rangeExpression, Expression.LessThanOrEqual(referenceColumn, high));
innerExpression = Expression.OrElse(masterExpression, rangeExpression);
}
masterExpression = Expression.AndAlso(masterExpression, innerExpression);
}
var lamda = Expression.Lambda<Func<Sale, bool>>(masterExpression, parameters);
return _context.Sale.Where(lamda.Compile());
}
Il est actuellement jeter un ArgumentException quand je l'appelle Expression.Lamda. Decimal ne peut pas être utilisé là-bas et il dit qu'il veut le type Sale, mais je ne sais pas quoi mettre là pour les ventes, et je ne suis pas sûr que je suis même sur la bonne voie ici. Je suis également préoccupé par le fait que mon masterExpression se duplique à chaque fois au lieu de l'ajouter comme je l'ai fait avec le générateur de chaînes, mais peut-être que cela fonctionnera de toute façon.
Je cherche de l'aide sur la façon de convertir cette requête dynamique en arbre d'expression, et je suis ouvert à une approche totalement différente si je suis hors de la base ici.
Dose votre travail de code d'origine? Cela ne devrait pas fonctionner et pourquoi utilisez-vous 1 = 1 et 1 = 0? –
Oui cela fonctionne si la collection fait partie d'un DbContext utilisant SQL Server. 1 = 1 et 1 = 0 sont là donc je peux toujours ajouter 'AND'/'OU' à la chaîne de requête sans avoir à traiter le premier cas spécial d'itération, etc. – Valuator
Essayez d'utiliser LINQKit (http: //www.albahari .com/nutshell/linqkit.aspx), cela le rend beaucoup plus facile. La page dit: Avec LINQKit, vous pouvez: ... Construire dynamiquement des prédicats – Tom