3

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

Community
  • 1
  • 1
undefined
  • 33,537
  • 22
  • 129
  • 198
  • Does it work with your current code? – Keyur PATEL Sep 20 '16 at 04:04
  • `Me = x.Me.ToViewModel()` doesnt work, if you would like a demo app of the rest working I can post to GH – undefined Sep 20 '16 at 04:06
  • Actually it would help to see your `Person` model and `PersonModel`, not the full code, but the relevant parts. – Keyur PATEL Sep 20 '16 at 04:07
  • A bit far-fetched, but does this work: `Me = x.Friends.AsQueryable().ToViewModels().Where(n => n.Name = x.Me.Name).Single()` – Keyur PATEL Sep 20 '16 at 04:11
  • Full working code in a console app here: https://github.com/lukemcgregor/ExtensionMethodProjection – undefined Sep 20 '16 at 04:12
  • @KeyurPATEL yes `Me = x.Friends.AsQueryable().ToViewModels().Where(n => n.Name == x.Me.Name).FirstOrDefault()` that would run but it doesnt really solve the problem. I could probably even rewite the code to use the reverse nav properties and a where clause but it make my code complex and less readable. I also couldnt hide this behind a helper – undefined Sep 20 '16 at 04:15
  • Take a look at these: http://stackoverflow.com/a/3850254/6741868 and http://stackoverflow.com/a/31464364/6741868. They suggest that linq won't allow methods unless you force it behind `.AsEnumerable()` or `.AsQueryable()` in your case. – Keyur PATEL Sep 20 '16 at 04:20
  • @KeyurPATEL yeah thats definitally the case, this question is about how i would go about unwrapping an extension method onto the existing queryable. Its really about how the underlying expression visitor works in relation to an assignment such as `Me = new ProfileModel { Name = x.Me.Name }` – undefined Sep 20 '16 at 04:24
  • Ah, I misunderstood your question then. Hope somebody can answer this one for you, requires deep understanding. – Keyur PATEL Sep 20 '16 at 04:28
  • @KeyurPATEL yeah its pretty niche, it requires an understanding of how expression trees are working in relation to an assignment like this – undefined Sep 20 '16 at 04:29
  • Doesn't seem like you are taking advantage of an IQueryable anyway, so why not just use IEnumerable? – jamesSampica Sep 20 '16 at 04:30
  • @Shoe in real lifes, my projections get much more complex. This is a way of not over/underfetching. If i were to do this in an enumerable I would also need to know the include chains each projection required. – undefined Sep 20 '16 at 04:32

1 Answers1

3

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

Community
  • 1
  • 1
MBoros
  • 1,090
  • 7
  • 19
  • you could also directly convert on such constructs to the desired target expression – MBoros Sep 20 '16 at 19:21
  • This looks awesome, you referred to `ReplacementInExpressionTreesAttribute`, what would this look like? I assume this is something I would need to write? It looks like this is supposed to replace the method call with the expression listed when used in a tree. This is exactly what im after :) – undefined Sep 27 '16 at 02:41
  • Ok ive built something that does this replacement, I had to put it into my existing visitor rather than using a new one like `MultiParamReplaceVisitor`. It works for doing that replacement but it breaks if there are any other extension methods under the replaced lambda. I think this is probably out of the scope of this question so I will ask another. Code of what I actually did here: https://github.com/lukemcgregor/ExtensionMethodProjection/blob/master/ExtensionMethodProjection/Expandable.cs – undefined Oct 05 '16 at 00:51
  • Have asked new question about my specific bug here: http://stackoverflow.com/questions/39864270/expression-visitor-only-calling-visitparameter-for-some-lambda-expressions would appreciate if you had any ideas. – undefined Oct 05 '16 at 01:58