从IQueryable

时间:2016-08-05 02:15:00

标签: c# .net entity-framework linq

继上一个问题(Dynamically generate lambda expression with constants from variables)后,我的目标是将解决方法应用于现有代码中的大量复杂LINQ EF查询。

例如,查询构建如下:

var query = Entities.Users.Where(u => u.UserName == varUserName);
query = query.Where(u => u.IsUserLocked == varUserLocked);
query = query.Where(u => u.LastModifiedAt > varLastModifiedAt);

然后结果推导为:

var results = query.ToList();

要解决我正在使用的EF提供程序中的问题,我必须修改Where()表达式,如下所示:

var query = Entities.Users.Where(OracleEFQueryUtils.ReplaceVariablesWithConstants<Func<Entity.User, bool>>(u => u.UserName == varUserName));
query = query.Where(OracleEFQueryUtils.ReplaceVariablesWithConstants<Func<Entity.User, bool>>(u => u.IsUserLocked == varUserLocked));
query = query.Where(OracleEFQueryUtils.ReplaceVariablesWithConstants<Func<Entity.User, bool>>(u => u.LastModifiedAt > varLastModifiedAt));

可以看出,虽然这有效,但它会产生非常冗长的代码,这也意味着必须迁移大型现有代码库才能使用这种新方法。

我正在寻找一种简单的方法将此方法应用于所有Where()表达式。

到目前为止,我已经想到了我正在研究的两种方法:

  • 方法1:从System.Linq.Queryable继承并覆盖Where()以添加必要的方法调用。然后将所有受影响的文件更改为在文件顶部使用我自己的类,而不是使用System.Linq。
  • 方法2:创建一个带有IQueryable的新方法,并处理表达式树以返回一个新的IQueryable,它将所有变量替换为常量,类似于手动调用my方法的效果在所有Where()表达式上。

我更喜欢方法2,因为它直观地感觉更干净,但是我遇到了无法找到要修改的组合表达式树的问题。我找到了IQueryable.Expression属性,但我找不到如何从那里继续。

作为参考,我对OracleEFQueryUtils的实现如下:

class OracleEFQueryUtils
{
    public static Expression<TDelegate> ReplaceVariablesWithConstants<TDelegate>(Expression<TDelegate> source)
    {
        return source.Update(
            new ReplaceVariablesWithConstantsVisitor().Visit(source.Body),
            source.Parameters);
    }

    class ReplaceVariablesWithConstantsVisitor : ExpressionVisitor
    {
        protected override Expression VisitMember(MemberExpression node)
        {
            var expression = Visit(node.Expression);
            if (expression is ConstantExpression)
            {
                var variable = ((ConstantExpression)expression).Value;
                var value = node.Member is FieldInfo ?
                    ((FieldInfo)node.Member).GetValue(variable) :
                    ((PropertyInfo)node.Member).GetValue(variable);
                return Expression.Constant(value, node.Type);
            }
            return node.Update(expression);
        }
    }
}

我可能正在尝试做一些框架不支持的事情,但我期待任何想法!感谢。

1 个答案:

答案 0 :(得分:1)

我要研究创建扩展以覆盖您想要支持的查询操作的方法。根据你想要的程度,它可以产生一些漂亮的代码。

public interface IOracleQueryable<T> : IQueryable<T> { }

public static class OracleQueryExtensions
{
    public static IOracleQueryable<TSource> AsOracleQueryable<TSource>(
            this IQueryable<TSource> source) => new OracleQueryable<TSource>(source);

    public static IOracleQueryable<TSource> Where<TSource>(
            this IOracleQueryable<TSource> source, Expression<Func<TSource, bool>> predicate) =>
        Queryable.Where(source, Replace(predicate)).AsOracleQueryable();

    private static Expression<TDelegate> Replace<TDelegate>(Expression<TDelegate> expr) =>
        OracleEFQueryUtils.ReplaceVariablesWithConstants<TDelegate>(expr);

    private class OracleQueryable<TSource> : IOracleQueryable<TSource>
    {
        public OracleQueryable(IQueryable<TSource> source) { Source = source; }
        private IQueryable<TSource> Source { get; }

        public Expression Expression => Source.Expression;
        public IQueryProvider Provider => Source.Provider;
        public Type ElementType => Source.ElementType;
        public IEnumerator<TSource> GetEnumerator() => Source.GetEnumerator();
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() =>
            GetEnumerator();
    }
}

然后,您现在需要做的就是记住在查询中添加AsOracleQueryable()

var query = Entities.Users.AsOracleQueryable()
    .Where(u => u.UserName == varUserName)
    .Where(u => u.IsUserLocked == varUserLocked)
    .Where(u => u.LastModifiedAt > varLastModifiedAt);

它甚至可以在查询语法中正常工作。

var query =
    from u in Entities.Users.AsOracleQueryable()
    where u.UserName == varUserName
    where u.IsUserLocked == varUserLocked
    where u.LastModifiedAt > varLastModifiedAt
    select u;