(ниже я буду использовать квадратные скобки для параметров шаблонов)
В основном, "дизайн" сводится к
построению добрососедских отношений с системой типов C#. Некоторые возможности системы типов достаточно приятны, и ими можно удобно пользоваться.
На работе я сделал прототип вот такого подъязыка для запросов к нашей БД:
string name = "b1";
var query = IPRQuery
.From(ClassA.Description) // Создаём выборку.
.Where( a => a.Index < 10) // прореживаем выборку.
.RelatedTo(ClassB.Description) // Создаём ещё одну выборку -
// экземпляров класса ClassB, состоящих
// хоть в каком-то отношении с экземплярами выше.
// Это ограничивает и первую выборку, кстати.
.Where ( b => b.Name == name) // Прореживаем и...
// проецируем:
.Select( (a,b) => new { Name = b.Name, Index = a.Index, A = a});
foreach (var e in query) { // создание запроса и обращение к БД.
Console.WriteLine("Name "+e.Name+", Index "+e.Index+", ClassA object index "+a.Index);
}
Вся эта штука создана на основе статических методов и методов расширения.
Один из From, например, сделан как статический метод IPRQuery:
class IPRQuery {
static public IPRQuery[T] From[T](IPRClassDescription[T] source) where T : IPRObject
}
Он создаёт выборку. Однако мы можем добавить ещё одну выборку, и тут нам нужны методы расширения:
class IPRQueryExt {
static public IPRQuery[Pair[X,T]]
From[X,T](this IPRQuery[X],IPRClassDescription[T] source) where T : IPRObject
}
Это позволяет сделать сколь угодно много From через точку.
RelatedTo сделана ровно таким же способом, только вместо простого добавления ещё одного источника мы добавляем ещё один источник с операцией связи. Она тоже требует параметр источника производный от IPRObject, также создаёт IPRQuery от пары.
From и RelatedTo можно перегрузить для разных источников: это описание класса (выше), IPRQuery[T] для подзапросов и конкретный объект, производный от IPRObject, например, экземпляр ClassA выше (From(classAObject)). Получается удобно, можно, например, выбрать все объекты, связанные с данным.
Where и Select также сделаны на основе перегрузки, и тоже, по большей части, методы расширения.
class IPRQueryExt {
static public IPRQuery[T]
Where[T](this IPRQuery[T],Func[T,Bool])
static public IPRQuery[Pair[X,T]]
Where[X,T](this IPRQuery[X,T],Func[T,Bool])
static public IPRQuery[Pair[X,T]]
Where[X,T](this IPRQuery[X,T],Func[X,T,Bool])
static public IPRQuery[Pair[Pair[X1,X2],T]]
Where[X1,X2,T](this IPRQuery[Pair[Pair[X1,X2],T]],Func[X1,X2,T,Bool])
}
И тд. Мы делаем вложенную структуру типов IPRQuery плоской для простого вызова функции.
Select завершает выборку, создавая проекцию данных из БД в данные C#. Сделан он по образу и подобию Where.
Отдельно надо сказать про преобразование кода функций.
Я не стал особо заморачиваться, а сделал практически как на ФЯ, сравнением с образцом. Преобразование кода сводится к перебору всех типов, что могут придти в каком-то месте:
internal string WhereExpr(Expression expr) {
if (expr is BinaryExpression) { ...}
else if (expr is UnaryExpression) { ...}
else if (expr is ...) { ...}
throw new Exception("Unknown expression "+expr.ToString());
}
А чего мучиться? ;)
Для получения значений параметров из окружения (выше это видно где мы прореживаем с b => b.Name == name) я, не мудрствуя лукаво, создаю лямбда-выражение типа Func[object] и вычисляю его, получая object, который я преобразую в строку (и для строк (typeof(string).IsAssignable(object.GetT
ype())) я ещё делаю замену символов - кавычки, спецсимволы, тому подобное).
Вуаля!
Я ещё не задумывался над GroupBy, SortBy и т.п. Посмотрим. ;)
В общем, мой прототип получил одобрение у всех заинтересованных сторон и я надеюсь довести его до полноценной библиотеки. Когда руки дойдут. ;)