Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use ValueInjecter to copy an EntityFramework POCO to a DTO without triggering lazy load lists and properties

I am having an issue with using ValueInjecter to create deep clones of EntityFramework POCO's into similar DTO classes.

In cases where I am injecting from a complicated POCO object with multiple related entities/child entities with navigation properties into a somewhat simpler DTO, ValueInjecter seems to still be touching multiple property values and incurring lazy loading of this data from the database.

I believe that it is ValueInjecter getting the value of every property in a particular source object as it prepares to inject the values into the specified target.

My actual project is fairly complicated, but as an example, I have taken the NerdDinner example and replicated the issue in a much simpler fashion. (NerdDinner is an example of code first dev with EF4 (ScottGu NerdDinner Example).

So I have the two model classes.

public class Dinner
{
    public int DinnerId { get; set; }
    public string Title { get; set; }
    public DateTime EventDate { get; set; }
    public string Address { get; set; }
    public string HostedBy { get; set; }
    public virtual ICollection<RSVP> Rsvps { get; set; }
}

and

public class RSVP
{
    public int RsvpID { get; set; }
    public int DinnerID { get; set; }
    public string AttendeeEmail { get; set; }
    public virtual Dinner Dinner { get; set; }
}

I also created a DTO class:

public class DinnerDTO
{ 
    public int DinnerId { get; set; }
    public string Title { get; set; }
    public DateTime EventDate { get; set; }
    public string Address { get; set; }
    public string HostedBy { get; set; }
}

Note that I do not have the Rsvps collection found in Dinner in my DinnerDTO.

Also of importance, I am using the CloneInjection convention for deep cloning an object. This code is suggested both here on SO, as well as many other sites as an approach to performing a deep clone injection. This code is found here: CloneInjection Code

Now, to emphasize the lazy loading occuring, I went and inserted 10,000 RSVP's for a Dinner with Id = 1.

I then execute the following code:

var dinner = nerdDinners.Dinners.Where(x => x.DinnerId == 1).FirstOrDefault();
DinnerDTO dinnerDTO = new DinnerDTO();
dinnerDTO.InjectFrom<CloneInjection>(dinner);

If I set a breakpoint at the line with the InjectFrom, and step over it, there is a considerable lag as it lazy loads the 10,000 RSVP's up. If I also set a breakpoint in the CloneInjection code at both the Match and the SetValue methods, neither of these are hit until after the loading lag has resolved. This tells me that it must be something internal to ValueInjecter that is incurring the lazy load of the RSVPs property.

Now, if I modify the above code to this: (adding an Include to the query)

var dinner = nerdDinners.Dinners.Where(x => x.DinnerId == 1).Include("RSVPs").FirstOrDefault();
DinnerDTO dinnerDTO = new DinnerDTO();
dinnerDTO.InjectFrom<CloneInjection>(dinner);

This change forces an "Eager Load" of the RSVP's list, and as expected, the lag is in the line with the query, and the InjectFrom line ticks past with no lag at all.

I've read through some vaguely related posts on StackOverflow, a few recommend disabling and then enabling LazyLoading on the datacontext. I tried that, and while it did work, it felt pretty dirty.

I read through this post (Copying NHibernate POCO to DTO without triggering lazy load or eager load) and the related code, his approach seems to be using some NHibernate methods to determine if a property is an uninitialized proxy and somehow strip them out. I have been unable to find anything similar in EF4.

The part of this that really trips me up, is that the Rsvps collection isn't even in my DTO object, I'm not even interested in it's value. This doesn't seem right to me. I don't think the ValueInjecter code should interrogate values of properties that the target object might not even care about.

Is there some means by which I can override this behavior in ValueInjecter? Somehow defer evaluation of property values until it's absolutely certain I want the value, such as in the SetValue method of the ConventionInjection? Then at least it wouldn't be evaluating properties that my DTO doesn't even want.

The best solution I can think of, would be for ValueInjecter, or a custom convention, to somehow be able to detect an unloaded lazy load property, and instead of evaluating it, it would instead just set that property to null on the target. I don't think that is possible though.

Is there some better approach through EF that I should be using? I don't want to Eager Load everything in the database.

Am I completely off, and the problem isn't in ValueInjecter at all?

* EDIT * I have found a solution and answered this question, I am still curious if I'm just doing it wrong, or if there is a still better approach.

like image 926
thornhill Avatar asked Feb 10 '13 01:02

thornhill


1 Answers

I feel like I found a satisfactory answer to my own question, and so I'm just going to close this out myself. I ended up doing two things.

Firstly I disabled Lazy Loading entirely on the dbContext. Something akin to this in the dbContext constructor.

this.Configuration.LazyLoadingEnabled = false;

I wasn't really using the lazy load feature of EF, so turning it off was no big loss. This just means that if I want related entities to be populated, I have to specify them in an Include on my query. No big deal.

The other thing I did was rework the deep clone injection convention to use the SmartConventionInjection code found here SmartConventionInjection source. Besides being an even faster injection than the base injection, it also doesn't touch property values until the SetValue call, so even if I did have some lazy loading properties, they wouldn't be touched unless the DTO also had that property.

like image 51
thornhill Avatar answered Oct 13 '22 09:10

thornhill