如何在 C# 中使用 Func / Expressions 创建查询对象?

How to create a query object with Func / Expressions in C#?

我想为我使用 ElasticSearch 的项目创建一个可重用的查询对象。我已经在使用 LongLe 的类似 QueryObject from the Generic Unit of Work/Repositories 来使用 Entity Framework.

对数据库进行查询

我似乎无法完全理解如何执行此操作 - 我不确定如何 "chain" 将 lambda 表达式的各个部分组合在一起。我试过使用 Expression/Func 但这对我来说是一个我不完全理解的新领域。我感觉好像我只是在黑暗中刺伤。

因为我什至不知道如何准确表达我的问题,下面是我目前正在做的事情、我正在尝试做的事情以及我目前的进展的一个例子:

我目前要做的事情:

    ISearchResponse<DemoIndexModel> result = client.Search<DemoIndexModel>(s => s.Query(
        q => q.Term(t => t.FirstName, firstName)
        && q.Term(t => t.LastName, lastName)));

我想做的事情:

    var query = new DemoIndexQuery();
    query = query.ByFirstName(firstName);
    query = query.ByLastName(lastName);

    result = client.Search<DemoIndexModel>(s => s.Query(query.Compile()));

目前代码:

public abstract class ElasticQueryObject<T> where T : class
    {
        private Func<QueryDescriptor<T>, QueryContainer> _query;

        // tried using Expression, still completely lost
        private Expression<Func<QueryDescriptor<T>, QueryContainer>> _expression;

        public Func<QueryDescriptor<T>, QueryContainer> Compile()
        {
            return _query;
        }

        public Func<QueryDescriptor<T>, QueryContainer> And(Func<QueryDescriptor<T>, QueryContainer> query)
        {
            if (_query == null)
            {
                _query = query;
            }
            else
            {
                // how do I chain the query??? I only can figure out how to set it.
            }

            return null;
        }
    }

    public class DemoIndexQuery : ElasticQueryObject<DemoIndexModel>
    {
        public DemoIndexQuery ByFirstName(string firstName)
        {
            And(p => p.Term(term => term.FirstName, firstName));

            return this;
        }

        public DemoIndexQuery ByLastName(string lastName)
        {
            And(p => p.Term(term => term.LastName, lastName));

            return this;
        }
    }

您违反了 LINQ 合同。查询应该是不可变的——所有在查询上操作的方法都应该 return 一个新查询而不是修改旧查询。由于查询的构建方式,这本质上是可组合的,所以不用

public DemoIndexQuery ByFirstName(string firstName)
{
    And(p => p.Term(term => term.FirstName, firstName));

    return this;
}

你可以只用这个:

public DemoIndexQuery ByFirstName(string firstName)
{
    return Where(p => p.Term(term => term.FirstName, firstName));
}

如果由于某种原因无法做到这一点,您需要自己构建表达式树,然后再传递它。最简单的方法是这样的:

Expression<...> oldQuery = ...;
var newCondition = (Expression<...>)(p => p.Term(...));

return Expression.And(oldQuery, newCondition);

如果您的查询提供程序不支持此功能,您需要做更多的工作 - 您可以单独构建整个 where 谓词,然后确保修复 lambda 和 lambda 参数。