如何在执行之前将Entity Framework包装起来拦截LINQ表达式?

时间:2009-12-03 13:33:59

标签: c# linq entity-framework expression-trees

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

查看实体框架源(在反射器中)它最终归结为IQueryProvider.Execute,其中EF通过提供ObjectContext属性的internal IQueryProvider Provider { get; }与表达式耦合。

所以我创建了一个包装类(实现IQueryProvider)来在调用Execute时执行Expression重写,然后将其传递给原始的Provider。

问题是,Provider后面的字段是private ObjectQueryProvider _queryProvider;。此ObjectQueryProvider内部密封类,这意味着无法创建提供添加重写的子类。

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

如何解决这个问题?我看错了方向吗?有没有办法让我自己注意这个ObjectQueryProvider

更新:虽然提供的解决方案在您使用存储库模式“包装”ObjectContext时都能正常工作,但是允许从ObjectContext直接使用生成的子类的解决方案将更可取。因此保持与Dynamic Data脚手架兼容。

3 个答案:

答案 0 :(得分:15)

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

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

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

我已经创建了一个可编译的实现,让它与实体框架一起工作,并添加了对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");
}

答案 1 :(得分:11)

我确切地知道您需要的源代码 - 但不知道如何附加文件。

以下是一些片段(片段!我必须调整此代码,因此可能无法编译):

的IQueryable:

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();
    }

    IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _provider.ExecuteEnumerable(this._expression).GetEnumerator();
    }

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

    public Expression Expression
    {
        get { return _expression; }
    }

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

IQueryProvider:

public class QueryTranslatorProvider<T> : ExpressionTreeTranslator, IQueryProvider
{
    IQueryable _source;

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

    #region IQueryProvider Members

    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.FindElementTypes().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);
    }

    #endregion        

    #region Visits
    protected override MethodCallExpression VisitMethodCall(MethodCallExpression m)
    {
        return m;
    }

    protected override Expression VisitUnary(UnaryExpression u)
    {
         return Expression.MakeUnary(u.NodeType, base.Visit(u.Operand), u.Type.ToImplementationType(), u.Method);
    }
    #endregion
}

用法(警告:改编的代码!无法编译):

private Dictionary<Type, object> _table = new Dictionary<Type, object>();
public override IQueryable<T> GetObjectQuery<T>()
{
    if (!_table.ContainsKey(type))
    {
        _table[type] = new QueryTranslator<T>(
            _ctx.CreateQuery<T>("[" + typeof(T).Name + "]"));
    }

    return (IQueryable<T>)_table[type];
}

Expression Visitors / Translator:

http://blogs.msdn.com/mattwar/archive/2007/07/31/linq-building-an-iqueryable-provider-part-ii.aspx

http://msdn.microsoft.com/en-us/library/bb882521.aspx

编辑:添加了FindElementTypes()。希望所有方法现在都存在。

    /// <summary>
    /// Finds all implemented IEnumerables of the given Type
    /// </summary>
    public static IQueryable<Type> FindIEnumerables(this Type seqType)
    {
        if (seqType == null || seqType == typeof(object) || seqType == typeof(string))
            return new Type[] { }.AsQueryable();

        if (seqType.IsArray || seqType == typeof(IEnumerable))
            return new Type[] { typeof(IEnumerable) }.AsQueryable();

        if (seqType.IsGenericType && seqType.GetGenericArguments().Length == 1 && seqType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        {
            return new Type[] { seqType, typeof(IEnumerable) }.AsQueryable();
        }

        var result = new List<Type>();

        foreach (var iface in (seqType.GetInterfaces() ?? new Type[] { }))
        {
            result.AddRange(FindIEnumerables(iface));
        }

        return FindIEnumerables(seqType.BaseType).Union(result);
    }

    /// <summary>
    /// Finds all element types provided by a specified sequence type.
    /// "Element types" are T for IEnumerable&lt;T&gt; and object for IEnumerable.
    /// </summary>
    public static IQueryable<Type> FindElementTypes(this Type seqType)
    {
        return seqType.FindIEnumerables().Select(t => t.IsGenericType ? t.GetGenericArguments().Single() : typeof(object));
    }

答案 2 :(得分:4)

只是想加入Arthur的例子。

Arthur警告说 是他的GetObjectQuery()方法中的一个错误。

它使用typeof(T).Name作为EntitySet的名称创建基本查询。

EntitySet名称与类型名称完全不同。

如果您使用的是EF 4,您应该这样做:

public override IQueryable<T> GetObjectQuery<T>()
{
    if (!_table.ContainsKey(type))
    {
        _table[type] = new QueryTranslator<T>(
            _ctx.CreateObjectSet<T>();
    }

    return (IQueryable<T>)_table[type];
}

只要您没有每种类型的多个实体集(MEST),这是非常罕见的。

如果您使用的是3.5,则可以使用我的Tip 13中的代码获取EntitySet名称并将其输入为:

public override IQueryable<T> GetObjectQuery<T>()
{
    if (!_table.ContainsKey(type))
    {
        _table[type] = new QueryTranslator<T>(
            _ctx.CreateQuery<T>("[" + GetEntitySetName<T>() + "]"));

    } 
    return (IQueryable<T>)_table[type];
}

希望这有帮助

亚历

Entity Framework Tips