2014-07-04 8 views
15

Innanzitutto, spec. Usiamo MVC5, .NET 4.5.1 e Entity Framework 6.1.C'è un modo per usare `dynamic` nell'albero di espressione lambda?

Nella nostra applicazione aziendale MVC5 abbiamo un sacco di codice CRUD ripetitivo. Il mio compito è "automatizzare" la maggior parte di esso, il che significa estrarlo nelle classi base e renderlo riutilizzabile. Al momento, ho classi base per controller, modelli di vista e modelli di entità EF6.

La mia classe di base astratta che tutte le entità EF6 ereditano:

public abstract class BaseEntity<TSubclass> 
    where TSubclass : BaseEntity<TSubclass> 
{ 
    public abstract Expression<Func<TSubclass, object>> UpdateCriterion(); 
} 

UpdateCriterion metodo viene utilizzato in AddOrUpdate metodo contesto del database. Ho un parametro generico per sottoclassi perché UpdateCriterion deve restituire un'espressione lambda che utilizza un tipo di sottoclasse esatto, non un'interfaccia o una classe base. Una sottoclasse estremamente semplificata attuazione di questa classe di base astratta sarebbe simile a questa:

public class Worker : BaseEntity<Worker> 
{ 
    public int ID { get; set; } 
    public int Name { get; set; } 

    public override Expression<Func<Worker, object>> UpdateCriterion() 
    { 
     return worker => worker.ID; 
    } 
} 

Dopo di che, in SaveOrUpdate azione del mio controller di base, avrei codice come questo:

public ActionResult Save(TViewModel viewModel) 
{ 
    if (ModelState.IsValid) 
    { 
     var entityModel = viewModel.ConstructEntityModel(); 
     db.Set<TEntityModel>().AddOrUpdate<TEntityModel>(entityModel.UpdateCriterion(), entityModel); 
     db.SaveChanges(); 
    } 
} 

Grazie a ciò, le sottoclassi del controller di base non hanno bisogno di implementare il metodo Save come hanno fatto prima. Ora, tutto questo funziona e funziona davvero bene nonostante la sintassi funky (intendo, class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass>, sul serio?).

Ecco il mio problema. Per la maggior parte del campo entità ID è la chiave, ma per alcuni non lo è, quindi non posso generalizzare correttamente con un'implementazione di superclasse. Quindi per ora, ogni sottoclasse di entità implementa il proprio UpdateCriterion. Ma poiché la maggior parte delle entità (90% +) e => e.ID è l'implementazione corretta, ho un sacco di duplicazioni. Quindi voglio riscrivere la classe base all'entità di qualcosa di simile:

public abstract class BaseEntity<TSubclass> 
    where TSubclass : BaseEntity<TSubclass> 
{ 
    public virtual Expression<Func<TSubclass, object>> UpdateCriterion() 
    { 
     return entity => ((dynamic)entity).ID; 
    } 
} 

L'intento è quello di fornire implementazione predefinita che utilizza ID come chiave, e consentire sottoclassi di ignorare che se usano una chiave diversa. Non riesco a utilizzare un'interfaccia o una classe base con campo ID perché non tutte le entità ce l'hanno. Ho pensato di usare dynamic per estrarre il campo ID, ma ho il seguente errore: Error: An expression tree may not contain a dynamic operation.

Quindi, qualche idea su come fare questo? Riflessione funzionerebbe nella base UpdateCriterion?

risposta

1

No, non è possibile utilizzare la dinamica in una query Linq to Entities. Ma è possibile creare l'espressione Lambda in fase di esecuzione.

public virtual Expression<Func<TSubclass, object>> UpdateCriterion() 
{ 
    var param = Expression.Parameter(typeof(TSubclass)); 
    var body = Expression.Convert(Expression.Property(param, "ID"), typeof(object)); 

    return Expression.Lambda<Func<TSubclass, object>>(body, param); 
} 

Se il tipo TSubclass non ha un Expression.Property proprietà ID (param, "ID") sarà un'eccezione.

Inoltre, è possibile utilizzare MetadataWorkspace dal modello entità per ottenere la colonna Chiave primaria per TSubclass.

+0

Grazie, questo era esattamente quello di cui avevo bisogno! – Davor

+0

Oh, inoltre, sai come aggiungere più campi in quella parte del corpo? come 'e => new {e.ID, e.Name}'? – Davor

+0

Hai imparato come costruire 'e => new {e.ID, e.Name}?' Con le classi di espressione? – codeworx

1

Se si definisce la classe BaseEntity, è possibile aggiungere una proprietà di sola lettura virtuale che restituisce la proprietà ID effettiva. Credo che EF consideri le proprietà di sola lettura come "calcolate", quindi non vengono archiviate nel db.

public abstract class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass> 
{ 
    public abstract object ID { get; } 
    public virtual Expression<Func<TSubclass, object>> UpdateCriterion() 
    { 
     return entity => entity.ID; 
    } 
} 

public partial class Foo : BaseEntity<Foo> 
{ 
    public Int32 FooId { get; set; } 
    public override object ID { get { return FooId; } } 
} 

Solo un pensiero - ho provato solo la compilazione in LINQPad e controllando il valore di una chiamata a UpdateCriterion. :)

1

Dai uno sguardo allo this answer. Usa la riflessione per ottenere la proprietà dell'ID. Penso che risolve il problema:

public static object GetPropValue(object src, string propName) 
{ 
    return src.GetType().GetProperty(propName).GetValue(src, null); 
} 

Poi sostituire l'espressione lambda da

return entity => GetPropValue(entity, "ID"); 

io non ho provato, perché non ho il codice completamente funzionante per testarlo. Se funziona, fatecelo sapere.

+1

Sono abbastanza sicuro che non ha bisogno del valore, ha bisogno dell'espressione. – codeworx

+0

nessun problema. a Lambda Exp può avere chiamate di metodo (ad eccezione di alcuni casi specifici, ad esempio un'espressione compilata in SQL) –

+0

Sì, ma in questo caso il risultato viene utilizzato per il metodo AddOrUpdate di un DbSet. E questo metodo ha bisogno di PropertyExpression. – codeworx