2016-01-07 13 views
5

Ho la seguente metodo nella mia classe generica:Cast a tipo IQueryable per interfacciarsi in LINQ to Entities

// This is the class declaration 
public abstract class BaseService<TEntity, TKey> : IBaseService<TEntity, TKey> where TEntity : class, IEntity<TKey> 

// The Method 
public IQueryable<TEntity> GetActive() 
{ 
    if (typeof(IActivable).IsAssignableFrom(typeof(TEntity))) 
    { 
     return this.repository.Get().Cast<IActivable>() 
      .Where(q => q.Active) 
      .Cast<TEntity>(); 
    } 
    else 
    { 
     return this.Get(); 
    } 
} 

Questa è l'interfaccia:

public interface IActivable 
{ 
    bool Active { get; set; } 
} 

In sostanza, TEntity è un'entità (POCO) classe, che può implementare IActivable se hanno la proprietà Active. Voglio il metodo per restituire tutti i record che hanno il valore Active true. Tuttavia, ho questo errore:

Unable to cast the type 'WebTest.Models.Entities.Product' to type 'Data.IActivable'. LINQ to Entities only supports casting EDM primitive or enumeration types.

Capisco perché si verifica questo errore. Ma gli articoli su SO non hanno alcuna soluzione valida per il mio caso. È realizzabile con Cast o in altro modo? Nota: non voglio convertire in IEnumerable, voglio mantenere IQueryable.

risposta

7

L'espressione parser EF funziona senza fusione, tuttavia non sarà in grado di compilare il codice C# senza il casting (C# si lamenterà che non sa che TEntity ha una proprietà Active). La soluzione è: lanciare il compilatore C# e non eseguire il cast per il parser di espressioni EF.

Quindi se si è certi (lo si sta verificando nel if, così è) che l'oggetto implementa IActivable, è possibile creare l'espressione con il cast (per la compilazione) e quindi rimuovere i cast in runtime (che sono non necessario) per EF. Per il vostro caso particolare:

public IQueryable<TEntity> GetActive() 
{ 
    if (typeof(IActivable).IsAssignableFrom(typeof(TEntity))) 
    { 
    Expression<Func<TEntity, bool>> getActive = x => ((IActivable)x).Active; 
    getActive = (Expression<Func<TEntity, bool>>)RemoveCastsVisitor.Visit(getActive); 
    return this.repository.Get().Where(getActive); 
    } 
    else 
    { 
    return this.Get(); 
    } 
} 

Il visitatore espressione viene implementata in questo modo:

internal class RemoveCastsVisitor : ExpressionVisitor 
{ 
    private static readonly ExpressionVisitor Default = new RemoveCastsVisitor(); 

    private RemoveCastsVisitor() 
    { 
    } 

    public new static Expression Visit(Expression node) 
    { 
    return Default.Visit(node); 
    } 

    protected override Expression VisitUnary(UnaryExpression node) 
    { 
    if (node.NodeType == ExpressionType.Convert 
     && node.Type.IsAssignableFrom(node.Operand.Type)) 
    { 
     return base.Visit(node.Operand); 
    } 
    return base.VisitUnary(node); 
    } 
} 

E 'solo controlla se un casting è necessaria: se il valore attuale implementa già il tipo è colata a, esso' semplicemente rimuoverò la conversione dall'espressione, e EF la raccoglierà correttamente.

+1

non sapevo che fosse possibile bypassare il casting, bella soluzione! –

+0

Intelligente! Non penserei mai a questo trucco. –

+0

Grandi cose anche se nominare un'interfaccia IActivable mi fa davvero sentire nauseato! Per favore cambiala in IActivatable :) –

0

Attualmente ho un'alternativa è utilizzare il metodo di estensione. Tuttavia il lato negativo è che il mio IBaseService non può dichiarare il metodo GetActive perché le classi concrete non lo implementano realmente.

public static class BaseServiceExtension 
{ 

    public static IQueryable<TEntity> GetActive<TEntity, TKey>(this IBaseService<TEntity, TKey> service) 
     where TEntity : class, IEntity<TKey>, IActivable 
    { 
     return service.Get().Where(q => q.Active); 
    } 

} 
0

Il trucco è quello di gettare l'intera IQueryable <TEntity> per IQueryable <IActivable> al posto del primo cast:

if (typeof(IActivable).IsAssignableFrom(typeof(TEntity))) 
{ 
    return ((IQueryable<IActivable>)(this.repository.Get())) 
     .Where(q => q.Active) 
     .Cast<TEntity>(); 
}