Я пытался инкапсулировать сопоставление объектов в хранилище данных проектов. Возможно, EF обеспечит необходимый уровень абстракции, но по ряду причин я сейчас использую Linq to SQL. Следующий код предназначен для возврата пользователей в базе данных в виде списка объектов ModUser, где ModUser - это POCO, предоставляемый хранилищем:
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 { ...
Код не будет выполнен, поскольку я не могу вызвать выражение MapResource, так как я пытаюсь вызвать его из другого выражения. Мне удалось обойти это, заменив 'MapResource' на u => new ModResource (), затем используя ExpressionVisitor, чтобы найти этот заполнитель и заменить его выражением MapResource.
У меня также есть похожие проблемы, когда я пытаюсь назначить свойство ModUser с выражением, включающим одно свойство, то есть UserResource = MapResource. Мне удалось обойти эту вторую проблему, вручную объединив выражения, необходимые с помощью методов класса Expression.
Я понимаю, что могу изменить код выше на
UserResources = u.Resources(r => MapResource.Compile().Invoke(r));
Но тогда в конечном произведенном SQL-запросе нужно будет получить все атрибуты r, а не только те, которые необходимы MapResouce, поскольку мы сейчас имеем дело с функцией. Кроме того, если MapResouce потребуется доступ к другим таблицам на r, это будет невозможно, поскольку она используется как функция, а не как выражение. Я мог бы установить для DeferredLoadingEnabled значение true, но это породило бы множество отдельных запросов вместо того, чтобы изменять основной запрос для объединения с любыми необходимыми таблицами.
Кто-нибудь знает, будут ли эти операции упрощаться в будущих версиях .NET, или я поступаю неправильно? Мне действительно нравятся функции Linq и Expression, я просто хотел бы использовать их, используя более читаемый код.
Обновлено
Думаю, я мог бы добавить несколько примеров того, как я сделал выражения более составными. Они не лаконичны, но выполняют свою работу.
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;
});
}
Так что я здесь делаю? Обратите внимание, что в этой версии MapUser я не создаю объект ModResource правильно, я просто создаю фиктивную версию. Затем я вызываю метод посетителя выражения, который ищет фиктивный вызов и заменяет его тем, который я изначально хотел там. Мне кажется, что синтаксис выражений отсутствует, так как я могу по существу построить дерево выражений, которое я изначально хотел, но для этого мне нужно просто увидеть дерево. Ниже приведен еще один обходной путь, который я нашел в случае единственного случая:
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);
}
Во втором примере я знаю, что могу получить данные ресурса из данных пользователя, но не могу «встроить» выражение, чтобы показать, как это сделать, и сопоставить данные ресурса с POCO ресурса. Но я могу вручную создать дерево выражений, которому присвоен уже сопоставленный ресурс POCO, и использовать его. Затем я могу создать другое выражение, показывающее, как получить необработанные данные ресурса от пользователя, и окончательное выражение, показывающее, как сопоставить необработанные данные ресурса в POCO ресурса. Теперь стало возможным, что я могу объединить всю эту информацию в одно дерево выражений таким образом, чтобы «свернуть» параметр, специфичный для ресурса, поскольку я могу получить его из основного пользовательского параметра. Это то, что делает код выше.
Итак, я нашел способы сделать выражения легко компонуемыми ... Это просто не кажется чистым.