1

This works to carve out a DDL object from an Address object from our database:

public class DDL {
    public int?   id   { get; set; }
    public string name { get; set; }
}

List<DDL> mylist = Addresses
    .Select( q => new DDL { id = q.id, name = q.name })
    .ToList();

However, we'd like to keep our POCO to ViewModel mappings in a single place outside of our MVC controller code. We'd like to do something like this:

List<DDL> mylist = Addresses
    .Select( q => new DDL(q))  // <-- constructor maps POCO to VM
    .ToList();

But SQL cannot use the constructor function. The object initializer above doesn't use functions to map fields. Of course you could do .AsEnumerable().Select( q => new DDL(q)), but this selects all the fields in SQL (including the data), sends it to C#, then C# carves out the fields we need (terribly inefficient to transfer data we don't need.)

Any suggestions? We happen to be using Entity Framework 6 to pull data.

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
Zachary Scott
  • 20,968
  • 35
  • 123
  • 205

3 Answers3

2

You can use anonymous types to restrict what to select from the DB and then use those fields to construct your object :

List<DDL> mylist = Addresses
    .Select( q => new { id, name })
    .AsEnumerable()
    .Select(i => new DDL(i.id, i.name) // <- On the Enumerable and not on the Queryable
    .ToList();
Bruno
  • 4,685
  • 7
  • 54
  • 105
  • 1
    You would do better to stick with the OP's suggestion of `AsEnumerable` rather than `ToArray` -- this will save you the expense of creating an array. – phoog Apr 02 '15 at 17:09
  • 2
    You're welcome -- I should have mentioned that if you do that, the SQL call is actually executed when you call `ToList` -- the effect of the `AsEnumerable` call is to cause the `Select` extension method to resolve to `Enumerable.Select` instead of `Queryable.Select`; it doesn't force the query to execute. This doesn't much matter in a single expression like we have here, of course, but if it were broken up into many expressions it could become significant. Also, your `;` is in the wrong place. – phoog Apr 02 '15 at 17:12
  • 1
    If AsEnumerable doesnt query the database, than the second select will throw an exception, regardless of which extension (Enumerable or Queryable) you use. EF cannot convert a parametered constructor into a SQL query. – Pluc Apr 02 '15 at 17:33
  • @Pluc I agree and it is why I initially used `.ToArray()` to pull the data...can't test it right now and I was trusting phoog on that one :) – Bruno Apr 02 '15 at 17:35
  • @ibiza It's not a bad idea, but it's essentially what the first example does. – Zachary Scott Apr 02 '15 at 18:44
  • @Pluc Check out this question about how `AsEnumerable` works in this case. http://stackoverflow.com/questions/17968469/whats-the-differences-between-tolist-asenumerable-asqueryable – juharr Apr 02 '15 at 18:48
  • @juharr You are right about not throwing an exception. However, it would cause bad performance as it queries the entire row. There is no projection. I've just tested it. – Pluc Apr 02 '15 at 19:52
2

All you need is to define the expression somewhere and use it. For example, in your ViewModel as a static read-only field.

public class SomeViewModel
{
    public static readonly Expression<Func<SomeEntity, SomeViewModel>> Map = (o) => new SomeViewModel
    {
        id = o.id,
        name = o.name
    }

    public int id { get; set; }
    public string name { get; set; }
}

// Somewhere in your controller
var mappedAddresses = Addresses.Select(SomeViewModel.Map);

I personally made myself a little static Mapper that keeps all the maps and use them for me. The maps are declared in a static initializer in all my ViewModels. The result gives me something that feels like AutoMapper, yet doesn't require the lib or the complicated mapping code (but also won't do any magic for you).

I can write something like this:

MyCustomMapper.Map<Entity, ViewModel>(entity);

and it's overloaded to accept IEnumerables, IQueryables and a single ViewModel. I also added overloads with only 1 generic type (the entity) that accept a type parameter. This was a requirement for me.

Pluc
  • 2,909
  • 1
  • 22
  • 36
  • "It never occurred to me to think of SPACE as the thing that was moving!" – Zachary Scott Apr 02 '15 at 18:50
  • 1
    Do not use `Mapper.Map` on IQueryable objects, you need to use the `Automapper.QueryableExtensions` namespace. You will get very poor SQL queries if you use Mapper.Map. See [Stop using AutoMapper in your Data Access Code](http://www.devtrends.co.uk/blog/stop-using-automapper-in-your-data-access-code) for a message from the author of Automapper where he explains queryable extensions. – Scott Chamberlain Apr 02 '15 at 19:12
  • @ScottChamberlain Where did I say I was using AutoMapper? – Pluc Apr 02 '15 at 19:35
  • @Dr.Zim Sorry to say but I had to google the quote to know what it was. I'm not a Startrek fan :) – Pluc Apr 02 '15 at 19:37
  • @Pluc *"I can write something like this: `Mapper.Map(entity);` and it's overloaded to accept IEnumerables, IQueryables and a single ViewModel."* It is not overloaded to accept IQueryables, `IQueryable` is derived from `IEnumerable` so it accepts the queryable as an enumerable but you get none of the optimizations. If you use `Mapper.Map` it will always query all columns of all the included tables in the query instead of only the ones it needs like when you use `.Project().To()`. – Scott Chamberlain Apr 02 '15 at 19:43
  • @Pluc lol. If we put AutoMapper aside, your answer is very good. It allows me to add delegates in the ViewModels that can convert from their POCOs. It's a very nice substitute for the constructor functions. – Zachary Scott Apr 02 '15 at 19:44
  • I will admit I am the one who did the -1 on your answer, if you took out or corrected the bad advice for using automapper I would be happy to turn it to a +1 for the first half of your answer. – Scott Chamberlain Apr 02 '15 at 19:45
  • Are you serious? I just said I don't use AutoMapper, and you can't say not to use "Mapper.Map" if you don't have my implementation of the method. – Pluc Apr 02 '15 at 19:49
  • @ScottChamberlain There, I renamed it to "MyCustomMapper" because apparently, telling you twice that this class isn't AutoMapper isn't enough. – Pluc Apr 02 '15 at 19:54
  • @ScottChamberlain Your opinion about AutoMapper needs an update: http://stackoverflow.com/a/12365931/861716 – Gert Arnold May 13 '15 at 12:56
1

Are you against using 3rd party libraries? Automapper's QueryableExtensions does exactly what you want.

List<DDL> mylist = Addresses
    .Project().To<DDL>()
    .ToList();

It even has nice features like being able to filter on the transformed object and that filter being performed server side.

List<DDL> mylist = Addresses
    .Project().To<DDL>()
    .Where(d => d.name = "Smith") //This gets translated to SQL even though it was performed on DDL.
    .ToList();
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • What about [Stop Using AutoMapper in your Data Access Code](http://www.devtrends.co.uk/blog/stop-using-automapper-in-your-data-access-code)? – Zachary Scott Apr 02 '15 at 18:40
  • 1
    @Dr.Zim Yes, if you actually read the article they talk about you should use his `Addresses.Project().To()` method of doing it instead of doing `Mapper.Map(Addresses)`. That code got integrated in to the main automapper project as the `Automapper.QueryableExtensions` namespace instead of a separate download. – Scott Chamberlain Apr 02 '15 at 19:06
  • @Dr.Zim did you intend to post these comments on [Pluk's answer](http://stackoverflow.com/a/29418821/80274) instead of mine? It would have made sense if you posted them there (for example why are you said `as well as Scott's`?). – Scott Chamberlain Apr 02 '15 at 19:13
  • you type faster than I. LOL. Your answer is certainly the best if you're using AutoMapper. – Zachary Scott Apr 02 '15 at 19:21