Самое простое решение – вынести все таки строки в отдельный класс и использовать их только туда.
Но есть еще способ. Это динамически парить такие константы из лямбда выражений. Из преимуществ это дает проверку времени компиляции и более простое переименование, но работает медленнее чем например подход с константами.
Например у нас есть выражение:
DocumentsRepository.Where(p => p.Name == "MyDoc.txt", "Tags", "SubDocuments", "Comments");
Суть – имеем какой-то репозиторий, у него есть реализованная операция Where. Ну пусть например так:
public IQueryable<T> Where(Expression<Func<T, bool>> expression, params string[] children)
{
if (expression == null)
{
throw new ArgumentNullException("expression", "expression is null.");
}
if (children == null)
{
throw new ArgumentNullException("expression", "expression is null.");
}
IQueryable<T> query = this.ObjectSet;
foreach (string child in children)
{
query = query.Include(child);
}
return query.Where(expression);
}В качестве аргументов, передаются те подобъекты которые мы хотим подгрузить также из хранилища, потому что выключен lazy loading и подобъекты по умолчанию не вытягиваются. В идеале хотелось бы поменять выражение на такое:
DocumentsRepository.Where(p => p.Name == "MyDoc.txt")
.With(p => p.Tags)
.With(p => p.SubDocuments)
.With(p => p.Comments);Итак, реализуем. Первым шагом необходимо добавить метод расширения (extension method) для того чтобы создать такой плавающий синтаксис для With, вторым шагом – парсить лямбда выражение и подставлять строку для подгруздки дочерних объектов. With будет наш самописный метод расширения чтобы добавить вложенные сущности к загрузке вместе с обьектом из хранилища, по типу Include из EF.
public static IQueryable<Document> With<TPropOut>(this IQueryable<Document> query,
Expression<Func<Document, TPropOut>> action)
{
string name = Resolve(action);
return query.Include(name);
}Базовый вариант для парсинга строк:
private static string Resolve<TIn, TOut>(Expression<Func<TIn, TOut>> action)
{
string path = new QueryPathVisitor().GetPathForProperty(action);
return path;
}И сам парсер:
private class QueryPathVisitor : ExpressionVisitor
{
private Stack<string> stack;
public string GetPathForProperty(Expression expression)
{
stack = new Stack<string>();
Visit(expression);
return stack.Aggregate(new StringBuilder(),
(sb, name) => (sb.Length > 0 ? sb.Append(".") : sb)
.Append(name)).ToString();
}
protected override Expression VisitMember(MemberExpression expression)
{
if (stack != null)
stack.Push(expression.Member.Name);
return base.VisitMember(expression);
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
if (IsLinq(expression.Method))
{
for (int i = 1; i < expression.Arguments.Count; i++)
{
Visit(expression.Arguments[i]);
}
Visit(expression.Arguments[0]);
return expression;
}
return base.VisitMethodCall(expression);
}
private static bool IsLinq(MethodInfo method)
{
if (method.DeclaringType != typeof(Queryable) && method.DeclaringType != typeof(Enumerable))
return false;
return Attribute.GetCustomAttribute(method, typeof(ExtensionAttribute)) != null;
}
protected override Expression VisitMethodCall(MethodCallExpression expression)
{
if (IsLinq(expression.Method))
{
for (int i = 1; i < expression.Arguments.Count; i++)
{
Visit(expression.Arguments[i]);
}
Visit(expression.Arguments[0]);
return expression;
}
return base.VisitMethodCall(expression);
}
}Теперь у нас есть возможность заменить выражение со строковыми константами вначале на то которое хотели:
DocumentsRepository.Where(p => p.Name == "MyDoc.txt")
.With(p => p.Tags)
.With(p => p.SubDocuments)
.With(p => p.Comments);Кстати, если говорить о конкретной реализации репозитория то метод из EntityFramework – Include содержит перегрузку которая принимает лямбда выражение. Но используя свой парсер мы можем применить такой же подход и для других подобных сценариев.
Кстати более сложные свойства можно распарсить так:
Document document = DocumentsRepository
.Where(p => p.Name == "MyDoc.txt", "Comments", "Comments.Author")
.SingleOrDefault();Результат:
Document document = DocumentsRepository.Where(p => p.Name == "MyDoc.txt")
.With(p => p.Comments)
.With(p => p.Comments.Select(c => c.Author))
.SingleOrDefault();
Комментариев нет:
Отправить комментарий