Pytanie Czy istnieje sposób użycia `dynamic` w drzewie wyrażeń lambda?


Po pierwsze, spec. Używamy MVC5, .NET 4.5.1 i Entity framework 6.1.

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.

Moja abstrakcyjna klasa bazowa, w której wszystkie elementy EF6 dziedziczą:

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

UpdateCriterion metoda jest używana w AddOrUpdate metoda kontekstu 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. Bardzo uproszczona podklasa implementująca tę abstrakcyjną klasę bazową 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;
    }
}

Potem, w SaveOrUpdate działanie mojego kontrolera bazowego, powinienem mieć następujący kod:

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ą być implementowane Save sami, tak jak wcześniej. Teraz wszystko to działa i działa naprawdę dobrze pomimo funkowej składni (mam na myśli, class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass>, poważnie?).

Nadchodzi mój problem. Dla większości obszarów jednostek ID jest kluczem, ale dla niektórych nie jest, więc nie mogę generalizować poprawnie z implementacją superklasy. Na razie każda podklasa jednostki implementuje ją UpdateCriterion. Ale, ponieważ dla większości (90% +) podmiotów e => e.ID jest poprawną implementacją, mam dużo duplikacji. Więc chcę przepisać klasę podstawową jednostki na coś takiego:

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

Celem jest dostarczenie domyślnej implementacji, która używa identyfikatora jako klucza, i zezwolenie podklasom na zastąpienie go, jeśli używają innego klucza. Nie mogę używać interfejsu lub klasy bazowej z polem identyfikacyjnym, ponieważ nie wszystkie elementy go mają. Myślałem, że skorzystam dynamic wyciągnąć ID pole, 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 zadziała w bazie UpdateCriterion?


15
2017-07-04 13:30


pochodzenie




Odpowiedzi:


Nie, nie można używać dynamicznego w zapytaniu Linq do Entities. Ale możesz budować 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 właściwości ID Expression.Property (parametr, "ID") wygeneruje wyjątek.

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


1
2017-07-04 17:35



Dzięki, to było dokładnie to, czego potrzebowałem! - Davor
Och, dodatkowo, czy wiesz, jak dodać wiele pól w tej części ciała? lubić e => new {e.ID, e.Name}? - Davor
Czy wiesz, jak budować? e => new {e.ID, e.Name}? z klasami Expression? - codeworx
Tak. Przepraszam za bycie niejasnym. - Davor
W takim przypadku treść będzie wyrazem Expression.MemberInit. Problem jest typem anonimowym. Jeśli napiszesz Lambda z p => new {p.Prop1, p.Prop2}, kompilator utworzy konkretny typ z pasującymi właściwościami. Jeśli utworzysz element MemberInit w czasie wykonywania, typ anonimowy nie istnieje, więc musisz podać poprawny istniejący typ. Expression.MemberInit(Expression.New(typeof(PrimaryKeyWithTwoValues<int,string>)),Expression.Bind(...)); lub utwórz dynamicznie z Reflection.Emit. - codeworx


Jeśli definiujesz klasę BaseEntity, możesz dodać wirtualną własność 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; } } 
}

Tylko jedna myśl - próbowałem tylko kompilacji w LinqPadzie i sprawdzenia wartości połączenia do UpdateCriterion. :)


1
2017-07-04 13:58





Spojrzeć na ta odpowiedź. Używa refleksji, aby uzyskać właściwość ID. Myślę, że rozwiązuje twój problem:

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

Następnie zamieniasz swoje wyrażenie lambda na

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

Nie testowałem, ponieważ nie mam kodu w pełni działającego, aby go przetestować. Jeśli zadziała, daj nam znać.


1
2017-07-04 14:34



Jestem prawie pewien, że nie potrzebuje wartości, potrzebuje ekspresji. - codeworx
nie ma problemu. a Lambda Exp może wywoływać metody (z wyjątkiem niektórych szczególnych przypadków, np. wyrażenia skompilowanego do SQL) - Rafael Brasil
Tak, ale w tym przypadku wynik jest używany do metody AddOrUpdate w DbSet. Ta metoda wymaga właściwości PropertyExpression. - codeworx