linq 对单个子实体使用表达式

cl25kdpy  于 2023-03-05  发布在  其他
关注(0)|答案(1)|浏览(149)

我第一次深入到表达式中,将其作为减少代码重复的方法。
我创建了2个表达式函数,以将工作隔离为2个单独的实体。

  • 研究中心
  • 站点服务器
public class CMSite
{
   public int ID {get;set;}
   public string Name {get;set;}
   public ICollection<CMSiteServer> SiteServers { get; set; }
}

public class CMSiteServer
{
   public int ID {get;set;}
   public string Name {get;set;}
   public int SiteID {get;set;}
   [ForeignKey("CMSite")]
   public decimal SiteID { get; set; }
   public CMSite Site {get;set;}
}

关系是1个站点=多个站点服务器
每个站点和服务器都有一系列属性,我创建了各种过滤对象来允许搜索这些条目。
使用案例:
1.搜索包含具有属性x的服务器的站点
1.搜索包含具有属性x的站点的服务器
我的代码包含以下方法:

public static Expression<Func<CMSiteServer, bool>> GetServerExpression(CMSiteServerFilter filter, bool Active) {
   ...
}

public static Expression<Func<CMSite, bool>> GetSiteExpression(CMSiteFilter filter, bool Active) {
   ...
}

我可以用这个方法解决用例1:

public static IQueryable<CMSite> ApplyQueryFilterServer(this IQueryable<CMSite> qry, CMSiteServerFilter filter, bool Active)
    {
        if (filter == null)
            return qry;

        var exp = GetServerExpression(filter, Active);
        qry = qry.Where((s) => s.CMSiteServers.AsQueryable().Any(exp));

        return qry;
    }

然而,我找不到用例2的等价物。

public static IQueryable<CMSite> ApplyQueryFilterSite(this IQueryable<CMSiteServer> qry, CMSiteFilter filter, bool Active)
    {
        if (filter == null)
            return qry;

        var exp = GetServerExpression(filter, Active);

        // this is the piece that won't work:
        qry = qry.Where((s) => s.CMSite.AsQueryable().Any(exp));

        return qry;
    }

因为Server.CMSite是单数,所以"AsQueryable"不起作用
我试图避免为每个IQueryable源元素定义一次所有的过滤条件。

igetnqfo

igetnqfo1#

经过大量的挖掘,我找到了解决我的问题,通过这篇文章:
在c#中将表达式的一部分定义为变量
我添加了一个新的辅助函数:

public static Expression<Func<CMSiteServer, bool>> GetSiteExpressionForServer(CMSiteFilter filter, bool Active)
{
     var exp = GetSiteExpression(filter, Active);
     return exp.ApplyTo((CMSiteServer x) => x.CMSite);
}

结合文章中的帮助函数:

public static Expression<Func<TOuter, TResult>> Bind<TOuter, TInner, TResult>(this Expression<Func<TOuter, TInner>> source, Expression<Func<TInner, TResult>> resultSelector)
{
     var body = new ParameterExpressionReplacer { source = resultSelector.Parameters[0], target = source.Body }.Visit(resultSelector.Body);
     var lambda = Expression.Lambda<Func<TOuter, TResult>>(body, source.Parameters);
     return lambda;
}

public static Expression<Func<TOuter, TResult>> ApplyTo<TInner, TResult, TOuter>(this Expression<Func<TInner, TResult>> source, Expression<Func<TOuter, TInner>> innerSelector)
{
     return innerSelector.Bind(source);
}

此外,我还解决了处理多个过滤条件IEnumerable的相关用例,其中每个条件都是“或”,例如,其中@{}是过滤对象@{Name == 'A'},@{Name == 'B'},@{ID == 4}
应组成以下SQL条件:(名称=“A”或名称=“B”或ID = 4)

// function convert a list of filters into a new expression that combines them together
public static Expression<Func<CMSite, bool>> GetServerExpression(IEnumerable<CMSiteServerFilter> filters, bool Active)
    {
        // make sure we got some filters
        if (filters == null)
            return (s => true);

        // compile the list of expressions, one for each filter
        var expressions = filters.Select((f) => GetServerExpression(f, Active)).ToList();

        // if no expressions, we are done
        if (expressions.Count == 0)
            return (s => true);

        //set our initial condition, since we are using "OR" we want to default to false
        Expression<Func<CMSiteServer, bool>> exp = (s => false);
        // merge the multiple expressions into a new single expression
        foreach (var e in expressions)
            exp = exp.Or(e);

        // apply the combined expression to our list of servers
        return (s => s.CMSiteServers.AsQueryable().Where(exp).Any());
    }

上面的“Or”扩展名来自于这篇文章:Combining two expressions (Expression<Func<T, bool>>)

public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b)
{

        ParameterExpression p = a.Parameters[0];

        SubstExpressionVisitor visitor = new SubstExpressionVisitor();
        visitor.subst[b.Parameters[0]] = p;

        Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
        return Expression.Lambda<Func<T, bool>>(body, p);
}

//And the utility class to substitute the parameters in a lambda:
internal class SubstExpressionVisitor : ExpressionVisitor
{
    public Dictionary<Expression, Expression> subst = new Dictionary<Expression, Expression>();

    protected override Expression VisitParameter(ParameterExpression node)
    {
        Expression newValue;
        if (subst.TryGetValue(node, out newValue))
        {
            return newValue;
        }
        return node;
    }
}

相关问题