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.
@chris NHibernate nie był używany przez jakiś czas, ale nie jest to, że "kod" przeniósł się do mapowania w plikach XML? – eglasius
Użyj Fluent NHibernate - otrzymasz kod zamiast xml, z weryfikacją czasu refaktoryzacji/kompilacji, której możesz się spodziewać. –
Naprawdę szukam rozwiązania Linq do SQL, ponieważ przeniesienie do NHibernate nie jest opcją dla tego projektu. – LaserJesus