2009-02-28 7 views
5

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.

risposta

0

Penso che se si desidera utilizzare POCO, Linq to SQL non è la scelta migliore. Penso che probabilmente starai molto meglio usando qualcosa come NHibernate. L'uso di Linq in SQL con POCO significa che si sta costruendo un livello sopra il livello dati (da Linq a SQL) in cima al database. Con NHibernate, dovresti creare tutto il tuo codice e collegarlo direttamente al database. Meno strati == meno codice == meno lavoro.

+0

NHibernate @ Chris non hanno usato per un bel po 'di tempo, ma non è che "il codice" si trasferisce a mappature in file XML? – eglasius

+0

Usa Fluent NHibernate - quindi ottieni il codice anziché xml, con tutta la verifica del tempo di refactoring/compile che ti aspetteresti. –

+0

Sono davvero alla ricerca di una soluzione Linq to SQL in quanto il passaggio a NHibernate non è un'opzione per questo progetto. – LaserJesus

1

Il modo in cui Linq To SQL supporta POCO è leggermente diverso.

Per raggiungere l'ignoranza di persistenza, si utilizzerà un file di mapping che descrive come viene mappato un modUser (colonne, associazioni ecc.) E non il progettista LTS. Quando si crea un nuovo contesto, si passa il file di mapping XML come XMLMappingSource.

In questo modo, LTS restituirà gli oggetti dal database.

Ho letto qua e là che definire le proprietà dell'associazione di raccolta come proprietà di lettura/scrittura di tipo IList (di T) è sufficiente per LinqToSQL per fornire il caricamento lazy su tali raccolte, ma non l'ho provato, quindi non posso garantire per questo.

Entity Framework sarà ancora peggio per il supporto POCO nella sua versione attuale (in pratica nessuno in quanto la maggior parte delle persone comprende il termine POCO).

A questo si applicano tutte le normali limitazioni LTS, quindi nessuna mappatura "Oggetto valore". Se vuoi qualcosa di un po 'più lontano dal supporto di database e POCO, allora devi dare un'occhiata a NHibernate.

+0

Un file di mappatura non avrà la potenza espressiva di cui ho bisogno. L'unico modo che posso immaginare di fare questo è usare una serie di espressioni componibili. In realtà ho questo lavoro e sto per aggiornare il post per mostrare un esempio. Non è pulito come vorrei. – LaserJesus

1

Ok devo ammettere che non ho davvero finito di leggere la domanda dell'OP (sorriso imbarazzato) ma sapevi che puoi usare gli attributi Linq-to-SQL per decorare qualsiasi oggetto POCO? Non devi usare il designer.

Ecco un esempio casuale dal codice che è aperto di fronte a me in questo momento. Questo è un POCO chiamato "Prodotto" che ha alcuni attributi applicati che gli permetteranno di interagire con un DataContext Linq-to-SQL.

HTH

using System; 
using System.Collections.Generic; 
using System.Data.Linq; 
using System.Data.Linq.Mapping; 
using System.Linq; 
using System.Web; 

namespace Redacted.Site.Models.Store 
{ 
    /// <summary> 
    /// A "Product" is a good for purchase at the store. 
    /// </summary> 
    [Table(Name = "s.products")] 
    public partial class Product 
    { 
     /// <summary>Gets or sets the PK of the object/row.</summary> 
     [Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL")] 
     public Int32 ID { get; set; } 

     /// <summary>Gets or sets the Title.</summary> 
     [Column(Name = "title", DbType = "NVARCHAR(500) NOT NULL")] 
     public String Title { get; set; } 

     /// <summary>Gets or sets the Lede.</summary> 
     [Column(Name = "lede", DbType = "NVARCHAR(MAX) NOT NULL")] 
     public String Lede { get; set; } 

     /// <summary>Gets or sets the Description.</summary> 
     [Column(Name = "description", DbType = "NTEXT NOT NULL")] 
     public String Description { get; set; } 

     /// <summary>Gets or sets the Price.</summary> 
     [Column(Name = "price", DbType = "FLOAT NOT NULL")] 
     public Double Price { get; set; } 

     /// <summary>Gets or sets the FK to the <see cref="Department"/>.</summary> 
     [Column(Name = "department_id", DbType = "TINYINT NOT NULL")] 
     public Byte DepartmentID { get; set; } 

     /// <summary>Gets or sets the date/time the product was released to the store.</summary> 
     [Column(Name = "released_on_utc", DbType = "DATETIME NOT NULL")] 
     public Int32 ReleasedOnUtc { get; set; } 

    } 
} 
+0

È possibile utilizzare questo approccio per popolare i POCO che derivano i loro attributi da più tabelle, a volte attraverso relazioni non banali? La mia prima risposta è che questo approccio funzionerà solo per mappature molto semplici. Avete collegamenti o esempi di scenari più complessi? – LaserJesus

+0

Sicuro. Il progettista L2S è solo un generatore di codice. Non c'è magia.Quindi puoi fare tutto ciò che il progettista L2S può fare nel tuo codice, con il tuo generatore. Sono in viaggio, ma lo correggerò con un esempio più complesso la prossima settimana. – Portman

+0

La mia preoccupazione è che non sono stato in grado di ottenere ciò che voglio attraverso il designer e ho dovuto trovare mezzi più espressivi. Ma aspetterò e verificherò gli esempi più complessi una volta che sono in piedi, grazie. – LaserJesus

Problemi correlati