Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to use `dynamic` in lambda expression tree?

First, spec. We use MVC5, .NET 4.5.1, and Entity framework 6.1.

In our MVC5 business application we have a lot of repetitive CRUD code. My job is to "automate" most of it, which means extracting it to base classes and making it reusable. Right now, I have base classes for controllers, view models and EF6 entity models.

My abstract base class that all EF6 entities inherit:

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

UpdateCriterion method is used in AddOrUpdate method of database context. I have a generic parameter for subclasses because UpdateCriterion needs to return lambda expression that uses exact subclass type, not an interface or base class. An extremely simplified subclass implementing this abstract base class would look like this:

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

After that, in SaveOrUpdate action of my base controller, I would have code like this:

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

Thanks to that, subclasses of the base controller don't need to implement Save method themselves, as they did before. Now, all of this works, and it actually works really well despite the funky syntax (I mean, class BaseEntity<TSubclass> where TSubclass : BaseEntity<TSubclass>, seriously?).

Here comes my problem. For most of the entities field ID is the key, but for some it isn't, so I can't generalise properly with a superclass implementation. So for now, every entity subclass implements it's own UpdateCriterion. But, since for most (90%+) entities e => e.ID is the correct implementation, I have a lot of duplication. So I want to rewrite the entity base class to something like this:

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

The intention is to provide default implementation that uses ID as key, and allow subclasses to override it if they use a different key. I can't use an interface or a base class with ID field because not all entities have it. I thought I'd use dynamic to pull out ID field, but I get following error: Error: An expression tree may not contain a dynamic operation.

So, any idea on how to do this? Would reflection work in base UpdateCriterion?

like image 983
Davor Avatar asked Jul 04 '14 13:07

Davor


2 Answers

No, you cannot use dynamic in a Linq to Entities query. But you can build the Lambda Expression at runtime.

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

If the TSubclass type does not have an ID property Expression.Property(param, "ID") will throw an exception.

Additionally you could use the MetadataWorkspace from your entity model to get the Primary Key column for TSubclass.

like image 141
codeworx Avatar answered Nov 20 '22 22:11

codeworx


If you are defining the BaseEntity class, you could add a virtual read only property that returns the actual ID property. I beleive EF treats read only properties as "computed", so they are not stored to the 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; } } 
}

Just a thought - I only tried compiling in LinqPad and checking the value of a call to UpdateCriterion. :)

like image 43
Kenned Avatar answered Nov 20 '22 22:11

Kenned