ЗАЧЕМ МНЕ ЭТО НУЖНО:
Программа, над которой я работаю, имеет универсальный конструктор запросов, который использует выражение.trees для динамического запроса всех таблиц базы данных. С простым выбором и несколькими сотнями или тысячами данных он работает нормально. Когда пользователю нужны связанные данные таблицы, он использует ленивую загрузку и сопоставление для достижения того, что необходимо, но если (и когда) данные достигнут сотен тысяч, они будут непригодны для использования. Я пытаюсь удалить ленивую загрузку и сделать это с предварительным соединением в дереве выражений, что делает его одной поездкой.
Мне это нужно, потому что у пользователя есть возможность динамического поиска в БД, а наличие конкретных запросов для каждой таблицы очень сложно поддерживать, потому что у него много таблиц, которые нужно поддерживать вручную, а клиент требует много настроек.
ПРОБЛЕМА:
Чтобы выполнить следующий запрос,
IQueryable<A> As = db.A
.Join(
db.B,
_a => _a.bID,
_b => _b.ID,
(_a, _b) => new { a = _a, b = _b })
.Where(s => s.b.Name == "xpto")
.Select(s => s.a);
A получено имеет общий тип, а B получено имеет строку.
Использование блестящего @NetMage ?noredirect=1#comment113107421_63910903">ответ (это только часть. Полный ответ находится по ссылке или ниже):
private static Type TypeGenArg(this Expression e, int n) => e.Type.GetGenericArguments()[n];
public static MethodCallExpression Join(this Expression outer,
Expression inner,
LambdaExpression outerKeyFne,
LambdaExpression innerKeyFne,
LambdaExpression resultFne)
=>
Expression.Call(
TQueryable,
"Join",
new[] { outer.TypeGenArg(0),
inner.TypeGenArg(0),
outerKeyFne.ReturnType,
resultFne.ReturnType },
outer,
inner,
outerKeyFne,
innerKeyFne,
resultFne);
В следующем вызове, который делает MethodCallExpression 'join':
var join = db.A.AsConst().Join(db.B.AsConst(), aKeyFne, bKeyFne, resultFne);
Кажется, что db.A и db.B должны быть известны, но это не так, потому что клиент решает, какую таблицу просматривать, а «B» является следствием этого выбора.
В контексте этого класса; A - это общий тип T, а B - строка B. Таким образом, каждое преобразование, которое я пытался вызвать с помощью предыдущего оператора, вызывает различные типы ошибок, когда они получены в "объединении" или позже. Лучшая попытка была, когда я сделал:
//GetType of Generic Type A
AType = typeof(A);
//GetType from string 'B'
string BModelstr = "B";
string areaM = "project.Models." + BModelstr;
Assembly currentAssem = Assembly.GetExecutingAssembly();
string assemblStr = currentAssem.ToString();
string complete = areaM + "," + assemblStr;
Type BType = Type.GetType(complete);
//And then calling:
var join1 = (db.Set(AType)).AsConst().Join((db.Set(BType)).AsConst(), aKeyFne, bKeyFne, resultFne);
Это вызвало ошибку на
private static Type TypeGenArg(this Expression e, int n) => e.Type.GetGenericArguments()[n];
Сказать, что это не дженерик.
ВОПРОС:
- Как я могу использовать общий тип T как db.A и строку «B» как db.B, чтобы быть принятым в expression.call() «join» и получать их типы?
- Изменить соединение?
- Отправка разных параметров, таких как
join<A>
и строка?
Соответствующий код @NetMage, который я пытаюсь реализовать для этого вопроса (полная ссылка):
var aParm = typeof(A).Param("_a");
var aKeyFne = aParm.Lambda(aParm.Dot("bID"));
var bParm = typeof(B).Param("_b");
var bKeyFne = bParm.Lambda(bParm.Dot("ID"));
var anonType = (new { a = default(A), b = default(B) }).GetType();
var resultFne = (aParm, bParm).Lambda(anonType.New(aParm, bParm));
var join = db.A.AsConst().Join(db.B.AsConst(), aKeyFne, bKeyFne, resultFne);
.....
С использованием:
public static class ValueTupleExt {
private static T[] makeArray<T>(params T[] itemArray) => itemArray;
public static T[] ToArray<T>(this (T, T) tuple) => makeArray(tuple.Item1, tuple.Item2);
}
public static class ExpressionExt {
private static Type TQueryable = typeof(Queryable);
private static Type TypeGenArg(this Expression e, int n) => e.Type.GetGenericArguments()[n];
public static MethodCallExpression Join(this Expression outer, Expression inner, LambdaExpression outerKeyFne, LambdaExpression innerKeyFne, LambdaExpression resultFne) =>
Expression.Call(TQueryable, "Join", new[] { outer.TypeGenArg(0), inner.TypeGenArg(0), outerKeyFne.ReturnType, resultFne.ReturnType }, outer, inner, outerKeyFne, innerKeyFne, resultFne);
public static MethodCallExpression Select(this Expression src, LambdaExpression resultFne) => Expression.Call(TQueryable, "Select", new[] { src.TypeGenArg(0), resultFne.ReturnType }, src, resultFne);
public static MethodCallExpression Where(this Expression src, LambdaExpression predFne) => Expression.Call(TQueryable, "Where", new[] { src.TypeGenArg(0) }, src, predFne);
public static ConstantExpression AsConst<T>(this T obj) => Expression.Constant(obj, typeof(T));
public static MemberExpression Dot(this Expression obj, string propNames) =>
(MemberExpression)propNames.Split('.').Aggregate(obj, (ans, propName) => Expression.PropertyOrField(ans, propName));
public static LambdaExpression Lambda(this ParameterExpression p1, Expression body) => Expression.Lambda(body, p1);
public static LambdaExpression Lambda(this (ParameterExpression, ParameterExpression) parms, Expression body) => Expression.Lambda(body, parms.ToArray());
public static NewExpression New(this Type t, params Expression[] vals) => Expression.New(t.GetConstructors()[0], vals, t.GetProperties());
public static BinaryExpression opEq(this Expression left, Expression right) => Expression.Equal(left, right);
public static ParameterExpression Param(this Type t, string pName) => Expression.Parameter(t, pName);
}
Помощь будет очень признательна.
Спасибо!