Ho il seguente metodo che è pensato per costruirmi su una singola istanza di oggetto, dove le sue proprietà sono costruite chiamando ricorsivamente lo stesso metodo:
public ChannelObjectModel GetChannelObject(Guid id, Guid crmId)
{
var result = (from channelObject in _channelObjectRepository.Get(x => x.Id == id)
select new ChannelObjectModel
{
Id = channelObject.Id,
Name = channelObject.Name,
ChannelId = channelObject.ChannelId,
ParentObjectId = channelObject.ParentObjectId,
TypeId = channelObject.TypeId,
ChannelObjectType = channelObject.ChannelObjectTypeId.HasValue ? GetChannelObject(channelObject.ChannelObjectTypeId.Value, crmId) : null,
ChannelObjectSearchType = channelObject.ChannelObjectSearchTypeId.HasValue ? GetChannelObject(channelObject.ChannelObjectSearchTypeId.Value, crmId) : null,
ChannelObjectSupportingObject = channelObject.ChannelObjectSupportingObjectId.HasValue ? GetChannelObject(channelObject.ChannelObjectSupportingObjectId.Value, crmId) : null,
Mapping = _channelObjectMappingRepository.Get().Where(mapping => mapping.ChannelObjectId == channelObject.Id && mapping.CrmId == crmId).Select(mapping => new ChannelObjectMappingModel
{
CrmObjectId = mapping.CrmObjectId
}).ToList(),
Fields = _channelObjectRepository.Get().Where(x => x.ParentObjectId == id).Select(field => GetChannelObject(field.Id, crmId)).ToList()
}
);
return result.First();
}
public class ChannelObjectModel
{
public ChannelObjectModel()
{
Mapping = new List<ChannelObjectMappingModel>();
Fields = new List<ChannelObjectModel>();
}
public Guid Id { get; set; }
public Guid ChannelId { get; set; }
public string Name { get; set; }
public List<ChannelObjectMappingModel> Mapping { get; set; }
public int TypeId { get; set; }
public Guid? ParentObjectId { get; set; }
public ChannelObjectModel ParentObject { get; set; }
public List<ChannelObjectModel> Fields { get; set; }
public Guid? ChannelObjectTypeId { get; set; }
public ChannelObjectModel ChannelObjectType { get; set; }
public Guid? ChannelObjectSearchTypeId { get; set; }
public ChannelObjectModel ChannelObjectSearchType { get; set; }
public Guid? ChannelObjectSupportingObjectId { get; set; }
public ChannelObjectModel ChannelObjectSupportingObject { get; set; }
}
questo è il collegamento a un database SQL utilizzando Entity Framework Core 2.1.1
Mentre funziona tecnicamente, causa un sacco di richieste di database da realizzare - me ne rendo conto a causa delle ToList(
) e First()
ecc.
Tuttavia, a causa della natura dell'oggetto, posso creare un enorme oggetto IQueryable<anonymous>
con a from.... select new {...}
e chiamare First
su di esso, ma il codice era lungo più di 300 righe andando solo a 5 livelli nella gerarchia, quindi sto cercando di sostituirlo con qualcosa come il codice sopra, che è molto più pulito, anche se molto più lento ..
ChannelObjectType, ChannelObjectSearchType, ChannelObjectSupportingObject
Tutte ChannelObjectModel
istanze e i campi di ChannelObjectModel
sono un elenco di istanze di ChannelObjectModel
.
La query impiega circa 30 secondi per essere eseguita al momento, che è troppo lenta e si trova anche su un piccolo database localhost, quindi peggiorerà solo con un numero maggiore di record db e genererà un sacco di chiamate al database quando la eseguirò .
Il codice delle 300+ linee genera molte meno query ed è ragionevolmente veloce, ma è ovviamente un codice orribile, orribile (che non ho scritto!)
Qualcuno può suggerire un modo in cui posso ricorsivamente costruire un oggetto in un modo simile al metodo di cui sopra, ma tagliare drasticamente il numero di chiamate al database in modo che sia più veloce?
Lavoro con EF6, non con Core, ma per quanto ne so, valgono le stesse cose qui.
Prima di tutto, sposta questa funzione nel tuo repository, in modo che tutte le chiamate condividano l'istanza di DbContext.
In secondo luogo, utilizza Includi sul tuo DbSet sulle proprietà per caricarle con ansia:
ctx.DbSet<ChannelObjectModel>()
.Include(x => x.Fields)
.Include(x => x.Mapping)
.Include(x => x.ParentObject)
...
Una buona pratica è di rendere questa una funzione del contesto (o del metodo di estensione) chiamata ad esempio BuildChannelObject () e dovrebbe restituire IQueryable - solo gli include.
Quindi puoi iniziare la parte ricorsiva:
public ChannelObjectModel GetChannelObjectModel(Guid id)
{
var set = ctx.BuildChannelObject(); // ctx is this
var channelModel = set.FirstOrDefault(x => x.Id == id); // this loads the first level
LoadRecursive(channelModel, set);
return channelModel;
}
private void LoadRecursive(ChannelObjectModel c, IQueryable<ChannelObjectModel> set)
{
if(c == null)
return; // recursion end condition
c.ParentObject = set.FirstOrDefault(x => x.Id == c?.ParentObject.Id);
// all other properties
LoadRecursive(c.ParentObject, set);
// all other properties
}
Se tutto questo codice utilizza la stessa istanza di DbContext, dovrebbe essere abbastanza veloce. In caso contrario, puoi usare un altro trucco:
ctx.DbSet<ChannelObjectModel>().BuildChannelObjectModel().Load();
Carica tutti gli oggetti nella cache di memoria di DbContext. Sfortunatamente, muore con l'istanza di contesto, ma rende quelle chiamate ricorsive molto più veloci, dal momento che non viene effettuato alcun arresto del database.
Se il problema persiste, è possibile aggiungere AsNoTracking()
come ultima istruzione di BuildChannelObjectModel ().
Se il problema persiste, è sufficiente implementare la cache di memoria dell'applicazione di tali oggetti e utilizzarla al posto di eseguire query sul database ogni volta: questo funziona perfettamente se l'app è un servizio che può avere un avvio prolungato, ma quindi funzionare rapidamente.
Un altro approccio è quello di abilitare il caricamento lento contrassegnando le proprietà di navigazione come virtuali, ma ricorda che il tipo restituito sarà derivato dal proxy anonimo, non dal tuo originale ChannelObjectModel! Inoltre, le proprietà vengono caricate solo fino a quando non si dispone del contesto, dopo di che si ottiene un'eccezione. Caricare tutte le proprietà con il contesto e quindi restituire l'oggetto completo è anche un po 'complicato - il modo più semplice (ma non il migliore!) Di farlo per serializzare l'oggetto su JSON (ricorda i riferimenti circurali) prima di restituirlo.
Se ciò non ti soddisfa, passa a nHibernate che, per impostazione predefinita, ha una cache a livello di applicazione.