c# - 如何包装 Entity Framework 以在执行前拦截 LINQ 表达式?

标签 c# linq entity-framework expression-trees

我想在执行前重写 LINQ 表达式的某些部分。而且我在将我的重写器注入(inject)正确的位置时遇到了问题(实际上根本没有)。

查看 Entity Framework 源代码(在反射器中),它最终归结为 IQueryProvider.Execute,它在 EF 中通过 ObjectContext 提供的表达式耦合内部 IQueryProvider 提供者 { get; 属性。

所以我创建了一个包装类(实现IQueryProvider)来在调用 Execute 时重写表达式,然后将其传递给原始提供程序。

问题是,Provider 后面的字段是 private ObjectQueryProvider _queryProvider;。此 ObjectQueryProvider 是一个内部密封类,这意味着不可能创建提供附加重写的子类。

因此,由于 ObjectContext 的耦合非常紧密,这种方法让我陷入了死胡同。

如何解决这个问题?我看错方向了吗?有没有办法围绕这个 ObjectQueryProvider 注入(inject)我自己?

更新:虽然提供的解决方案在您使用存储库模式“包装”ObjectContext 时都有效,但允许直接使用从 ObjectContext 生成的子类的解决方案会更可取。特此保持与动态数据脚手架的兼容性。

最佳答案

根据 Arthur 的回答,我创建了一个有效的包装器。

所提供的代码片段提供了一种使用您自己的 QueryProvider 和 IQueryable 根包装每个 LINQ 查询的方法。这意味着您必须控制初始查询的开始(因为您大部分时间都会使用任何类型的模式)。

这个方法的问题是它不透明,更理想的情况是在构造函数级别的实体容器中注入(inject)一些东西。

我创建了一个可编译的实现,让它与 Entity Framework 一起工作,并添加了对 ObjectQuery.Include 方法的支持。可以从 MSDN 复制表达式访问者类.

public class QueryTranslator<T> : IOrderedQueryable<T>
{
    private Expression expression = null;
    private QueryTranslatorProvider<T> provider = null;

    public QueryTranslator(IQueryable source)
    {
        expression = Expression.Constant(this);
        provider = new QueryTranslatorProvider<T>(source);
    }

    public QueryTranslator(IQueryable source, Expression e)
    {
        if (e == null) throw new ArgumentNullException("e");
        expression = e;
        provider = new QueryTranslatorProvider<T>(source);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IEnumerable<T>)provider.ExecuteEnumerable(this.expression)).GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return provider.ExecuteEnumerable(this.expression).GetEnumerator();
    }

    public QueryTranslator<T> Include(String path)
    {
        ObjectQuery<T> possibleObjectQuery = provider.source as ObjectQuery<T>;
        if (possibleObjectQuery != null)
        {
            return new QueryTranslator<T>(possibleObjectQuery.Include(path));
        }
        else
        {
            throw new InvalidOperationException("The Include should only happen at the beginning of a LINQ expression");
        }
    }

    public Type ElementType
    {
        get { return typeof(T); }
    }

    public Expression Expression
    {
        get { return expression; }
    }

    public IQueryProvider Provider
    {
        get { return provider; }
    }
}

public class QueryTranslatorProvider<T> : ExpressionVisitor, IQueryProvider
{
    internal IQueryable source;

    public QueryTranslatorProvider(IQueryable source)
    {
        if (source == null) throw new ArgumentNullException("source");
        this.source = source;
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        if (expression == null) throw new ArgumentNullException("expression");

        return new QueryTranslator<TElement>(source, expression) as IQueryable<TElement>;
    }

    public IQueryable CreateQuery(Expression expression)
    {
        if (expression == null) throw new ArgumentNullException("expression");
        Type elementType = expression.Type.GetGenericArguments().First();
        IQueryable result = (IQueryable)Activator.CreateInstance(typeof(QueryTranslator<>).MakeGenericType(elementType),
            new object[] { source, expression });
        return result;
    }

    public TResult Execute<TResult>(Expression expression)
    {
        if (expression == null) throw new ArgumentNullException("expression");
        object result = (this as IQueryProvider).Execute(expression);
        return (TResult)result;
    }

    public object Execute(Expression expression)
    {
        if (expression == null) throw new ArgumentNullException("expression");

        Expression translated = this.Visit(expression);
        return source.Provider.Execute(translated);
    }

    internal IEnumerable ExecuteEnumerable(Expression expression)
    {
        if (expression == null) throw new ArgumentNullException("expression");

        Expression translated = this.Visit(expression);
        return source.Provider.CreateQuery(translated);
    }

    #region Visitors
    protected override Expression VisitConstant(ConstantExpression c)
    {
        // fix up the Expression tree to work with EF again
        if (c.Type == typeof(QueryTranslator<T>))
        {
            return source.Expression;
        }
        else
        {
            return base.VisitConstant(c);
        }
    }
    #endregion
}

存储库中的示例用法:

public IQueryable<User> List()
{
    return new QueryTranslator<User>(entities.Users).Include("Department");
}

关于c# - 如何包装 Entity Framework 以在执行前拦截 LINQ 表达式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1839901/

相关文章:

c# - VisualStudio 无法识别我添加的新引用

entity-framework - 对 EF 自动迁移和播种的困惑 - 在每个程序启动时播种

.net - .NET 4.5 中有什么版本的 Entity Framework ?

entity-framework - Entity Framework 的最小权限原则

c# - 是否可以自动创建自动映射器映射?

c# - 在 .NET 中,为什么常量在编译时而不是在 JIT 时求值?

c# - 如何使用 itext7 在固定矩形内缩放文本?

c# - 比较 C# 中的 Sum 方法

c# - 如何优化 LINQ 查询?

c# - 将 IEnumerable<dynamic> 转换为 JsonArray