Ho cercato di incapsulare il mapping degli oggetti in un repository di dati di progetto. Forse EF fornirà il livello di astrazione richiesto ma, per una serie di motivi, sto usando Linq in SQL al momento. Il seguente codice si propone di riportare gli utenti nel database come un elenco di oggetti ModUser, dove ModUser è POCO che il repository espone:Qual è il modo migliore per incapsulare l'accesso ai dati SQL da Linq?
public List<ModUser> GetUsers() {
Users.Select(MapUser).ToList();
}
public Expression<Func<User, ModUser>> MapUser {
get {
return u => new ModUser() {
UserId = u.User_Id,
UserResources = u.Resources(MapResource)
}
}
}
public Expression<Func<Resource, ModResource>> MapResource { ...
Il codice fallirà come io non posso chiamare l'espressione MapResource dato che sono provando a chiamarlo dall'interno di un'altra espressione. Sono riuscito ad aggirare questo sostituendo 'MapResource' con u => new ModResource(), quindi utilizzando ExpressionVisitor per trovare questo nodo segnaposto e sostituirlo con l'espressione MapResource.
Ho anche problemi simili quando provo ad assegnare una proprietà di ModUser con un'espressione che coinvolge una singola proprietà, ad esempio UserResource = MapResource. Sono riuscito a risolvere questo secondo problema combinando manualmente le espressioni richieste utilizzando i metodi sulla classe Expression.
mi rendo conto che avrei potuto cambiare il codice qui sopra per
UserResources = u.Resources(r => MapResource.Compile().Invoke(r));
Ma poi la query SQL finale prodotta sarà necessario ottenere tutti gli attributi di r, non solo quelli necessari per MapResouce, visto che siamo ora si tratta di una funzione. Inoltre, se MapResouce richiede l'accesso a ulteriori tabelle su r non sarà possibile poiché viene utilizzato come funzione non come espressione. Potrei impostare DeferredLoadingEnabled su true ma questo genererebbe una moltitudine di singole query piuttosto che modificare la query principale per unirmi a qualsiasi tabella richiesta.
Qualcuno sa se queste operazioni diventeranno più facili nelle versioni future di .NET o sto andando in questo modo nel modo sbagliato? Mi piacciono molto le funzionalità di Linq ed Expression, vorrei solo poterle impiegare usando un codice più leggibile.
Aggiornato
pensato che avrei potuto aggiungere alcuni esempi di come ho fatto le espressioni più componibili. Non sono concisi, ma ottengono il lavoro fatto.
public Expression<Func<User, ModUser>> MapUser {
get {
Expression<Func<User, ModUser>> mapUser = u => new ModUser() {
UserId = u.User_Id,
UserResources = u.Resources(r => new ModResource())
};
return mapUser.MapResources(this);
}
}
public Expression<Func<Resource, ModResource>> MapResource { ... }
public static Expression<Func<T0, T1>> MapResources<T0, T1>(this Expression<Func<T0, T1>> exp, DataContext dc) {
return exp.Visit<MethodCallExpression, Expression<Func<T0, T1>>>(m => {
if(m.Arguments.Count > 1 && m.Arguments[1].Type == typeof(Func<DataContext.Resource, ModResource>)) { //Find a select statement that has the sub expression as an argument
//The resource mapping expression will require the Resource object, which is obtained here
ParameterExpression resourceParam = ((LambdaExpression)m.Arguments[1]).Parameters[0];
return Expression.Call(m.Method, m.Arguments[0], //The first argument is the record selection for the 'select' method
Expression.Lambda<Func<DataContext.Resource, ModResource>>(//Provide the proper mapping expression as the projection for the 'select' method
Expression.Invoke(dc.MapResource, resourceParam),
resourceParam)
);
}
return m;
});
}
Quindi cosa ci faccio qui? Si noti che in questa versione di MapUser non creo correttamente l'oggetto ModResource, ma creo solo una versione fittizia. Quindi chiamo un metodo per i visitatori dell'espressione che cerca la chiamata fittizia e la sostituisce con quella che inizialmente desideravo lì. A me sembra che manchi la sintassi dell'espressione in quanto sono in grado di costruire essenzialmente l'albero delle espressioni che avevo originariamente voluto, ma devo effettivamente consultare l'albero per farlo. Qui di seguito è un'altra soluzione che ho trovato che si occupa del caso singolare:
public Expression<Func<User, ModUser>> MapUser {
get {
Expression<Func<User, ModResource, ModUser>> mapUser = (u, resource) => new ModUser() {
UserId = u.User_Id,
UserResource = resource;
}
return mapUser.CollapseArgument(MapResource, user => user.MainResource);
}
}
public Expression<Func<Resource, ModResource>> MapResource { ... }
public static Expression<Func<T0, T3>> CollapseArgument<T0, T1, T2, T3>(this Expression<Func<T0, T1, T3>> exp, Expression<Func<T2, T1>> exp0, Expression<Func<T0, T2>> exp1) {
var param0 = Expression.Parameter(typeof(T0), "p0");
var argExp = Expression.Invoke(exp0, Expression.Invoke(exp1, param0));
return Expression.Lambda<Func<T0, T3>>(
Expression.Invoke(exp, param0, argExp),
param0);
}
In questo secondo esempio so che posso ottenere i dati di risorse dai dati utente, ma non posso "inline" un'espressione per mostra come fare questo e mappare i dati delle risorse a una risorsa POCO. Ma posso creare manualmente un albero di espressioni che riceve una risorsa POCO già mappata e la usa. Posso quindi creare un'altra espressione che mostra come ottenere i dati grezzi della risorsa dall'utente e un'espressione finale che mostra come mappare i dati della risorsa grezza in una risorsa POCO. È ora concepibile che io possa combinare tutte queste informazioni in un singolo albero di espressioni in un modo che "comprime" il parametro specifico della risorsa poiché posso ottenerlo dal parametro utente principale. Questo è ciò che fa il codice sopra.
Quindi ho trovato modi per rendere le espressioni altamente componibili ...Non sembra pulito.
NHibernate @ Chris non hanno usato per un bel po 'di tempo, ma non è che "il codice" si trasferisce a mappature in file XML? – eglasius
Usa Fluent NHibernate - quindi ottieni il codice anziché xml, con tutta la verifica del tempo di refactoring/compile che ti aspetteresti. –
Sono davvero alla ricerca di una soluzione Linq to SQL in quanto il passaggio a NHibernate non è un'opzione per questo progetto. – LaserJesus