Intercepter toutes les requêtes EF6 Linq

entity-framework-6 expression-trees interceptor linq

Question

J'ai une fonction que je veux exécuter sur chaque requête Linq exécutée sur un DbContext pour modifier l'arbre d'expression avant l'exécution. J'ai jeté un coup d'œil à l'interface IDbCommandTreeInterceptor mais cela ne semble pas fournir d'arborescence d'expression (ce qui, je suppose, est compréhensible puisqu'il ne s'agissait peut-être pas d'une requête Linq).

Existe-t-il un moyen d'intercepter et de modifier toutes les expressions avant l'exécution?

nb. Cela doit être une modification de l’arbre Linq car j’ai déjà construit un cadre pour la modification des arbres Linq qui était à l’origine pour Linq en SQL.

Réponse acceptée

La création d'un proxy pour le fournisseur LINQ afin d'intercepter chaque exécution d'une expression LINQ (comme suggéré dans les commentaires) reste une bonne solution. En fait, je m'amuse avec ce genre de choses dans le cadre de ce projet , qui prend explicitement en charge EF6, y compris les requêtes asynchrones EF6. Vous pouvez créer un ExpressionVisitor .NET standard pour effectuer une interception:

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

Mais la question demande également "de s'exécuter sur chaque requête Linq exécutée sur un DbContext", ce qui sera la partie la plus délicate. Une approche peut être une sorte d'abstraction de DbContext / DbSet , de sorte que votre code n'accède pas directement aux objets DbSet . Et à l'intérieur de la mise en œuvre de cette abstraction, l'interception peut arriver ...

Une autre approche (et je pense que cela répond le mieux à cette question) serait un proxy pour DbSet , qui appelle le proxy LINQ pour les requêtes, ce qui permet une interception. Tout d'abord, nous devons hériter 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());
    }
}

Ensuite, il est nécessaire d'écraser tous les membres pour appeler le DbSet réel pour les requêtes autres que les requêtes (ceci est toutefois un peu détaillé):

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

Enfin, nous devons utiliser l'objet d'interception:

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

Et, enfin, nous pouvons utiliser un DbContext ordinaire:

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

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

Nous n'avons qu'à écraser sa méthode Set pour injecter notre proxy.

Remarque : au cas où quelqu'un se le demanderait, il est malheureusement nécessaire d'écraser tous les membres de DbSet , car l'héritage de DbSet n'est conçu que pour les souches de test. Ainsi, le simple héritage de DbSet rompt le DbSet ...



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow