8

This is something that has bugged me since the shift to EF 6. How do we now map collections through to view models such that mapping changes back are not painful using IEnumerables. Here is a code snippet below demonstrating my problem:

Entity - SS.Entity.Event

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<SS.Entity.User> Broadcasters { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<SS.Entity.User> Viewers { get; set; }

Model - SS.Model.Event

public virtual ICollection<SS.Model.User> Broadcasters { get; set; }
public virtual ICollection<SS.Model.User> Viewers { get; set; }

Mapping Back to Entity After Modification of Collection

Broadcasters = e.Broadcasters.Select(u => new SS.Entity.User
{
    Id = u.Id,
    SkypeId = u.SkypeId,
    Name = u.Name
}).ToList(), // THIS IS THE PROBLEM
Viewers = e.Viewers.Select(u => new SS.Entity.User
{
    Id = u.Id,
    SkypeId = u.SkypeId,
    Name = u.Name
}).ToList() // THIS IS THE PROBLEM

The problem with this is I cannot map an ICollection to another ICollection as Select produces an IEnumerable which makes mapping back properties to EF afterwards a pain as I have to recreate the collection or enumerate it to update it. I know I'm missing something obvious, I have updated the ICollection's to be virtual as outlined in other answers but it is unclear to me how this helps.

Any help would be greatly appreciated!

Gerard

Gerard Wilkinson
  • 1,512
  • 14
  • 33
  • Why not `Broadcasters = e.Broadcasters`? It's very unclear what you're asking. Is there one `User` class, or two? And how is lazy loading related to this? – Gert Arnold Aug 18 '16 at 20:40
  • I tried to make it clearer by adding namespaces. Essentially my question is how do you map back collection changes in EF6. I see no clear way to do this anymore without a ToList(). Therefore there is a significant performance hit when working with large collections. – Gerard Wilkinson Aug 18 '16 at 22:51
  • "....map collections through to view models such that mapping changes back are not painful using IEnumerables"..... this is not clear. What do you mean by "map collections through". As illustrated in your post you are creating new types from your DTOs..... why is this not working for you? –  Aug 18 '16 at 23:03
  • Are you constructing your viewmodels correctly? You can greatly simplify them using this approach: http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc/29135747#29135747 –  Aug 18 '16 at 23:06
  • I've updated the question as I had made a mistake there. Thats a great answer and clearly explains why to use ViewModels. My question is dealing with collections off entities and mapping back updates. – Gerard Wilkinson Aug 18 '16 at 23:33
  • 1
    I don't understand how you expect to execute a conversion (one user type to another) *without* enumerating. The only "challenge" is to make sure you enumerate only once. And maybe you shouldn't bring mega collections into the client. What's the client supposed to do with them? – Gert Arnold Aug 19 '16 at 07:09
  • This was one of the great benefits of the original EF everything was IEnumerables which meant that it worked out what had changed and then executed a query to just update those changes. It seems like MSFT just want you to directly use the EF entities nowadays which is frustrating. – Gerard Wilkinson Aug 19 '16 at 16:39
  • Did you try .AddRange option which I mentioned in answer instead of assigning a new collection directly? – Developer Aug 25 '16 at 00:41

2 Answers2

2

Assuming your ".ToList()" issue is while saving back to database, is this what your are looking for?:

 var event = new SS.Entity.Event {Name = "New Name" and other properties};

 IEnumerable<SS.Entity.User> broadcasters = e.Broadcasters
        .Select(u => new SS.Entity.User
             {
              Id = u.Id,
              SkypeId = u.SkypeId,
              Name = u.Name
           });

 var viewers = e.Viewers.Select(u =>
          new SS.Entity.User
          {
             Id = u.Id,
             SkypeId = u.SkypeId,
              Name = u.Name
          });

 //add broadcasters to event
 event.Broadcasters.AddRange(broadcasters);

 //add viewers to event
 event.Viewers.AddRange(viewers);

 dataContext.Events.Add(event);
 dataContext.SaveChanges();
Developer
  • 6,240
  • 3
  • 18
  • 24
1

On your ViewModel, they can just be IEnumerable<T>, unless you need to be doing a .Add() on them later. That can be whatever you want to be. Also, they do not have to be virtual on the Model because you're not having EF create a derived type of your model (again, unless you have other reason to).

To set them back to what they are in the DTO object, if you just want to set it directly and not check individual properties, you can use .ToList() to have it fulfill the ICollection<T> requirement.

// Assuming "var model" is coming in as a parameter
var station = context.Viewers.First();
station.Broadcasters = model.Broadcasters.Select(b => new User {
    Id = b.Id,
    SkypeId = b.SkypeId,
    Name = b.Name
}).ToList();
station.Viewers = model.Viewers.Select(v => new User {
    Id = v.Id,
    SkypeId = v.SkypeId,
    Name = v.Name
});
krillgar
  • 12,596
  • 6
  • 50
  • 86
  • This used to work in versions before EF6 but no longer works. Instantiating new entities even for mapping back causes new entities to be created for each update rather than just updating the colleciton. – Gerard Wilkinson Aug 18 '16 at 16:43
  • Also ToList() enumerates the collection which if that collection is huge causes performance issues. – Gerard Wilkinson Aug 18 '16 at 16:46
  • The collection is going to be iterated, no matter what. At the very least, your question is very unclear as to what your problem set is. What is happening now with EF6 that wasn't happening in EF5? – krillgar Aug 18 '16 at 16:51
  • If you have the ID of your object set, and the property is set up as `DatabaseGeneratedOption.Identity`, then it will run an UPDATE instead of INSERT regardless of the Tracked State of your Entity. – krillgar Aug 18 '16 at 16:52
  • Its a while since I have looked at Entity Framework. However from what I can remember I used to be able to map collection changes without ToList. I am not necessarily iterating over the collection. For instance if I just remove or add one user to the Viewer or Broadcaster collections. – Gerard Wilkinson Aug 18 '16 at 17:07
  • Yes, you can do that too. However, you said in your question that you want to avoid manually iterating over everything. – krillgar Aug 18 '16 at 17:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/121279/discussion-between-gerard-wilkinson-and-krillgar). – Gerard Wilkinson Aug 18 '16 at 17:13