我有一个函数,我想在DbContext上执行的每个Linq查询上运行,以在执行前修改表达式树。我一直在看IDbCommandTreeInterceptor接口,但这似乎并没有提供一个表达式树(我想这是可以理解的,因为它可能不是一个Linq查询的时间它到达这一点)。
是否有任何方法可以在执行前拦截和修改所有表达式?
nb。这必须是Linq树修改,因为我已经建立了一个框架来修改Linq树,最初是为Linq to SQL。
为LINQ提供程序创建一个代理来拦截每个LINQ表达式的执行(正如评论中建议的那样)仍然是一个很好的解决方案。事实上,我在这个项目中玩这个东西,它明确支持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
用于非查询内容:
(注意:不幸的是,必须覆盖每个DbSet
成员,因为继承DbSet
仅为测试存根设计。因此,仅仅继承DbSet
就会破坏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
。我们只需要重写它的Set
方法来注入我们的代理:
public class MyContext : DbContext
{
public DbSet<Entity> Entities { get; set; }
public override DbSet<TEntity> Set<TEntity>()
{
return new DbSetProxy<TEntity>(base.Set<TEntity>());
}
}