Interceptar todas las consultas Linq EF6

entity-framework-6 expression-trees interceptor linq

Pregunta

Tengo una función que quiero ejecutar en cada consulta Linq ejecutada en un DbContext para modificar el árbol de expresiones antes de la ejecución. He estado echando un vistazo a la interfaz IDbCommandTreeInterceptor pero eso no parece proporcionar un árbol de expresiones (lo que supongo que es comprensible ya que puede que no haya sido una consulta de Linq en el momento en que llegue a este punto).

¿Hay alguna manera de que pueda interceptar y modificar todas las expresiones antes de la ejecución?

nótese bien. Esto tiene que ser una modificación del árbol de Linq porque ya he creado un marco para modificar los árboles de Linq que fue originalmente para Linq a SQL.

Respuesta aceptada

Crear una proxy para que el proveedor de LINQ intercepte cada ejecución de expresión de LINQ (como se sugiere en los comentarios) sigue siendo una buena solución. De hecho, estoy jugando con estas cosas dentro de este proyecto , que explícitamente admite EF6, incluidas las consultas asíncronas EF6. Puede crear un ExpressionVisitor .NET estándar para hacer una intercepción:

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

Pero la pregunta también exige "ejecutar cada consulta Linq ejecutada en un DbContext", y esa será la parte difícil. Un enfoque puede ser algún tipo de abstracción de DbContext / DbSet , por lo que su código no accede directamente a los objetos DbSet . Y dentro de la implementación de esta abstracción puede ocurrir la intercepción ...

Otro enfoque (y creo que responde mejor a esta pregunta) sería un proxy para DbSet , que llama al proxy LINQ para consultas, lo que permite la intercepción. Primero, tenemos que heredar de 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());
    }
}

Entonces, es necesario sobrescribir a todos los miembros para llamar al DbSet real para cosas que no sean de consulta (aunque esto es un poco detallado):

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

Finalmente, debemos hacer uso del objeto de intercepción:

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

Y, por fin, podemos usar un DbContext ordinario:

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

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

Solo tenemos que sobrescribir su método Set para inyectar nuestro proxy.

Nota : en caso de que alguien se DbSet , desafortunadamente es necesario sobrescribir a todos los miembros de DbSet , porque la herencia de DbSet solo está diseñada para comprobantes de prueba. Por lo tanto, simplemente heredar DbSet rompe el DbSet ...



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow