What could be the reason my base entity is not loaded in extension method?

c# entity-framework entity-framework-core kendo-grid linq

Question

I am having difficulty in Kendo Grid and Linq usage.I am using kendo only in backend to simplfiy filtering and sorting.Everything was working fine until I touched an objet which has related objects.My objects base objects not loaded in my extension method but if I use object initilization in linq select method it works fine. Maybe my linq knowledge is not enough but I want to learn what is the trick happening here ? Any help would be greatly appreciated.

My Extension Method Which gives me the ViewModel :

public static MeetingRoomCultureListViewModel ToMeetingRoomCultureListViewModel(this MeetingRoomCulture meetingRoomCulture) {

        var viewModel = new MeetingRoomCultureListViewModel();

        viewModel.Id = meetingRoomCulture.BaseEntityId;

        viewModel.CancellationDuration = meetingRoomCulture.BaseEntity.CancellationDuration;

        viewModel.MinimumMeetingDuration = meetingRoomCulture.BaseEntity.MinimumMeetingDuration;
        viewModel.OnlyAdminsCanReserve = meetingRoomCulture.BaseEntity.OnlyAdminsCanReserve;
        viewModel.Price = meetingRoomCulture.BaseEntity.Price;
        viewModel.SaltoLockId = meetingRoomCulture.BaseEntity.SaltoLockId;
        viewModel.Status = meetingRoomCulture.Status;
        viewModel.Capacity = meetingRoomCulture.BaseEntity.Capacity;
        viewModel.CleaningDuration = meetingRoomCulture.BaseEntity.CleaningDuration;
        viewModel.Color = meetingRoomCulture.BaseEntity.Color;
        viewModel.Currency = meetingRoomCulture.BaseEntity.Currency;
        viewModel.IsHidden = meetingRoomCulture.BaseEntity.IsHidden;
        viewModel.LocationId = meetingRoomCulture.BaseEntity.LocationId;
        viewModel.LocationName = meetingRoomCulture.BaseEntity.Location.Name;
        viewModel.MaximumDaysForReservationInFuture = meetingRoomCulture.BaseEntity.MaximumDaysForReservationInFuture;
        viewModel.PictureUrl = meetingRoomCulture.BaseEntity.PictureUrl;
        viewModel.Name = meetingRoomCulture.BaseEntity.Name;
        viewModel.TaxRatio = meetingRoomCulture.BaseEntity.TaxRatio;
        viewModel.MaximumMeetingDuration = meetingRoomCulture.BaseEntity.MaximumMeetingDuration;
        viewModel.CreatedDate = meetingRoomCulture.CreatedDate;
        viewModel.LastModifiedDate = meetingRoomCulture.LastModifiedDate;
        return viewModel; 
} 

Here this BaseEntity is not loading and I get null reference exception.

And my api method :

 public IActionResult Get([FromQuery] [DataSourceRequest] DataSourceRequest request) {
        var ds = _dbContext.MeetingRoomCultures.AsNoTracking().Include(x => x.BaseEntity).ThenInclude(x => x.Location).CultureFilter(CurrentCulture)
            .Select(x => x.ToMeetingRoomCultureListViewModel()).ToDataSourceResult(request);
        return Ok(ds);
    }

However If I use object initialization everything works fine and Base Entity is loaded no exception happens .Like below :

 public IActionResult Get([FromQuery] [DataSourceRequest] DataSourceRequest request) {
        var ds = _dbContext.MeetingRoomCultures.AsNoTracking().Include(x => x.BaseEntity).ThenInclude(x => x.Location).CultureFilter(CurrentCulture)
            .Select(x => new MeetingRoomCultureListViewModel()
            {
                Id = x.BaseEntityId,

                CancellationDuration = x.BaseEntity.CancellationDuration,

                MinimumMeetingDuration = x.BaseEntity.MinimumMeetingDuration,
                OnlyAdminsCanReserve = x.BaseEntity.OnlyAdminsCanReserve,
                Price = x.BaseEntity.Price,
                SaltoLockId = x.BaseEntity.SaltoLockId,
                Status = x.Status,
                Capacity = x.BaseEntity.Capacity,
                CleaningDuration = x.BaseEntity.CleaningDuration,
                Color = x.BaseEntity.Color,
                Currency = x.BaseEntity.Currency,
                IsHidden = x.BaseEntity.IsHidden,
                LocationId = x.BaseEntity.LocationId,
                LocationName = x.BaseEntity.Location.Name,
                MaximumDaysForReservationInFuture = x.BaseEntity.MaximumDaysForReservationInFuture,
                PictureUrl = x.BaseEntity.PictureUrl,
                Name = x.BaseEntity.Name,
                TaxRatio = x.BaseEntity.TaxRatio,
                MaximumMeetingDuration = x.BaseEntity.MaximumMeetingDuration,
                CreatedDate = x.CreatedDate,
                LastModifiedDate = x.LastModifiedDate
            }).ToDataSourceResult(request);
        return Ok(ds);
    }
1
2
9/12/2018 7:43:36 AM

Accepted Answer

There are several reasons.

First, both queries use projection (Select) to non entity type, hence fall into Ignored includes category:

If you change the query so that it no longer returns instances of the entity type that the query began with, then the include operators are ignored.

The difference is where the Select is evaluated. Custom (extension) methods cannot be translated to SQL, hence are executed on the client. Since includes are ignored, you get null reference navigation properties and null or empty collection navigation properties. While in the second scenario the query is translated to SQL and executed server (database) side. There are no real "objects" or "collections" involved inside the SQL query, just tables and joins.

For more info, see Client vs. Server Evaluation abd How Queries Work (and basically the whole Querying Data related) documentation topics.

To recap, for performance (and many other reasons), always try to create server side queries. Which means not using custom methods at all. If you need to reuse the logic, put it inside Expression<TSource, TResult>, compile a delegate and use it from elsewhere.

For instance:

public static class Selectors
{
    public static readonly Expression<Func<MeetingRoomCulture, MeetingRoomCultureListViewModel>>
    MeetingRoomCultureToListViewModel = source => new MeetingRoomCultureListViewModel
    {
        Id = source.BaseEntityId,

        CancellationDuration = source.BaseEntity.CancellationDuration,

        // the rest ...
    };


    private static readonly Func<MeetingRoomCulture, MeetingRoomCultureListViewModel>
    MeetingRoomCultureToListViewModelFunc = MeetingRoomCultureToListViewModel.Compile();

    public static MeetingRoomCultureListViewModel ToMeetingRoomCultureListViewModel(
        this MeetingRoomCulture source) => MeetingRoomCultureToListViewModelFunc(source);

}

And of course use the expression inside LINQ to Entities queries:

var ds = _dbContext.MeetingRoomCultures // no tracking, no includes
    .CultureFilter(CurrentCulture)
    .Select(Selectors.MeetingRoomCultureToListViewModel) // <--
    .ToDataSourceResult(request);

As mentioned in the comments, 3rd party libraries like AutoMapper can greatly simplify the transformations from / to entity models.

3
9/12/2018 8:49:35 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow