英文:
How to get records in EF that match a list of combinations (key/values)?
问题
以下是翻译好的部分:
我有一个包含每个用户/年份组合记录的数据库表。
如何使用EF和一个用户ID/年份组合列表从数据库中获取数据?
示例组合:
用户ID 年份
1 2015
1 2016
1 2018
12 2016
12 2019
3 2015
91 1999
我只需要上述组合中定义的记录。无法理解如何使用EF/Linq编写此查询?
List<UserYearCombination> userYears = GetApprovedYears();
var records = dbcontext.YearResults.Where(?????);
类:
public class YearResult
{
public int UserId;
public int Year;
public DateTime CreatedOn;
public int StatusId;
public double Production;
public double Area;
public double Fte;
public double Revenue;
public double Diesel;
public double EmissionsCo2;
public double EmissionInTonsN;
public double EmissionInTonsP;
public double EmissionInTonsA;
// 其他属性...
}
public class UserYearCombination
{
public int UserId;
public int Year;
}
英文:
I have a database table with records for each user/year combination.
How can I get data from the database using EF and a list of userId/year combinations?
Sample combinations:
UserId Year
1 2015
1 2016
1 2018
12 2016
12 2019
3 2015
91 1999
I only need the records defined in above combinations. Can't wrap my head around how to write this using EF/Linq?
List<UserYearCombination> userYears = GetApprovedYears();
var records = dbcontext.YearResults.Where(?????);
Classes
public class YearResult
{
public int UserId;
public int Year;
public DateTime CreatedOn;
public int StatusId;
public double Production;
public double Area;
public double Fte;
public double Revenue;
public double Diesel;
public double EmissionsCo2;
public double EmissionInTonsN;
public double EmissionInTonsP;
public double EmissionInTonsA;
....
}
public class UserYearCombination
{
public int UserId;
public int Year;
}
答案1
得分: 5
这是一个臭名昭著的问题,我之前在这里讨论过。Krishna Muppalla的解决方案是我在那里提出的解决方案之一。它的缺点是它不支持sargable,也就是说它无法从涉及的数据库字段上受益。
与此同时,我想出了另一种解决方案,可能在某些情况下有帮助。基本上,它通过一个字段对输入数据进行分组,然后通过分组键和组元素的Contains查询找到并联合数据库数据:
IQueryable<YearResult> items = null;
foreach (var yearUserIds in userYears.GroupBy(t => t.Year, t => t.UserId))
{
var userIds = yearUserIds.ToList();
var grp = dbcontext.YearResults
.Where(x => x.Year == yearUserIds.Key
&& userIds.Contains(x.UserId));
items = items == null ? grp : items.Concat(grp);
}
我在这里使用Concat
,因为Union
会浪费时间使结果唯一,而在EF6中,Concat
会生成具有链接的SQL UNION
语句,而Union
生成嵌套的UNION
语句,最大嵌套级别可能会达到上限。
当索引存在时,此查询可能性能足够好。理论上,SQL语句中的UNION
的最大数量是无限的,但IN
子句中的项目数(Contains
转换为的)不应超过几千个。这意味着您的数据内容将确定哪个分组字段表现更好,Year
还是UserId
。挑战在于在保持UNION
数量最小的同时,使所有IN
子句中的项目数量保持在约5000以下。
英文:
This is a notorious problem that I discussed before here. Krishna Muppalla's solution is among the solutions I came up with there. Its disadvantage is that it's not sargable, i.e. it can't benefit from any indexes on the involved database fields.
In the meantime I coined another solution that may be helpful in some circumstances. Basically it groups the input data by one of the fields and then finds and unions database data by grouping key and a Contains query of group elements:
IQueryable<YearResult> items = null;
foreach (var yearUserIds in userYears.GroupBy(t => t.Year, t => t.UserId))
{
var userIds = yearUserIds.ToList();
var grp = dbcontext.YearResults
.Where(x => x.Year == yearUserIds.Key
&& userIds.Contains(x.UserId));
items = items == null ? grp : items.Concat(grp);
}
I use Concat
here because Union
will waste time making results distinct and in EF6 Concat
will generate SQL with chained UNION
statements while Union
generates nested UNION
statements and the maximum nesting level may be hit.
This query may perform well enough when indexes are in place. In theory, the maximum number of UNION
s in a SQL statement is unlimited, but the number of items in an IN
clause (that Contains
translates to) should not exceed a couple of thousands. That means that
the content of your data will determine which grouping field performs better, Year
or UserId
. The challenge is to minimize the number of UNION
s while keeping the number of items in all IN
clauses below approx. 5000.
答案2
得分: 1
你可以尝试这样做:
// 将可能的筛选条件添加到列表中
var searchIds = new List<string> { "1-2015", "1-2016", "2-2018" };
// 使用列表在 Where 子句中进行检查
var result = (from x in YearResults
where searchIds.Contains(x.UserId.ToString() + '-' + x.Year.ToString())
select new UserYearCombination
{
UserId = x.UserId,
Year = x.Year
}).ToList();
方法 2
var d = YearResults
.Where(x => searchIds.Contains(x.UserId.ToString() + '-' + x.Year.ToString()))
.Select(x => new UserYearCombination
{
UserId = x.UserId,
Year = x.Year
}).ToList();
英文:
you can try this
//add the possible filters to LIST
var searchIds = new List<string> { "1-2015", "1-2016", "2-2018" };
//use the list to check in Where clause
var result = (from x in YearResults
where searchIds.Contains(x.UserId.ToString()+'-'+x.Year.ToString())
select new UserYearCombination
{
UserId = x.UserId,
Year = x.Year
}).ToList();
Method 2
var d = YearResults
.Where(x=>searchIds.Contains(x.UserId.ToString() + '-' + x.Year.ToString()))
.Select(x => new UserYearCombination
{
UserId = x.UserId,
Year = x.Year
}).ToList();
答案3
得分: 0
I have translated the provided code snippet for you:
我一直在研究一个解决方案。请注意,这是基于我有限的C#表达式知识,如果您有任何建议或改进意见,我将不胜感激。
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);
// 获取属性并为每个属性构建选择器表达式
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("表达式必须是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);
}
}
}
以上是您提供的代码的翻译部分。
英文:
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')))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论