我有一个数据库表,其中包含每个用户/年份组合的记录。
如何使用 EF 和 userId/year 组合列表从数据库中获取数据? 样本组合:
UserId Year
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;
}
这是我之前讨论过的臭名昭著的问题here。 Krishna Muppalla 的解决方案是我在那里提出的解决方案之一。它的缺点是它不可搜索,即它不能从涉及的数据库字段上的任何索引中受益。
与此同时,我创造了另一个在某些情况下可能有用的解决方案。基本上它按字段之一对输入数据进行分组,然后通过分组键和包含组元素的查询来查找和联合数据库数据:
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
将生成带有链式 UNION
语句的 SQL,而 Union
生成嵌套的 UNION
语句并且可能会达到最大嵌套级别.
当索引就位时,此查询可能会执行得很好。理论上,一条 SQL 语句中
UNION
的最大数目是无限的,但是 IN
子句(即 Contains
翻译成)中的项数不应超过几千个。这意味着
您的数据内容将决定哪个分组字段表现更好,Year
或 UserId
。挑战在于最小化UNION
的数量,同时将所有IN
子句中的项目数量保持在大约以下。 5000.
你可以试试这个
//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();
方法二
var d = YearResults
.Where(x=>searchIds.Contains(x.UserId.ToString() + '-' + x.Year.ToString()))
.Select(x => new UserYearCombination
{
UserId = x.UserId,
Year = x.Year
}).ToList();
我一直在研究解决方案。请注意,这是基于我对 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);
// 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);
}
}
}
可以如下使用:
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();
上面的查询生成如下SQL代码:
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')))