2014-07-04 23 views
15

Po pierwsze, spec. Używamy MVC5, .NET 4.5.1 i Entity framework 6.1.Czy istnieje sposób użycia `dynamic` w drzewie wyrażeń lambda?

W naszej aplikacji biznesowej MVC5 mamy wiele powtarzalnych kodów CRUD. Moim zadaniem jest "zautomatyzowanie" większości z nich, co oznacza wyodrębnienie go do klas bazowych i sprawienie, że będzie można go ponownie wykorzystać. Obecnie mam klasy bazowe dla kontrolerów, modeli widoków i modeli encji EF6.

mój abstrakcyjne klasy bazowej że wszystkie podmioty EF6 dziedziczyć:

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

UpdateCriterion metoda jest stosowana w AddOrUpdate metody kontekście bazy danych. Mam ogólny parametr dla podklas, ponieważ UpdateCriterion musi zwracać wyrażenie lambda, które używa dokładnego typu podklasy, a nie interfejsu lub klasy bazowej. Niezwykle uproszczone podklasa realizacji tej abstrakcyjnej klasy bazowej będzie wyglądać następująco:

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

Po tym, w SaveOrUpdate działania mojego kontrolera bazowej, musiałbym kodu:

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

Dzięki temu, podklasy kontrolera bazowego nie muszą same implementować metody, tak jak poprzednio. Teraz wszystko to działa i działa naprawdę dobrze pomimo funkowej składni (to znaczy, class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass>, poważnie?).

Nadchodzi mój problem. Dla większości jednostek kluczem jest pole ID, ale dla niektórych nie jest, więc nie mogę generalizować poprawnie z implementacją nadklasy. Na razie każda podklasa jednostki implementuje własną UpdateCriterion. Ale ponieważ dla większości (90% +) podmiotów e => e.ID jest prawidłową implementacją, mam dużo duplikacji. Więc chcę przepisać klasę bazową jednostka do czegoś takiego:

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

Intencją jest zapewnienie domyślną implementację, która używa identyfikatora jako klucz i pozwolić podklasy zastąpić go, jeśli użyć innego klucza. Nie mogę używać interfejsu lub klasy bazowej z polem identyfikacyjnym, ponieważ nie wszystkie elementy go mają. Pomyślałem, że będę używał dynamic do wyciągnięcia pola ID, ale dostaję następujący błąd: Error: An expression tree may not contain a dynamic operation.

Więc, jakikolwiek pomysł, jak to zrobić? Czy refleksja będzie działać w bazie UpdateCriterion?

Odpowiedz

1

Nie, nie można użyć dynamicznego w zapytaniu Linq do Entities. Ale można zbudować wyrażenie Lambda w czasie wykonywania.

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

Jeśli typ TSubclass nie ma Expression.Property właściwość ID (parametr „id”) wygeneruje wyjątek.

Dodatkowo można użyć MetadataWorkspace z modelu encji, aby uzyskać kolumnę klucza głównego dla TSubclass.

+0

Dzięki, to było dokładnie to, czego potrzebowałem! – Davor

+0

Och, dodatkowo, czy wiesz, jak dodać wiele pól w tej części ciała? jak 'e => nowy {e.ID, e.Nazwa}'? – Davor

+0

Czy wiesz, jak zbudować 'e => new {e.ID, e.Name}?' Z klasami Expression? – codeworx

1

Jeśli definiujesz klasę BaseEntity, możesz dodać wirtualną właściwość tylko do odczytu, która zwraca rzeczywistą właściwość ID. Uważam, że EF traktuje właściwości tylko do odczytu jako "obliczone", więc nie są one przechowywane w bazie danych.

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

Po prostu myśl - Próbowałem tylko kompilacji w LinqPad i sprawdzania wartości połączenia do UpdateCriterion. :)

1

Spójrz na this answer. Używa refleksji, aby uzyskać właściwość ID. Myślę, że to rozwiąże problem:

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

Następnie należy zastąpić wyrażenia lambda przez

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

nie zostały przetestowane, ponieważ mam kod nie działa w pełni, aby go przetestować. Jeśli zadziała, daj nam znać.

+1

Jestem prawie pewien, że nie potrzebuje wartości, potrzebuje ekspresji. – codeworx

+0

nie ma problemu. a Lambda Exp może wywoływać metody (z wyjątkiem pewnych szczególnych przypadków, np. wyrażenia skompilowanego do SQL) –

+0

Tak, ale w tym przypadku wynik jest używany dla metody AddOrUpdate w DbSet. Ta metoda wymaga właściwości PropertyExpression. – codeworx

Powiązane problemy