Here's how to do it avoiding any nasty conversion to strings. What you really want is to take your array and convert it to Expressions that can be applied to Customer objects and then aggregate those using Expression.OrElse
to create a single Query expression that can then be applied to the database.
It's not simple, but here's how to do that.
You'd call the method at the end like so:
var result = Exists(Customers.AsQueryable(),
entity,
(q) => c => (q.CustomerID == c.CustomerID && q.CompanyName == c.CompanyName));
This has several advantages over 'stringfying' everything in your comparison. For one, the database can optimize the query using indices. For another you can pass more complex expressions that simple string comparisons if you wish, e.g. c.CustomerID > q.CustomerID
.
I've separated a CustomerQuery
class from the CustomerClass
because they are different (and fixed your pluralization).
The actual method that does the work is quite simple. All the methods before that are for rewriting Expressions with different parameters to create the OrElse
expression that you want to create. These methods are generally useful any time you want to manipulate expressions and understanding the base ExpressionVisitor
class and how parameter substitution works is a useful excercise. Note how it takes a Func
that maps a CustomerQuery
into an Expression
that can be applied to the Customer
database.
/// <summary>
/// An ExpressionVisitor for parameter substitution
/// </summary>
internal class ExpressionParameterSubstitute : ExpressionVisitor
{
private readonly ParameterExpression from;
private readonly Expression to;
/// <summary>
/// Creates a new instance of the <see cref="ExpressionParameterSubstitute"/> visitor
/// </summary>
public ExpressionParameterSubstitute(ParameterExpression from, Expression to)
{
this.from = from;
this.to = to;
}
/// <summary>
/// Visit a Lambda Expression
/// </summary>
protected override Expression VisitLambda<T>(Expression<T> node)
{
if (node.Parameters.All(p => p != this.from))
return node;
// We need to replace the `from` parameter, but in its place we need the `to` parameter(s)
// e.g. F<DateTime,Bool> subst F<Source,DateTime> => F<Source,bool>
// e.g. F<DateTime,Bool> subst F<Source1,Source2,DateTime> => F<Source1,Source2,bool>
if (to is LambdaExpression toLambda)
{
var substituteParameters = toLambda?.Parameters ?? Enumerable.Empty<ParameterExpression>();
ReadOnlyCollection<ParameterExpression> substitutedParameters
= new ReadOnlyCollection<ParameterExpression>(node.Parameters
.SelectMany(p => p == this.from ? substituteParameters : Enumerable.Repeat(p, 1))
.ToList());
var updatedBody = this.Visit(node.Body); // which will convert parameters to 'to'
return Expression.Lambda(updatedBody, substitutedParameters);
}
else
{
// to is not a lambda expression so simple substitution can work
ReadOnlyCollection<ParameterExpression> substitutedParameters
= new ReadOnlyCollection<ParameterExpression>(node.Parameters
.Where(p => p != this.from)
.ToList());
var updatedBody = this.Visit(node.Body); // which will convert parameters to 'to'
if (substitutedParameters.Any())
return Expression.Lambda(updatedBody, substitutedParameters);
else
return updatedBody;
}
}
/// <summary>
/// Visit a ParameterExpression
/// </summary>
protected override Expression VisitParameter(ParameterExpression node)
{
var toLambda = to as LambdaExpression;
if (node == from) return toLambda?.Body ?? to;
return base.VisitParameter(node);
}
}
public static Expression<Func<T, bool>> OrElse<T>(
Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ExpressionParameterSubstitute(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ExpressionParameterSubstitute(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.OrElse(left, right), parameter);
}
public static IDictionary<string, bool> Exists(IQueryable<Customer> customers, IEnumerable<CustomerQuery> data, Func<CustomerQuery, Expression<Func<Customer, bool>>> predicate)
{
Expression<Func<Customer, bool>> expression = x => false;
foreach (var item in data)
{
var exprForOne = predicate.Invoke(item);
expression = OrElse(expression, exprForOne);
}
var split = customers.GroupBy(expression).SelectMany(g => g.Select(c => new {c, g.Key})).ToDictionary(x => x.c.CustomerID, x => x.Key);
return split;
}