Reputation: 25320
I have an Entity Framwork object defined with a Master Detail relationship. There is a navigation property for the detail object collection.
At a later point in the code I'm trying to use AutoMapper to map one of the Master objects to a data transfer object. However the data transfer object needs a Boolean property on it specifying if the record has any detail records.
The map is trying to fill in this Boolean by doing the following:
Mapper.CreateMap<Master, MasterDto>()
.ForMember(dest => dest.HasDetails, src => src.Details.Any())
This works most of the time, but I have one Master record that has over 200,000 details records and when it gets to do this the mapping it is trying to get all of them out of the database before running the .Any() to figure out the collection contains anything. This is taking long enough to time out an ASP.NET connection.
Is there any way to query if the .Details collection contains a value without getting all the detail rows first?
Upvotes: 2
Views: 572
Reputation: 48995
While it may looks hacky, you could catch the ObjectMaterialized
event:
((IObjectContextAdapter)yourDbContext).ObjectContext.ObjectMaterialized += (sender, e) =>
{
var entityAsMaster = e.Entity as Master;
if (entityAsMaster != null)
{
entityAsMaster.HasDetails = this.context
.Entry(entityAsMaster)
.Collection(z => z.Details)
.Query()
.Any();
}
};
(this code could be located in your DbContext
factory).
The obvious advantage is that you don't need to modify your existing mapping/DTO code at all. If HasDetails
doesn't exist in your current entity, you can create it in a partial definition of your class.
Upvotes: 0
Reputation: 37780
When using lazy loading, Any
(as well as other extensions like Count
) loads the whole collection. You can't avoid it. Either use some intermediate type to select results, and map instances of that type to DTO, or select DTO directly:
context
.Masters
.Select(_ => new MasterDto
{
// ...
HasDetails = _.Details.Any()
});
Upvotes: 4
Reputation: 1723
I would imagine that the issue is because Master has been materialised, and in that context, LINQ would operate using LINQ to Objects instead of LINQ to Entities.
The answer is to map to a function that will execute a query using .Any()
src => { return _context.Masters.Where(m => m.Id == src.Id).Any(); }
Might work.Obviously, put your context reference and Id field in.
Upvotes: 0