Sto avendo difficoltà nell'uso di Kendo Grid e Linq. Sto usando il kendo solo nel backend per semplificare il filtraggio e l'ordinamento. Tutto funzionava bene fino a quando non ho toccato un oggetto che ha oggetti correlati. I miei oggetti base non sono caricati nel mio metodo di estensione ma se Io uso l'inizializzazione dell'oggetto nel metodo di selezione linq funziona correttamente. Forse la mia conoscenza di linq non è abbastanza, ma voglio sapere qual è il trucco che sta succedendo qui? Qualsiasi aiuto sarebbe molto apprezzato.
Il mio metodo di estensione che mi dà il 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;
}
Qui questa BaseEntity non si sta caricando e ottengo un'eccezione di riferimento null.
E il mio metodo API:
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);
}
Tuttavia, se utilizzo l'inizializzazione dell'oggetto, tutto funziona correttamente e l'entità base non viene caricata, non si verifica alcuna eccezione.
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);
}
Ci sono diversi motivi
Innanzitutto, entrambe le query utilizzano la proiezione ( Select
) per il tipo non entità, quindi rientrano nella categoria include ignorata :
Se si modifica la query in modo che non restituisca più istanze del tipo di entità con cui è iniziata la query, gli operatori di inclusione vengono ignorati.
La differenza è dove viene valutata la Select
. I metodi personalizzati (estensione) non possono essere tradotti in SQL, quindi vengono eseguiti sul client. Dal momento che comprende vengono ignorati, si ottiene null
proprietà di navigazione di riferimento e null
proprietà di navigazione raccolta o vuoti. Mentre nel secondo scenario la query viene tradotta in SQL e eseguita lato server (database). Non ci sono veri "oggetti" o "collezioni" coinvolti nella query SQL, solo tabelle e join.
Per maggiori informazioni, consultare Client vs. Server Evaluation abd Come funzionano le query (e in pratica gli argomenti relativi alla documentazione dell'intero query ).
Per ricapitolare, per prestazioni (e molti altri motivi), cerca sempre di creare query lato server. Il che significa non usare affatto metodi personalizzati. Se è necessario riutilizzare la logica, inserirla in Expression<TSource, TResult>
, compilare un delegato e utilizzarlo da un'altra posizione.
Per esempio:
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);
}
E ovviamente usa l'espressione all'interno di LINQ alle query di entità:
var ds = _dbContext.MeetingRoomCultures // no tracking, no includes
.CultureFilter(CurrentCulture)
.Select(Selectors.MeetingRoomCultureToListViewModel) // <--
.ToDataSourceResult(request);
Come menzionato nei commenti, le librerie di terze parti come AutoMapper possono semplificare notevolmente le trasformazioni da / a modelli di entità.