合并 2 个结果集的 LINQ 查询

LINQ query to combine 2 resultsets

我有一个 C# LINQ 查询,它有一个主查询,然后是其他 2 个查询,具体取决于变量是否未设置为 0。

查询正常,但我需要将结果集与 return 合并。

我希望最终结果集包含两个子查询的合并结果。有点像在 SQL 查询中你有:

SELECT * FROM myTable WHERE column1 = 'abc' OR column2 = 'xyz'

现在,我认为它使用的是 AND 而不是 OR

var GeoLocations = rows.Select(r => new ElementSearchGeoLocation(r))
    .Where(t => (t.GeoLocationType == "State" && t.CanViewState(t.GeoLocationState, user)) ||
                (t.GeoLocationType == "City" && t.CanViewCity(t.GeoLocationCity, user)));

if(SystemList != 0)
{
    GeoLocations = GeoLocations.Where(t => (dto.SystemList.Contains(t.SystemID)));
}

if (groupList != 0)
{
    GeoLocations = GeoLocations.Where(t => (dto.groupList.Contains(t.PoliticalID)));
}

return Ok(GeoLocations);

有没有办法在 LINQ 中执行此操作?

针对此行为提出了两种方法

  • Union,合并两个结果集,同时剔除重复项
  • Concat,它只是将两个结果集拼在一起

您选择哪一个取决于所需的行为。请注意,如果您的查询实际上是来自数据库的 IQueryables 和 运行(通过 linq-to-sql 或 Entity Framework 等).

如前所述,请不要忘记 LINQ 结果是延迟计算的,查询的这一部分可以安全地保存并重新散列以备后用。

使用Concat 添加额外的行。为了使代码超级精简,我会先存储初始的 `Select:

var AllLocations = rows.Select(r => new ElementSearchGeoLocation(r));
var mainQuery = AllLocations.Where(t => (t.GeoLocationType == "State" && t.CanViewState(t.GeoLocationState, user)) ||
                        (t.GeoLocationType == "City" && t.CanViewCity(t.GeoLocationCity, user)));

然后:

IEnumerable<GeoLocation> subQuery;
if (SystemList != 0)
   subQuery = AllLocations.Where(...);
else
   subQuery = AllLocations.Where(...);

var GeoLocations = mainQuery.Concat(subQuery);

如果您关心重复项,您可以在最后一步使用 Union 而不是 Concat

这是一个 PredicateBuilder 的实现,它能够 Or 两个不同的表达式:

public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

    public static Expression<Func<T, bool>> Or<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        var secondBody = expr2.Body.Replace(
            expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        var secondBody = expr2.Body.Replace(
            expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
    }
}

依赖于以下代码才能将一个表达式的所有实例替换为另一个:

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}
internal static class ExpressionExtensions
{
    public static Expression Replace(this Expression expression,
        Expression searchEx, Expression replaceEx)
    {
        return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
    }
}

使用这个你现在可以写:

var GeoLocations = rows.Select(r => new ElementSearchGeoLocation(r))
    .Where(t => (t.GeoLocationType == "State" && t.CanViewState(t.GeoLocationState, user)) ||
                (t.GeoLocationType == "City" && t.CanViewCity(t.GeoLocationCity, user)));

var predicate = PredicateBuilder.False();

if(SystemList != 0)
{
    predicate = predicate.Or(t => dto.SystemList.Contains(t.SystemID));
}

if (groupList != 0)
{
    predicate = predicate.Or(t => dto.groupList.Contains(t.PoliticalID));
}

return Ok(GeoLocations.Where(predicate));