2009-02-28 10 views
5

Próbowałem hermetyzować mapowanie obiektów w repozytorium danych projektów. Być może EF zapewni wymagany poziom abstrakcji, ale z wielu powodów używam Linq do SQL w tej chwili. Poniższy kod dąży do powrotu użytkowników w bazie danych jako lista obiektów ModUser, gdzie ModUser jest POCO, że repozytorium eksponuje:Jaki jest najlepszy sposób na dołączenie Linq do dostępu do danych SQL?

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 { ... 

Kod zawiedzie, bo nie można nazwać wyrażenie MapResource ponieważ jestem próbując wywołać to z wnętrza innego wyrażenia. Udało mi się ominąć to, zastępując "MapResource" przez u => new ModResource(), następnie używając ExpressionVisitor, aby znaleźć ten symbol zastępczy i zastąpić go wyrażeniem MapResource.

Mam również podobne problemy, gdy próbuję przypisać właściwość ModUser z wyrażeniem obejmującym jedną właściwość, tj. UserResource = MapResource. Udało mi się obejść ten drugi problem, ręcznie łącząc wyrażenia wymagane przy użyciu metod z klasy Expression.

Zdaję sobie sprawę, że mogę zmienić kod powyżej

UserResources = u.Resources(r => MapResource.Compile().Invoke(r)); 

ale kwerenda końcowy SQL produkowany będzie musiał uzyskać wszystkie atrybuty R, nie tylko te potrzebne MapResouce, ponieważ jesteśmy teraz zajmująca się funkcją. Ponadto, jeśli MapResouce będzie wymagać dostępu do kolejnych tabel na r, nie będzie to możliwe, ponieważ jest używane jako funkcja, a nie jako wyrażenie. Mogę ustawić wartość DeferredLoadingEnabled na true, ale to odradza się wiele pojedynczych zapytań, zamiast modyfikować główne zapytanie, aby połączyć się z dowolnymi tabelami.

Czy ktoś wie, czy te operacje staną się łatwiejsze w przyszłych wersjach .NET, czy też podchodzę do tego w niewłaściwy sposób? Bardzo podoba mi się funkcja Linq i Expression, chciałabym móc je wykorzystać, używając bardziej czytelnego kodu.

Updated

Myśl dodam kilka przykładów jak zrobiłem wyrażenia bardziej sk. Nie są zwięzłe, ale wykonują zadanie.

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; 
    }); 
} 

Co więc tutaj robię? Zauważ, że w tej wersji programu MapUser nie tworzę obiektu ModResource poprawnie, po prostu tworzę wersję fikcyjną. Następnie wywołuję metodę wyrażeń, która wyszukuje fałszywego wywołania i zastępuje ją tym, którego początkowo oczekiwałem. Wydaje mi się, że brakuje składni wyrażeń, ponieważ jestem w stanie w zasadzie skonstruować drzewo wyrażenia, które pierwotnie chciałem, ale muszę faktycznie zobaczyć drzewo, aby to zrobić. Poniżej jest kolejnym obejście znalazłem, która zajmuje się liczby pojedynczej:

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); 
} 

W tym drugim przykładzie wiem, że mogę uzyskać dane zasobu z danymi użytkownika, ale nie mogę „inline” wyrażenie do pokaż, jak to zrobić i zmapuj dane zasobów do zasobu POCO. Ale mogę ręcznie utworzyć drzewo wyrażenia, które ma już zmapowane zasoby POCO i używa go. Mogę następnie utworzyć inne wyrażenie pokazujące, jak uzyskać surowe dane zasobów od użytkownika i końcowe wyrażenie, które pokazuje, jak mapować dane surowego zasobu do zasobu POCO zasobów. Można sobie teraz wyobrazić, że mogę połączyć wszystkie te informacje w jedno drzewo wyrażeń w sposób, który "zwija" parametr specyficzny dla zasobu, ponieważ mogę go uzyskać z podstawowego parametru użytkownika. Tak właśnie działa powyższy kod.

Tak więc znalazłem sposoby na tworzenie wyrażeń wysoce kompozycyjnych ...Po prostu nie czuje się czysto.

Odpowiedz

0

Myślę, że jeśli chcesz używać POCO, Linq do SQL nie jest najlepszym wyborem. Myślę, że prawdopodobnie lepiej byłoby użyć czegoś takiego jak NHibernate. Użycie Linq do SQL z POCO oznacza, że ​​tworzysz warstwę na wierzchu warstwy danych (Linq do SQL) na bazie danych. Przy pomocy NHibernate tworzysz cały kod i mapujesz go bezpośrednio do bazy danych. Mniej warstw == mniej kodu == mniej pracy.

+0

@chris NHibernate nie był używany przez jakiś czas, ale nie jest to, że "kod" przeniósł się do mapowania w plikach XML? – eglasius

+0

Użyj Fluent NHibernate - otrzymasz kod zamiast xml, z weryfikacją czasu refaktoryzacji/kompilacji, której możesz się spodziewać. –

+0

Naprawdę szukam rozwiązania Linq do SQL, ponieważ przeniesienie do NHibernate nie jest opcją dla tego projektu. – LaserJesus

1

Sposób w jaki Linq To SQL obsługuje POCO jest nieco inny.

Aby osiągnąć ignorancję uporczywości, należy użyć pliku odwzorowania opisującego sposób mapowania modUser (kolumny, asocjacje itd.), A nie projektanta LTS. Po utworzeniu nowego kontekstu przekazuje się go jako plik XMLMappingSource.

W ten sposób LTS zwróci obiekty z bazy danych.

Czytałem tu i tam, że zdefiniowanie właściwości skojarzenia kolekcji jako właściwości odczytu/zapisu typu IList (z T) wystarcza dla LinqToSQL, aby zapewnić leniwy ładunek w tych kolekcjach, ale ja go nie wypróbowałem, więc nie mogę ręczyć za to.

Struktura Entity będzie jeszcze gorsza dla wsparcia POCO w jego obecnej wersji (w zasadzie żadna, o ile większość ludzi rozumie termin POCO).

Obowiązują wszystkie zwykłe ograniczenia LTS, więc nie ma odwzorowania "wartości obiektu". Jeśli chcesz czegoś nieco oddalonego od bazy danych ORAZ wsparcia POCO, musisz spojrzeć na NHibernate.

+0

Plik odwzorowania nie będzie miał ekspresyjnej mocy, której wymagam. Jedyny sposób, w jaki mogę to sobie wyobrazić, to użycie szeregu kompozycyjnych wyrażeń. Faktycznie mam to działa i mam zamiar zaktualizować post, aby pokazać przykład. To nie jest tak czyste, jak bym chciał. – LaserJesus

1

Ok Muszę przyznać, że tak naprawdę nie dokończyłem czytania pytania OP (uśmiech owczarski), ale czy wiesz, że możesz użyć atrybutów Linq-do-SQL do dekoracji dowolnego obiektu POCO? Nie musisz korzystać z projektanta.

Oto losowy przykład z kodu, który jest teraz przede mną otwarty. Jest to POCO o nazwie "Produkt", który ma pewne atrybuty zastosowane do niego, co pozwoli mu współdziałać z DataContext Linq-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

Czy można zastosować to podejście do zapełniania POCO, które czerpią swoje atrybuty z wielu tabel, czasami przez nietrywialne relacje? Moja początkowa odpowiedź jest taka, że ​​to podejście będzie działać tylko dla bardzo prostych odwzorowań. Czy masz jakieś linki lub przykłady bardziej złożonych scenariuszy? – LaserJesus

+0

Pewnie. Projektant L2S to tylko generator kodu. Nie ma magii.Możesz więc zrobić wszystko, co projektant L2S może zrobić we własnym kodzie, z własnym generatorem. Jestem w drodze, ale zmienię to na bardziej złożony przykład w przyszłym tygodniu. – Portman

+0

Obawiam się, że nie udało mi się osiągnąć tego, co chcę, przez projektanta i musiałem znaleźć bardziej wyraziste środki. Ale poczekam i sprawdzę bardziej złożone przykłady, gdy już się pojawią, dzięki. – LaserJesus

Powiązane problemy