Projection of single entities in EF with extension

2019-07-19 06:40发布

问题:

I like to do projection from my entity models into my view models using extension methods. This means I dont over/under fetch for my models and it makes the code nice and readable. It makes sense that sometimes the projections may include nested models, and I want to get reuse on those sub-projections.

I want to be able to do something like the following:

ctx.People.FiltersAndThings().ToViewModels();//the project my DB Models into view models

Extension methods for actual projection

public static IQueryable<PersonModel> ToViewModels(this IQueryable<Person> entities)
{
    return entities.Select(x => new PersonModel {
        Me = x.Me.ToViewModel(), //this method cannot be translated into a store expression
        Friends = x.Friends.AsQueryable().ToViewModels() //works fine with some magic (tm)
    });
}

public static IQueryable<ProfileModel> ToViewModels(this IQueryable<Profile> entities)
{
    return entities.Select(x => new ProfileModel { Name = x.Name });
}


public static ProfileModel ToViewModel(this Profile entity)
{
    return new ProfileModel { Name = entity.Name };
}

When using a Queryable (eg Friends = x.Friends.AsQueryable().ToViewModels()) we can use some magic to flatten this to an expression (see https://stackoverflow.com/a/10726256/1070291, answer by @LordTerabyte) But when we are doing an assignment with a new clause (eg Me = new ProfileModel { Name = x.Me.Name }) its not an expression so if we bundle this under an extension method (eg Me = x.Me.ToViewModel()) we can't flatten this to an expression.

How does an assignment to a new object work under the scenes in EF?

Is there a way to do convertion to a new object via an extension method?

Full demo code here: https://github.com/lukemcgregor/ExtensionMethodProjection

Edit:

I now have a blog post (Composable Repositories - Nesting Extensions) and nuget package to help with nesting extension methods in linq

回答1:

Take a look at this answer. It does a very similar thing what you want. Basically you would define your transformation as an Expression tree, e.g:

public static Expression<Func<Profile, ProfileModel>> ToProfileViewModel()
{
    return entity => new ProfileModel { Name = entity.Name };
}

And then do invocations of this (e.g. ExpressionsHelper.ToProfileViewModel().AsQuote()(p)).

If you prefer, you can modify the visitors, to allow a nicer syntax. Something along the lines:

[ReplacementInExpressionTrees(MethodName=nameof(ExpressionsHelper.ToProfileViewModel))]
public static ProfileModel ToViewModel(this Profile profile)
{
    // this implementation is only here, so that if you call the method in a non expression tree, it will still work
    return ExpressionsHelper.ToProfileViewModel().Compile()(profile); // tip: cache the compiled func!

Now you need to create a visitor, that checks all method calls, and when finds a method with this attribute, it changes the whole call to ExpressionsHelper.ToProfileViewModel().AsQuote()(profile). This is as an exercise for you :) }