< Summary

Information
Class: OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridFormulaTranslator
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/Grid/OrdersGridFormulaTranslator.cs
Line coverage
66%
Covered lines: 148
Uncovered lines: 75
Coverable lines: 223
Total lines: 430
Line coverage: 66.3%
Branch coverage
48%
Covered branches: 131
Total branches: 272
Branch coverage: 48.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Lambda()100%11100%
get_ClrType()100%210%
.cctor()100%1159.18%
IsFieldTranslatable(...)100%210%
ResolveFieldKey(...)75%44100%
Translate(...)100%11100%
TryTranslate(...)100%1150%
Build(...)77.27%282276.92%
BuildBinary(...)56.36%1675566.66%
BuildCall(...)45.11%60313370.17%
Prop(...)100%11100%
NavProp(...)75%44100%
NavName(...)100%11100%
NavNavName(...)100%210%
ToDecimal(...)50%22100%
ToNullableBool(...)50%22100%
Truthy(...)25%22840%
AsBool(...)100%11100%
AsDecimal(...)12.5%35825%
AsDate(...)50%3250%
AsString(...)50%3250%
Unify(...)5.55%2061816.66%
Lift(...)100%11100%
Concat(...)100%11100%
StrCall(...)100%11100%
StrPredicate(...)100%11100%
DatePart(...)100%11100%
DateDiffDays(...)100%11100%
AddDays(...)100%11100%
ConstNumber(...)50%6450%
TryParseDate(...)100%210%
TypeOf(...)87.5%88100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/Grid/OrdersGridFormulaTranslator.cs

#LineLine coverage
 1using System.Linq.Expressions;
 2using System.Reflection;
 3using Microsoft.EntityFrameworkCore;
 4using OzonDomains.Models;
 5using static OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridFormula;
 6
 7namespace OzonOrdersWeb.WebServices.Orders.Grid;
 8
 9/// <summary>
 10/// Перевод формулы вычисляемого столбца в EF-выражение над <see cref="Order"/> — чтобы по столбцу можно было
 11/// сортировать, фильтровать и считать счётчики значений на сервере. Переводится подмножество: поля, лежащие в
 12/// таблице Orders или в справочниках (JOIN), арифметика, сравнения, если/первоеНепустое/мин/макс, текстовые и
 13/// датные функции с прямым SQL-аналогом. Семантика «пусто → пусто» совпадает с NULL в SQL.
 14/// Непереводимое (поля, считаемые в памяти; текст(число) и т.п.) — <see cref="NotTranslatableException"/>.
 15/// </summary>
 16public static class OrdersGridFormulaTranslator
 17{
 418    public sealed class NotTranslatableException(string message) : Exception(message);
 19
 20    /// <summary>Готовое выражение: лямбда Order → T (T = decimal?/string/DateTime?/bool?) и тип результата формулы.</su
 6921    public sealed record Translation(LambdaExpression Lambda, string Type)
 22    {
 023        public Type ClrType => Lambda.ReturnType;
 24    }
 25
 126    private static readonly ParameterExpression Param = Expression.Parameter(typeof(Order), "o");
 27
 28    /// <summary>Поля, у которых есть SQL-представление. Остальные из каталога считаются в памяти и не переводятся.</sum
 129    private static readonly Dictionary<string, Func<Expression>> FieldExpressions = new(StringComparer.OrdinalIgnoreCase
 130    {
 031        ["shipmentNumber"] = () => Prop("ShipmentNumber"),
 032        ["ozonClient"] = () => NavName("OzonClient"),
 033        ["deliveryCity"] = () => Prop("DeliveryCity"),
 534        ["processingDate"] = () => Prop("ProcessingDate"),
 335        ["shippingDate"] = () => Prop("ShippingDate"),
 036        ["lastStatusChangeDate"] = () => Prop("LastStatusChangeDate"),
 037        ["productKey"] = () => Prop("ProductKey"),
 338        ["manufacturer"] = () => NavName("EtProducer"),
 339        ["article"] = () => Prop("Article"),
 140        ["productName"] = () => Prop("ProductName"),
 041        ["category"] = () => NavProp("ProductInfo", "CommercialCategory"),
 042        ["volumetricWeight"] = () => ToDecimal(NavProp("ProductInfo", "VolumetricWeight")),
 043        ["weight"] = () => NavProp("ProductInfo", "Weight"),
 044        ["clientStatus"] = () => NavName("ClientStatus"),
 145        ["appStatus"] = () => NavName("AppStatus"),
 046        ["shipmentWarehouse"] = () => NavName("ShipmentWarehouse"),
 047        ["deliveryProvider"] = () => NavNavName("Delivery", "Provider"),
 048        ["deliveryName"] = () => NavName("Delivery"),
 249        ["trackingNumber"] = () => Prop("TrackingNumber"),
 150        ["scanit"] = () => Prop("Scanit"),
 151        ["supplier"] = () => NavName("Supplier"),
 152        ["orderNumberToSupplier"] = () => Prop("OrderNumberToSupplier"),
 153        ["sitePrice"] = () => NavProp("ProductInfo", "CurrentPriceWithDiscount"),
 454        ["quantity"] = () => ToDecimal(Prop("Quantity")),
 655        ["price"] = () => Prop("Price"),
 056        ["currency"] = () => NavName("Сurrency"),
 257        ["shipmentAmount"] = () => Prop("ShipmentAmount"),
 058        ["purchasePrice"] = () => Prop("PurchasePrice"),
 059        ["costPrice"] = () => Prop("CostPrice"),
 060        ["ozonCommissionMin"] = () => Prop("MinOzonCommission"),
 061        ["ozonCommissionMax"] = () => Prop("MaxOzonCommission"),
 062        ["profitMin"] = () => Prop("MinProfit"),
 263        ["profitMax"] = () => Prop("MaxProfit"),
 064        ["discountMin"] = () => Prop("MinDiscount"),
 065        ["discountMax"] = () => Prop("MaxDiscount"),
 166        ["isArchived"] = () => ToNullableBool(Expression.NotEqual(Prop("OrderArchive"), Expression.Constant(null))),
 167        ["fromFile"] = () => Prop("FromFile")
 168    };
 69
 070    public static bool IsFieldTranslatable(string key) => FieldExpressions.ContainsKey(key);
 71
 72    /// <summary>Поле по ключу или русскому названию из каталога.</summary>
 73    private static string? ResolveFieldKey(string nameOrLabel)
 74    {
 4175        var f = OrdersGridFormulaFields.All.FirstOrDefault(x =>
 91376            string.Equals(x.Key, nameOrLabel, StringComparison.OrdinalIgnoreCase) ||
 91377            string.Equals(x.Label, nameOrLabel, StringComparison.OrdinalIgnoreCase));
 4178        return f?.Key;
 79    }
 80
 81    /// <summary>Перевести формулу; бросает <see cref="NotTranslatableException"/> или <see cref="FormulaException"/>.</
 82    public static Translation Translate(string formula)
 83    {
 2784        var node = Parse(formula);
 2785        var body = Build(node);
 2386        return new Translation(Expression.Lambda(body, Param), TypeOf(body.Type));
 87    }
 88
 89    public static bool TryTranslate(string formula, out Translation? translation, out string? reason)
 90    {
 91        try
 92        {
 493            translation = Translate(formula);
 094            reason = null;
 095            return true;
 96        }
 1697        catch (NotTranslatableException ex) { translation = null; reason = ex.Message; return false; }
 098        catch (FormulaException ex) { translation = null; reason = ex.Message; return false; }
 499    }
 100
 101    // ---------- Построение ----------
 102
 1103    private static readonly Type Dec = typeof(decimal?);
 1104    private static readonly Type Str = typeof(string);
 1105    private static readonly Type Dt = typeof(DateTime?);
 1106    private static readonly Type Bl = typeof(bool?);
 107
 108    private static Expression Build(Node node)
 109    {
 110        switch (node)
 111        {
 8112            case Num n: return Expression.Constant((decimal?)(decimal)n.Value, Dec);
 9113            case OrdersGridFormula.Str s: return Expression.Constant(s.Value, Str);
 0114            case Bool b: return Expression.Constant((bool?)b.Value, Bl);
 0115            case Null: return Expression.Constant(null, Dec); // тип уточнится при унификации
 116            case Field f:
 117            {
 41118                var key = ResolveFieldKey(f.Name) ?? f.Name;
 41119                if (!FieldExpressions.TryGetValue(key, out var factory))
 3120                    throw new NotTranslatableException($"Поле «{f.Name}» считается на сервере в памяти — сортировка и фи
 38121                return factory();
 122            }
 123            case Un u:
 124            {
 3125                var a = Build(u.A);
 3126                return u.Op == "neg" ? Expression.Negate(AsDecimal(a, u.Pos)) : ToNullableBool(Expression.Not(AsBool(a))
 127            }
 17128            case Bin b: return BuildBinary(b);
 22129            case Call c: return BuildCall(c);
 130        }
 131
 0132        throw new NotTranslatableException("Неизвестный узел формулы.");
 133    }
 134
 135    private static Expression BuildBinary(Bin b)
 136    {
 17137        var l = Build(b.L);
 15138        var r = Build(b.R);
 15139        switch (b.Op)
 140        {
 1141            case "and": return ToNullableBool(Expression.AndAlso(Truthy(l), Truthy(r)));
 1142            case "or": return ToNullableBool(Expression.OrElse(Truthy(l), Truthy(r)));
 4143            case "=": { Unify(ref l, ref r); return ToNullableBool(Expression.Equal(l, r)); }
 0144            case "<>": { Unify(ref l, ref r); return ToNullableBool(Expression.NotEqual(l, r)); }
 145            case "<": case ">": case "<=": case ">=":
 146            {
 2147                Unify(ref l, ref r);
 2148                if (l.Type == Str)
 149                {
 0150                    var cmp = Expression.Call(typeof(string).GetMethod(nameof(string.Compare), [Str, Str])!, l, r);
 0151                    l = cmp; r = Expression.Constant(0);
 152                }
 4153                var op = b.Op switch { "<" => ExpressionType.LessThan, ">" => ExpressionType.GreaterThan, "<=" => Expres
 2154                return ToNullableBool(Expression.MakeBinary(op, l, r));
 155            }
 156            case "+":
 1157                if (l.Type == Str || r.Type == Str) return Concat(AsString(l), AsString(r));
 1158                if (l.Type == Dt) return AddDays(l, AsDecimal(r, b.Pos));
 1159                return Expression.Add(AsDecimal(l, b.Pos), AsDecimal(r, b.Pos));
 160            case "-":
 2161                if (l.Type == Dt && r.Type == Dt) return DateDiffDays(r, l);
 0162                if (l.Type == Dt) return AddDays(l, Expression.Negate(AsDecimal(r, b.Pos)));
 0163                return Expression.Subtract(AsDecimal(l, b.Pos), AsDecimal(r, b.Pos));
 5164            case "*": return Expression.Multiply(AsDecimal(l, b.Pos), AsDecimal(r, b.Pos));
 165            case "/":
 166            {
 167                // Деление на ноль в MySQL даёт NULL — как на клиенте; для EF делаем явную проверку.
 2168                var rd = AsDecimal(r, b.Pos);
 2169                var zero = Expression.Constant((decimal?)0m, Dec);
 2170                return Expression.Condition(Expression.Equal(rd, zero), Expression.Constant(null, Dec), Expression.Divid
 171            }
 172            case "%":
 173            {
 0174                var rd = AsDecimal(r, b.Pos);
 0175                var zero = Expression.Constant((decimal?)0m, Dec);
 0176                return Expression.Condition(Expression.Equal(rd, zero), Expression.Constant(null, Dec), Expression.Modul
 177            }
 178        }
 179
 0180        throw new NotTranslatableException($"Оператор «{b.Op}» не переводится.");
 181    }
 182
 183    private static Expression BuildCall(Call c)
 184    {
 22185        var args = c.Args.Select(Build).ToList();
 21186        switch (c.Name)
 187        {
 188            case "if":
 189            {
 4190                var a = args[1]; var b = args[2];
 2191                Unify(ref a, ref b);
 2192                return Expression.Condition(Truthy(args[0]), a, b);
 193            }
 194            case "round":
 195            {
 1196                var digits = args.Count > 1 ? (int)ConstNumber(args[1], "округл: знаков") : 0;
 1197                var x = AsDecimal(args[0], c.Pos);
 1198                var round = typeof(Math).GetMethod(nameof(Math.Round), [typeof(decimal), typeof(int)])!;
 2199                return Lift(x, v => Expression.Call(round, v, Expression.Constant(digits)));
 200            }
 201            case "abs":
 202            {
 0203                var abs = typeof(Math).GetMethod(nameof(Math.Abs), [typeof(decimal)])!;
 0204                return Lift(AsDecimal(args[0], c.Pos), v => Expression.Call(abs, v));
 205            }
 206            case "min": case "max":
 207            {
 208                // Попарно через CASE; NULL пропускается (COALESCE-подобно).
 1209                Expression acc = AsDecimal(args[0], c.Pos);
 4210                for (var i = 1; i < args.Count; i++)
 211                {
 1212                    var next = AsDecimal(args[i], c.Pos);
 1213                    var cmp = c.Name == "min" ? Expression.LessThan(acc, next) : Expression.GreaterThan(acc, next);
 1214                    var pick = Expression.Condition(Expression.OrElse(Expression.Equal(next, Expression.Constant(null, D
 1215                    acc = Expression.Condition(Expression.Equal(acc, Expression.Constant(null, Dec)), next, pick);
 216                }
 1217                return acc;
 218            }
 219            case "coalesce":
 220            {
 1221                Expression result = args[^1];
 4222                for (var i = args.Count - 2; i >= 0; i--)
 223                {
 2224                    var cur = args[i]; var rest = result;
 1225                    Unify(ref cur, ref rest);
 1226                    result = Expression.Coalesce(cur, rest);
 227                }
 1228                return result;
 229            }
 230            case "isempty":
 231            {
 3232                var a = args[0];
 6233                if (a.Type == Str) return ToNullableBool(Expression.OrElse(Expression.Equal(a, Expression.Constant(null,
 0234                return ToNullableBool(Expression.Equal(a, Expression.Constant(null, a.Type)));
 235            }
 236            case "concat":
 237            {
 3238                Expression acc = AsString(args[0]);
 24239                for (var i = 1; i < args.Count; i++) acc = Concat(acc, AsString(args[i]));
 3240                return acc;
 241            }
 0242            case "upper": return StrCall(args[0], nameof(string.ToUpper));
 1243            case "lower": return StrCall(args[0], nameof(string.ToLower));
 0244            case "trim": return StrCall(args[0], nameof(string.Trim));
 245            case "len":
 246            {
 0247                var s = AsString(args[0]);
 0248                return Expression.Condition(Expression.Equal(s, Expression.Constant(null, Str)), Expression.Constant(nul
 0249                    Expression.Convert(Expression.Property(s, nameof(string.Length)), Dec));
 250            }
 1251            case "contains": return StrPredicate(args, nameof(string.Contains));
 0252            case "startswith": return StrPredicate(args, nameof(string.StartsWith));
 0253            case "endswith": return StrPredicate(args, nameof(string.EndsWith));
 254            case "text":
 1255                if (args[0].Type == Str) return args[0];
 1256                throw new NotTranslatableException("текст(число/дата) не переводится в SQL — сортировка и фильтр недосту
 257            case "number":
 0258                if (args[0].Type == Dec) return args[0];
 0259                throw new NotTranslatableException("число(текст) не переводится в SQL.");
 1260            case "today": return Expression.Constant((DateTime?)DateTime.Today, Dt);
 261            case "date":
 262            {
 0263                if (args[0] is ConstantExpression { Value: string text } && TryParseDate(text, out var d)) return Expres
 0264                if (args[0].Type == Dt) return args[0];
 0265                throw new NotTranslatableException("дата(): в SQL переводится только дата-константа, например дата(\"31.
 266            }
 1267            case "days": return DateDiffDays(AsDate(args[1]), AsDate(args[0]));
 2268            case "adddays": return AddDays(AsDate(args[0]), AsDecimal(args[1], c.Pos));
 1269            case "year": return DatePart(args[0], nameof(DateTime.Year));
 1270            case "month": return DatePart(args[0], nameof(DateTime.Month));
 0271            case "day": return DatePart(args[0], nameof(DateTime.Day));
 272            case "weekday":
 273            {
 274                // DayOfWeek: 0 вс … 6 сб → 1 пн … 7 вс
 1275                var d = AsDate(args[0]);
 1276                var dow = Expression.Convert(Expression.Property(Expression.Property(d, "Value"), nameof(DateTime.DayOfW
 1277                var ru = Expression.Add(Expression.Modulo(Expression.Add(dow, Expression.Constant(6)), Expression.Consta
 1278                return Expression.Condition(Expression.Equal(d, Expression.Constant(null, Dt)), Expression.Constant(null
 279            }
 280        }
 281
 0282        throw new NotTranslatableException($"Функция «{c.Name}» не переводится в SQL.");
 283    }
 284
 285    // ---------- Помощники ----------
 286
 32287    private static Expression Prop(string name) => Expression.Property(Param, name);
 288
 289    private static Expression NavProp(string nav, string prop)
 290    {
 6291        var n = Expression.Property(Param, nav);
 6292        var p = Expression.Property(n, prop);
 6293        var t = p.Type.IsValueType && Nullable.GetUnderlyingType(p.Type) == null ? typeof(Nullable<>).MakeGenericType(p.
 6294        return Expression.Condition(Expression.NotEqual(n, Expression.Constant(null, n.Type)), Expression.Convert(p, t),
 295    }
 296
 5297    private static Expression NavName(string nav) => NavProp(nav, "Name");
 298
 299    private static Expression NavNavName(string nav1, string nav2)
 300    {
 0301        var n1 = Expression.Property(Param, nav1);
 0302        var n2 = Expression.Property(n1, nav2);
 0303        var name = Expression.Property(n2, "Name");
 0304        var ok = Expression.AndAlso(Expression.NotEqual(n1, Expression.Constant(null, n1.Type)), Expression.NotEqual(n2,
 0305        return Expression.Condition(ok, name, Expression.Constant(null, Str));
 306    }
 307
 308    private static Expression ToDecimal(Expression e)
 309    {
 4310        if (e.Type == Dec) return e;
 4311        return Expression.Convert(e, Dec);
 312    }
 313
 14314    private static Expression ToNullableBool(Expression e) => e.Type == Bl ? e : Expression.Convert(e, Bl);
 315
 316    private static Expression Truthy(Expression e)
 317    {
 9318        if (e.Type == typeof(bool)) return e;
 18319        if (e.Type == Bl) return Expression.Equal(e, Expression.Constant((bool?)true, Bl));
 0320        if (e.Type == Dec) return Expression.AndAlso(Expression.NotEqual(e, Expression.Constant(null, Dec)), Expression.
 0321        if (e.Type == Str) return Expression.AndAlso(Expression.NotEqual(e, Expression.Constant(null, Str)), Expression.
 0322        return Expression.NotEqual(e, Expression.Constant(null, e.Type));
 323    }
 324
 3325    private static Expression AsBool(Expression e) => Truthy(e);
 326
 327    private static Expression AsDecimal(Expression e, int pos)
 328    {
 42329        if (e.Type == Dec) return e;
 0330        if (e.Type == Bl) return Expression.Condition(Expression.Equal(e, Expression.Constant((bool?)true, Bl)), Express
 0331        if (e.Type == Str || e.Type == Dt) throw new NotTranslatableException($"Ожидается число (позиция {pos + 1}).");
 0332        return Expression.Convert(e, Dec);
 333    }
 334
 335    private static Expression AsDate(Expression e)
 336    {
 14337        if (e.Type == Dt) return e;
 0338        throw new NotTranslatableException("Ожидается дата: поле даты, сегодня() или дата(\"…\").");
 339    }
 340
 341    private static Expression AsString(Expression e)
 342    {
 24343        if (e.Type == Str) return e;
 0344        throw new NotTranslatableException("Склейка/текстовые функции в SQL — только для текстовых полей и строк (числа 
 345    }
 346
 347    /// <summary>Привести два операнда к общему типу (null-константа принимает тип соседа).</summary>
 348    private static void Unify(ref Expression a, ref Expression b)
 349    {
 14350        if (a.Type == b.Type) return;
 0351        if (a is ConstantExpression { Value: null }) { a = Expression.Constant(null, b.Type); return; }
 0352        if (b is ConstantExpression { Value: null }) { b = Expression.Constant(null, a.Type); return; }
 0353        if (a.Type == Bl && b.Type == Dec) { a = AsDecimal(a, 0); return; }
 0354        if (b.Type == Bl && a.Type == Dec) { b = AsDecimal(b, 0); return; }
 0355        throw new NotTranslatableException("Сравниваются значения разных типов.");
 356    }
 357
 358    private static Expression Lift(Expression nullableDecimal, Func<Expression, Expression> body)
 359    {
 1360        var value = Expression.Property(nullableDecimal, "Value");
 1361        return Expression.Condition(Expression.Equal(nullableDecimal, Expression.Constant(null, Dec)), Expression.Consta
 362    }
 363
 364    /// <summary>Коллация для результата CONCAT: смесь колонок utf8mb4_bin и литералов даёт «Illegal mix of collations» 
 365    private const string TextCollation = "utf8mb4_general_ci";
 366
 1367    private static readonly MethodInfo CollateMethod = typeof(RelationalDbFunctionsExtensions)
 1368        .GetMethod(nameof(RelationalDbFunctionsExtensions.Collate))!.MakeGenericMethod(Str);
 369
 370    private static Expression Concat(Expression a, Expression b)
 371    {
 6372        var concat = Expression.Call(typeof(string).GetMethod(nameof(string.Concat), [Str, Str])!, a, b);
 6373        return Expression.Call(CollateMethod, Expression.Constant(EF.Functions), concat, Expression.Constant(TextCollati
 374    }
 375
 376    private static Expression StrCall(Expression s, string method)
 377    {
 1378        var str = AsString(s);
 1379        var m = typeof(string).GetMethod(method, Type.EmptyTypes)!;
 1380        return Expression.Condition(Expression.Equal(str, Expression.Constant(null, Str)), Expression.Constant(null, Str
 381    }
 382
 383    private static Expression StrPredicate(List<Expression> args, string method)
 384    {
 1385        var s = AsString(args[0]);
 1386        var sub = AsString(args[1]);
 1387        var m = typeof(string).GetMethod(method, [Str])!;
 1388        return ToNullableBool(Expression.AndAlso(Expression.NotEqual(s, Expression.Constant(null, Str)), Expression.Call
 389    }
 390
 391    private static Expression DatePart(Expression d, string part)
 392    {
 2393        var date = AsDate(d);
 2394        var p = Expression.Convert(Expression.Property(Expression.Property(date, "Value"), part), Dec);
 2395        return Expression.Condition(Expression.Equal(date, Expression.Constant(null, Dt)), Expression.Constant(null, Dec
 396    }
 397
 1398    private static readonly MethodInfo DateDiffDayMethod = typeof(MySqlDbFunctionsExtensions)
 1399        .GetMethod(nameof(MySqlDbFunctionsExtensions.DateDiffDay), [typeof(DbFunctions), typeof(DateTime), typeof(DateTi
 400
 401    /// <summary>Дней от <paramref name="from"/> до <paramref name="to"/> (to − from), NULL если любая дата пустая.</sum
 402    private static Expression DateDiffDays(Expression from, Expression to)
 403    {
 2404        var call = Expression.Call(DateDiffDayMethod, Expression.Constant(EF.Functions), Expression.Property(from, "Valu
 2405        var anyNull = Expression.OrElse(Expression.Equal(from, Expression.Constant(null, Dt)), Expression.Equal(to, Expr
 2406        return Expression.Condition(anyNull, Expression.Constant(null, Dec), Expression.Convert(call, Dec));
 407    }
 408
 409    private static Expression AddDays(Expression date, Expression days)
 410    {
 2411        var add = typeof(DateTime).GetMethod(nameof(DateTime.AddDays), [typeof(double)])!;
 2412        var call = Expression.Call(Expression.Property(date, "Value"), add, Expression.Convert(Expression.Property(days,
 2413        var anyNull = Expression.OrElse(Expression.Equal(date, Expression.Constant(null, Dt)), Expression.Equal(days, Ex
 2414        return Expression.Condition(anyNull, Expression.Constant(null, Dt), Expression.Convert(call, Dt));
 415    }
 416
 417    private static double ConstNumber(Expression e, string what)
 418    {
 2419        if (e is ConstantExpression { Value: decimal d }) return (double)d;
 0420        throw new NotTranslatableException($"{what} — в SQL переводится только число-константа.");
 421    }
 422
 423    private static bool TryParseDate(string text, out DateTime date)
 424    {
 0425        var formats = new[] { "dd.MM.yyyy", "yyyy-MM-dd", "d.M.yyyy" };
 0426        return DateTime.TryParseExact(text.Trim(), formats, System.Globalization.CultureInfo.InvariantCulture, System.Gl
 427    }
 428
 23429    private static string TypeOf(Type t) => t == Dec ? "number" : t == Str ? "text" : t == Dt ? "date" : t == Bl ? "bool
 430}

Methods/Properties

.ctor(System.String)
get_Lambda()
get_ClrType()
.cctor()
IsFieldTranslatable(System.String)
ResolveFieldKey(System.String)
Translate(System.String)
TryTranslate(System.String,OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridFormulaTranslator/Translation&,System.String&)
Build(OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridFormula/Node)
BuildBinary(OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridFormula/Bin)
BuildCall(OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridFormula/Call)
Prop(System.String)
NavProp(System.String,System.String)
NavName(System.String)
NavNavName(System.String,System.String)
ToDecimal(System.Linq.Expressions.Expression)
ToNullableBool(System.Linq.Expressions.Expression)
Truthy(System.Linq.Expressions.Expression)
AsBool(System.Linq.Expressions.Expression)
AsDecimal(System.Linq.Expressions.Expression,System.Int32)
AsDate(System.Linq.Expressions.Expression)
AsString(System.Linq.Expressions.Expression)
Unify(System.Linq.Expressions.Expression&,System.Linq.Expressions.Expression&)
Lift(System.Linq.Expressions.Expression,System.Func`2<System.Linq.Expressions.Expression,System.Linq.Expressions.Expression>)
Concat(System.Linq.Expressions.Expression,System.Linq.Expressions.Expression)
StrCall(System.Linq.Expressions.Expression,System.String)
StrPredicate(System.Collections.Generic.List`1<System.Linq.Expressions.Expression>,System.String)
DatePart(System.Linq.Expressions.Expression,System.String)
DateDiffDays(System.Linq.Expressions.Expression,System.Linq.Expressions.Expression)
AddDays(System.Linq.Expressions.Expression,System.Linq.Expressions.Expression)
ConstNumber(System.Linq.Expressions.Expression,System.String)
TryParseDate(System.String,System.DateTime&)
TypeOf(System.Type)