如何在EF中获取与一组组合(键/值)匹配的记录?

huangapple go评论133阅读模式
英文:

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&lt;UserYearCombination&gt; 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&lt;YearResult&gt; items = null;

foreach (var yearUserIds in userYears.GroupBy(t =&gt; t.Year, t =&gt; t.UserId))
{
	var userIds = yearUserIds.ToList();
	var grp = dbcontext.YearResults
		.Where(x =&gt; x.Year == yearUserIds.Key 
		         &amp;&amp; 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&lt;YearResult&gt; items = null;

foreach (var yearUserIds in userYears.GroupBy(t =&gt; t.Year, t =&gt; t.UserId))
{
	var userIds = yearUserIds.ToList();
	var grp = dbcontext.YearResults
		.Where(x =&gt; x.Year == yearUserIds.Key 
		         &amp;&amp; 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 UNIONs 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 UNIONs 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&lt;string&gt; { &quot;1-2015&quot;, &quot;1-2016&quot;, &quot;2-2018&quot; };

//use the list to check in Where clause
var result = (from x in YearResults
        where searchIds.Contains(x.UserId.ToString()+&#39;-&#39;+x.Year.ToString())
        select new UserYearCombination
        {
            UserId = x.UserId,
            Year = x.Year
        }).ToList();

Method 2

var d = YearResults
           .Where(x=&gt;searchIds.Contains(x.UserId.ToString() + &#39;-&#39; + x.Year.ToString()))
           .Select(x =&gt; 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&lt;T&gt; WhereCompoundIn&lt;T, TKey&gt;(this IQueryable&lt;T&gt; source, IEnumerable&lt;TKey&gt; keys,
        Expression&lt;Func&lt;T, TKey&gt;&gt; keySelectorExpression)
    {
        var keyExpressions = GetPropertyExpressions(keySelectorExpression)
            .ToDictionary(x =&gt; x.Member.Name);

        // get the properties and build a selector expression for each property
        var propertyKeySelectors = typeof(TKey)
            .GetProperties()
            .Select(propertyInfo =&gt;
            {
                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&lt;Func&lt;TKey, object&gt;&gt;(conversion, parameter).Compile()
                };
            });

        var predicate = keys
            .Select(compoundKey =&gt;
                {
                    var andExpressions = propertyKeySelectors
                        .Select(key =&gt;
                        {
                            var keyValue = key.ValueSelector(compoundKey);
                            var propertySelectorExpression = keyExpressions[key.PropertyName];
                            // T.Property == keyValue
                            return Expression.Equal(propertySelectorExpression, Expression.Constant(keyValue));
                        })
                        // T.Property1 == keyValue1 &amp;&amp; T.Property2 == keyValue2 &amp;&amp; ...
                        .Aggregate(Expression.AndAlso);
                    return andExpressions;
                }
            )
            // T.Property1 == keyValue1 &amp;&amp; T.Property2 == keyValue2 &amp;&amp; ... || T.Property1 == keyValue1 &amp;&amp; T.Property2 == keyValue2 &amp;&amp; ...
            .Aggregate(Expression.OrElse);
        return source.Where(Expression.Lambda&lt;Func&lt;T, bool&gt;&gt;(predicate, keySelectorExpression.Parameters));
    }

    private static IEnumerable&lt;MemberExpression&gt; GetPropertyExpressions&lt;T, TResult&gt;(
        this Expression&lt;Func&lt;T, TResult&gt;&gt; expression)
    {
        if (expression.Body is not NewExpression newExpression)
            throw new ArgumentException(&quot;Expression must be a NewExpression&quot;, 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 =
    &quot;2480209000000469302,2480209000000469347,2480209000000469374,2480209000000470068&quot;
        .Split(&#39;,&#39;)
        .Select(productId =&gt; new { ProductId = productId, StoreId = &quot;MGA_SUR&quot; })
        .ToArray();

var productStocks = context.ProductStocks
    .Where(x =&gt; x.BusinessId == &quot;ZUPER&quot;)
    .WhereCompoundIn(compoundKeys, x =&gt; 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` = &#39;ZUPER&#39;)
  AND (((((`p`.`product_id` = &#39;2480209000000469302&#39;) AND (`p`.`store_id` = &#39;MGA_SUR&#39;)) OR
         ((`p`.`product_id` = &#39;2480209000000469347&#39;) AND (`p`.`store_id` = &#39;MGA_SUR&#39;))) OR
        ((`p`.`product_id` = &#39;2480209000000469374&#39;) AND (`p`.`store_id` = &#39;MGA_SUR&#39;))) OR
       ((`p`.`product_id` = &#39;2480209000000470068&#39;) AND (`p`.`store_id` = &#39;MGA_SUR&#39;)))

huangapple
  • 本文由 发表于 2020年1月3日 23:27:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/59581166.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定