拦截所有EF6 Linq查询

时间:2014-03-28 13:56:36

标签: linq expression-trees entity-framework-6 interceptor

我有一个函数,我希望在DbContext上的每个执行的Linq查询上运行,以在执行之前修改表达式树。我一直在看IDbCommandTreeInterceptor接口,但似乎没有提供一个表达式树(我认为这是可以理解的,因为它到达这一点时可能不是一个Linq查询)。

有没有办法可以在执行前拦截和修改所有表达式?

NB。这必须是Linq树修改,因为我已经构建了一个修改Linq树的框架,最初用于Linq到SQL。

1 个答案:

答案 0 :(得分:5)

为LINQ提供程序创建代理以拦截每个LINQ表达式执行(如注释中所示)仍然是一个很好的解决方案。事实上,我正在玩这个东西within this project,它明确支持EF6,包括EF6异步查询。您可以创建一个标准的.NET ExpressionVisitor来进行拦截:

intercepted = query.Rewrite(new MyInterceptor());

但问题还要求“运行每个在DbContext上执行Linq查询”,这将是棘手的部分。一种方法可以是DbContext / DbSet的某种抽象,因此您的代码不会直接访问DbSet个对象。在这个抽象的实现中,拦截可能发生......

另一种方法(我认为最能回答这个问题)将是DbSet的代理,它调用查询的LINQ代理,从而实现拦截。首先,我们必须继承DbSet

public class DbSetProxy<TEntity> : DbSet<TEntity>,
                                   IQueryable<TEntity>,
                                   IDbAsyncEnumerable<TEntity>
    where TEntity : class
{
    private readonly DbSet<TEntity> set;
    private readonly DbQuery<TEntity> query;

    private readonly IQueryable<TEntity> intercepted;

    public DbSetProxy(DbSet<TEntity> set)
        : this(set, set)
    {
    }

    public DbSetProxy(DbSet<TEntity> set, DbQuery<TEntity> query)
    {
        this.set = set;
        this.query = query;

        // use NeinLinq or any other LINQ proxy library
        intercepted = query.Rewrite(new MyInterceptor());
    }
}

然后,有必要覆盖所有成员来为非查询内容调用实际的DbSet(虽然这有点冗长):

public override DbQuery<TEntity> AsNoTracking()
{
    return new DbSetProxy<TEntity>(set, query.AsNoTracking());
}

public override DbQuery<TEntity> AsStreaming()
{
    return new DbSetProxy<TEntity>(set, query.AsStreaming());
}

public override DbQuery<TEntity> Include(string path)
{
    return new DbSetProxy<TEntity>(set, query.Include(path));
}

public override TEntity Add(TEntity entity)
{
    return set.Add(entity);
}

public override IEnumerable<TEntity> AddRange(IEnumerable<TEntity> entities)
{
    return set.AddRange(entities);
}

public override TEntity Attach(TEntity entity)
{
    return set.Attach(entity);
}

public override TEntity Create()
{
    return set.Create();
}

public override TDerivedEntity Create<TDerivedEntity>()
{
    return set.Create<TDerivedEntity>();
}

public override TEntity Find(params object[] keyValues)
{
    return set.Find(keyValues);
}

public override Task<TEntity> FindAsync(params object[] keyValues)
{
    return set.FindAsync(keyValues);
}

public override Task<TEntity> FindAsync(CancellationToken cancellationToken, params object[] keyValues)
{
    return set.FindAsync(cancellationToken, keyValues);
}

public override TEntity Remove(TEntity entity)
{
    return set.Remove(entity);
}

public override IEnumerable<TEntity> RemoveRange(IEnumerable<TEntity> entities)
{
    return set.RemoveRange(entities);
}

public override DbSqlQuery<TEntity> SqlQuery(string sql, params object[] parameters)
{
    return set.SqlQuery(sql, parameters);
}

public override ObservableCollection<TEntity> Local
{
    get { return set.Local; }
}

public override bool Equals(object obj)
{
    return set.Equals(obj);
}

public override int GetHashCode()
{
    return set.GetHashCode();
}

public override string ToString()
{
    return set.ToString();
}

最后,我们要使用拦截对象:

IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
{
    return intercepted.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
    return intercepted.GetEnumerator();
}

Type IQueryable.ElementType
{
    get { return intercepted.ElementType; }
}

Expression IQueryable.Expression
{
    get { return intercepted.Expression; }
}

IQueryProvider IQueryable.Provider
{
    get { return intercepted.Provider; }
}

IDbAsyncEnumerator<TEntity> IDbAsyncEnumerable<TEntity>.GetAsyncEnumerator()
{
    return ((IDbAsyncEnumerable<TEntity>)intercepted).GetAsyncEnumerator();
}

IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
    return ((IDbAsyncEnumerable<TEntity>)intercepted).GetAsyncEnumerator();
}

最后,我们可以使用普通的DbContext

public class MyContext : DbContext
{
    public DbSet<Entity> Entities { get; set; }

    public override DbSet<TEntity> Set<TEntity>()
    {
        return new DbSetProxy<TEntity>(base.Set<TEntity>());
    }
}

我们只需要覆盖其Set方法来注入我们的代理。

注意:如果有人想知道,遗憾的是必须覆盖每个DbSet成员,因为继承DbSet仅适用于测试存根。因此,只需继承DbSet即可打破DbSet ...