I have been working on a solution. please notice that this is based on my limited knowledge of C# Expressions, if you have any suggestions improvements i will appreciate it.
public static class EfExtensions
{
public static IQueryable<T> WhereCompoundIn<T, TKey>(this IQueryable<T> source, IEnumerable<TKey> keys,
Expression<Func<T, TKey>> keySelectorExpression)
{
var keyExpressions = GetPropertyExpressions(keySelectorExpression)
.ToDictionary(x => x.Member.Name);
// get the properties and build a selector expression for each property
var propertyKeySelectors = typeof(TKey)
.GetProperties()
.Select(propertyInfo =>
{
var parameter = Expression.Parameter(typeof(TKey));
var property = Expression.Property(parameter, propertyInfo);
var conversion = Expression.Convert(property, typeof(object));
return new
{
PropertyName = propertyInfo.Name,
ValueSelector = Expression.Lambda<Func<TKey, object>>(conversion, parameter).Compile()
};
});
var predicate = keys
.Select(compoundKey =>
{
var andExpressions = propertyKeySelectors
.Select(key =>
{
var keyValue = key.ValueSelector(compoundKey);
var propertySelectorExpression = keyExpressions[key.PropertyName];
// T.Property == keyValue
return Expression.Equal(propertySelectorExpression, Expression.Constant(keyValue));
})
// T.Property1 == keyValue1 && T.Property2 == keyValue2 && ...
.Aggregate(Expression.AndAlso);
return andExpressions;
}
)
// T.Property1 == keyValue1 && T.Property2 == keyValue2 && ... || T.Property1 == keyValue1 && T.Property2 == keyValue2 && ...
.Aggregate(Expression.OrElse);
return source.Where(Expression.Lambda<Func<T, bool>>(predicate, keySelectorExpression.Parameters));
}
private static IEnumerable<MemberExpression> GetPropertyExpressions<T, TResult>(
this Expression<Func<T, TResult>> expression)
{
if (expression.Body is not NewExpression newExpression)
throw new ArgumentException("Expression must be a NewExpression", nameof(expression));
foreach (var argumentExpression in newExpression.Arguments)
{
if (argumentExpression is not MemberExpression { Expression: not null } memberExpression) continue;
var memberName = memberExpression.Member.Name;
yield return Expression.Property(memberExpression.Expression, memberName);
}
}
}
Which can be used as follows:
var compoundKeys =
"2480209000000469302,2480209000000469347,2480209000000469374,2480209000000470068"
.Split(',')
.Select(productId => new { ProductId = productId, StoreId = "MGA_SUR" })
.ToArray();
var productStocks = context.ProductStocks
.Where(x => x.BusinessId == "ZUPER")
.WhereCompoundIn(compoundKeys, x => new { x.ProductId, x.StoreId })
.ToArray();
The query above generates the following SQL code:
SELECT `p`.`business_id`,
`p`.`store_id`,
`p`.`product_id`,
`p`.`created_by`,
`p`.`created_on`,
`p`.`is_active`,
`p`.`last_updated_by`,
`p`.`last_updated_on`,
`p`.`min_stock`,
`p`.`purchase_price`,
`p`.`sales_category`,
`p`.`sales_price`,
`p`.`stock`
FROM `product_stocks` AS `p`
WHERE (`p`.`business_id` = 'ZUPER')
AND (((((`p`.`product_id` = '2480209000000469302') AND (`p`.`store_id` = 'MGA_SUR')) OR
((`p`.`product_id` = '2480209000000469347') AND (`p`.`store_id` = 'MGA_SUR'))) OR
((`p`.`product_id` = '2480209000000469374') AND (`p`.`store_id` = 'MGA_SUR'))) OR
((`p`.`product_id` = '2480209000000470068') AND (`p`.`store_id` = 'MGA_SUR')))