From 644151a2481307fdbb53216d2d71022a71e75d2c Mon Sep 17 00:00:00 2001 From: Olmo del Corral Cano Date: Sun, 12 Dec 2021 16:57:53 +0100 Subject: [PATCH] add support for IsNew and GetType in LINQ provider and LambdaToJavascriptConverter (useful for ToString) --- .../Cache/ToStringExpressionVisitor.cs | 29 +++++ Signum.Engine/Database.cs | 2 +- .../Linq/ExpressionVisitor/QueryBinder.cs | 74 ++++++++++++ Signum.Engine/Operations/OperationLogic.cs | 2 +- Signum.Entities/Entity.cs | 13 +-- Signum.Entities/Lite.cs | 2 - Signum.Entities/Reflection/Reflector.cs | 13 +++ .../Facades/LambdaToJavascriptConverter.cs | 98 ++++++++++++++-- Signum.React/Scripts/Reflection.ts | 4 +- Signum.React/Scripts/Signum.Entities.t4s | 5 + Signum.React/Scripts/Signum.Entities.ts | 6 +- Signum.Test/LinqProvider/GetTypeAndNewTest.cs | 107 ++++++++++++++++++ 12 files changed, 327 insertions(+), 28 deletions(-) create mode 100644 Signum.Test/LinqProvider/GetTypeAndNewTest.cs diff --git a/Signum.Engine.Extensions/Cache/ToStringExpressionVisitor.cs b/Signum.Engine.Extensions/Cache/ToStringExpressionVisitor.cs index e987da2602..d7ea5ad2aa 100644 --- a/Signum.Engine.Extensions/Cache/ToStringExpressionVisitor.cs +++ b/Signum.Engine.Extensions/Cache/ToStringExpressionVisitor.cs @@ -42,6 +42,9 @@ protected override Expression VisitMember(MemberExpression node) if (exp is CachedEntityExpression cee) { + if (node.Member.Name == "IsNew") + return Expression.Constant(false); + Field field = cee.FieldEmbedded != null ? cee.FieldEmbedded.GetField(node.Member) : cee.FieldMixin != null ? cee.FieldMixin.GetField(node.Member) : @@ -53,6 +56,26 @@ protected override Expression VisitMember(MemberExpression node) return node.Update(exp); } + protected override Expression VisitConditional(ConditionalExpression c) // a.IsNew + { + Expression test = this.Visit(c.Test); + if (test is ConstantExpression co) + { + if ((bool)co.Value!) + return this.Visit(c.IfTrue); + else + return this.Visit(c.IfFalse); + } + + Expression ifTrue = this.Visit(c.IfTrue); + Expression ifFalse = this.Visit(c.IfFalse); + if (test != c.Test || ifTrue != c.IfTrue || ifFalse != c.IfFalse) + { + return Expression.Condition(test, ifTrue, ifFalse); + } + return c; + } + private Expression BindMember(CachedEntityExpression n, Field field, Expression? prevPrimaryKey) { Expression body = GetField(field, n.Constructor, prevPrimaryKey); @@ -192,6 +215,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) var obj = base.Visit(node.Object); var args = base.Visit(node.Arguments); + if (node.Method.Name == "ToString" && node.Arguments.IsEmpty() && obj is CachedEntityExpression ce && ce.Type.IsEntity()) { var table = (Table)ce.Constructor.table; @@ -212,6 +236,11 @@ protected override Expression VisitMethodCall(MethodCallExpression node) } } + if(node.Method.Name == "GetType" && obj is CachedEntityExpression ce2) + { + return Expression.Constant(ce2.Type); + } + LambdaExpression? lambda = ExpressionCleaner.GetFieldExpansion(obj?.Type, node.Method); if (lambda != null) diff --git a/Signum.Engine/Database.cs b/Signum.Engine/Database.cs index 5eb3a93441..c277f8fc30 100644 --- a/Signum.Engine/Database.cs +++ b/Signum.Engine/Database.cs @@ -58,7 +58,7 @@ public static T Save(this T entity) } catch (Exception e) { - e.Data["entity"] = ((Entity)(IEntity)entity).BaseToString(); + e.Data["entity"] = entity; throw; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs index 60d26b3ccf..a76a901668 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs @@ -1487,6 +1487,26 @@ protected override MemberAssignment VisitMemberAssignment(MemberAssignment assig return assignment; } + protected override Expression VisitConditional(ConditionalExpression c) // a.IsNew + { + Expression test = this.Visit(c.Test); + if (test is ConstantExpression co) + { + if ((bool)co.Value!) + return this.Visit(c.IfTrue); + else + return this.Visit(c.IfFalse); + } + + Expression ifTrue = this.Visit(c.IfTrue); + Expression ifFalse = this.Visit(c.IfFalse); + if (test != c.Test || ifTrue != c.IfTrue || ifFalse != c.IfFalse) + { + return Expression.Condition(test, ifTrue, ifFalse); + } + return c; + } + protected override Expression VisitMember(MemberExpression m) { Expression ex = base.VisitMember(m); @@ -1539,6 +1559,7 @@ public Expression BindMethodCall(MethodCallExpression m) return result; } + if (source is ImplementedByExpression ib) { if (m.Method.IsExtensionMethod()) @@ -1625,9 +1646,39 @@ public Expression BindMethodCall(MethodCallExpression m) return tablePeriod; } + Expression PartialEval(Expression ee) + { + if (m.Method.IsExtensionMethod()) + { + return ExpressionEvaluator.PartialEval(Expression.Call(null, m.Method, m.Arguments.Skip(1).PreAnd(ee))); + } + else + { + return ExpressionEvaluator.PartialEval(Expression.Call(ee, m.Method, m.Arguments)); + } + } + + if (source is TypeEntityExpression type) + { + return Condition(Expression.NotEqual(type.ExternalId, NullId(type.ExternalId.ValueType)), + ifTrue: PartialEval(Expression.Constant(type.TypeValue)), + ifFalse: Expression.Constant(null, m.Type)); + } + + if(source is TypeImplementedByExpression typeIB) + { + return typeIB.TypeImplementations.Aggregate( + (Expression)Expression.Constant(null, m.Type), + (acum, kvp) => Condition(Expression.NotEqual(kvp.Value, NullId(kvp.Value.Value.Type)), + ifTrue: PartialEval(Expression.Constant(kvp.Key)), + ifFalse: acum)); + } + return m; } + + private ConditionalExpression DispatchConditional(MethodCallExpression m, Expression test, Expression ifTrue, Expression ifFalse) { @@ -1748,6 +1799,9 @@ public Expression BindMemberAccess(MemberExpression m) if (pi != null && ReflectionTools.PropertyEquals(pi, EntityExpression.IdOrNullProperty)) return ee.ExternalId; + if (m.Member.Name == nameof(Entity.IsNew)) + return Expression.Constant(false); + FieldInfo? fi = m.Member as FieldInfo ?? Reflector.TryFindFieldInfo(ee.Type, pi!); if (fi == null) @@ -1862,6 +1916,25 @@ public Expression BindMemberAccess(MemberExpression m) _ => throw new InvalidOperationException("The member {0} of MListElement is not accesible on queries".FormatWith(m.Member)), }; } + case DbExpressionType.TypeEntity: + { + TypeEntityExpression type = (TypeEntityExpression)source; + + return Condition(Expression.NotEqual(type.ExternalId, NullId(type.ExternalId.ValueType)), + ifTrue: ExpressionEvaluator.PartialEval(Expression.MakeMemberAccess(Expression.Constant(type.TypeValue), m.Member)), + ifFalse: Expression.Constant(null, m.Type)); + } + + case DbExpressionType.TypeImplementedBy: + { + TypeImplementedByExpression typeIB = (TypeImplementedByExpression)source; + + return typeIB.TypeImplementations.Aggregate( + (Expression)Expression.Constant(null, m.Type), + (acum, kvp) => Condition(Expression.NotEqual(kvp.Value, NullId(kvp.Value.Value.Type)), + ifTrue: ExpressionEvaluator.PartialEval(Expression.MakeMemberAccess(Expression.Constant(kvp.Key), m.Member)), + ifFalse: acum)); + } } } break; @@ -3477,6 +3550,7 @@ protected override Expression VisitConditional(ConditionalExpression c) var ifTrue = Visit(c.IfTrue); var ifFalse = Visit(c.IfFalse); + if (colExpression is LiteReferenceExpression) { return Combiner(ifTrue, ifFalse, (col, l, r) => diff --git a/Signum.Engine/Operations/OperationLogic.cs b/Signum.Engine/Operations/OperationLogic.cs index 65a05e5596..d43f7f2f0d 100644 --- a/Signum.Engine/Operations/OperationLogic.cs +++ b/Signum.Engine/Operations/OperationLogic.cs @@ -329,7 +329,7 @@ public static Dictionary ServiceCanExecute(Entity entit } catch(Exception e) { - e.Data["entity"] = entity.BaseToString(); + e.Data["entity"] = entity; throw; } } diff --git a/Signum.Entities/Entity.cs b/Signum.Entities/Entity.cs index f62979c284..6edfa7775b 100644 --- a/Signum.Entities/Entity.cs +++ b/Signum.Entities/Entity.cs @@ -66,15 +66,12 @@ protected bool SetIfNew(ref T field, T value, [CallerMemberName]string? autom return base.Set(ref field, value, automaticPropertyName!); } - public override string ToString() - { - return BaseToString(); - } - public string BaseToString() - { - return "{0} ({1})".FormatWith(GetType().NiceName(), id.HasValue ? id.ToString() : LiteMessage.New_G.NiceToString().ForGenderAndNumber(this.GetType().GetGender())); - } + [AutoExpressionField] + public override string ToString() => As.Expression(() => BaseToString()); + + [AutoExpressionField] + public string BaseToString() => As.Expression(() => IsNew ? GetType().NewNiceName() : GetType().NiceName() + " " + Id); public override bool Equals(object? obj) { diff --git a/Signum.Entities/Lite.cs b/Signum.Entities/Lite.cs index aff48f0099..8a2f87b1d7 100644 --- a/Signum.Entities/Lite.cs +++ b/Signum.Entities/Lite.cs @@ -369,8 +369,6 @@ public enum LiteMessage IdNotValid, [Description("Invalid Format")] InvalidFormat, - [Description("New")] - New_G, [Description("Type {0} not found")] Type0NotFound, [Description("Text")] diff --git a/Signum.Entities/Reflection/Reflector.cs b/Signum.Entities/Reflection/Reflector.cs index fa878398a9..30115a62bc 100644 --- a/Signum.Entities/Reflection/Reflector.cs +++ b/Signum.Entities/Reflection/Reflector.cs @@ -480,4 +480,17 @@ public static int NumDecimals(string format) return str.Length; } + + public static string NiceCount(this Type type, int count) + { + if (count == 1) + return "1 " + type.NiceName(); + + return count + " " + type.NicePluralName(); + } + + public static string NewNiceName(this Type type) + { + return NormalWindowMessage.New0_G.NiceToString().ForGenderAndNumber(type.GetGender()).FormatWith(type.NiceName()); + } } diff --git a/Signum.React/Facades/LambdaToJavascriptConverter.cs b/Signum.React/Facades/LambdaToJavascriptConverter.cs index 0d003dc5d2..95484930cf 100644 --- a/Signum.React/Facades/LambdaToJavascriptConverter.cs +++ b/Signum.React/Facades/LambdaToJavascriptConverter.cs @@ -10,7 +10,9 @@ internal class LambdaToJavascriptConverter if (lambdaExpression == null) return null; - var body = ToJavascript(lambdaExpression.Parameters.Single(), lambdaExpression.Body); + var newLambda = (LambdaExpression)ExpressionCleaner.Clean(lambdaExpression)!; + + var body = ToJavascript(newLambda.Parameters.Single(), newLambda.Body); if (body == null) return null; @@ -30,14 +32,22 @@ internal class LambdaToJavascriptConverter if (param == expr) return "e"; - if (expr is ConstantExpression ce && expr.Type == typeof(string)) + if (expr is ConstantExpression ce) { - var str = (string)ce.Value!; + if (expr.Type == typeof(string)) + { + var str = (string)ce.Value!; - if (!str.HasText()) - return "\"\""; + if (!str.HasText()) + return "\"\""; - return "\"" + str.Replace(replacements) + "\""; + return "\"" + str.Replace(replacements) + "\""; + } + + if(ce.Value == null) + { + return "null"; + } } if (expr is MemberExpression me) @@ -58,15 +68,33 @@ internal class LambdaToJavascriptConverter return a + "." + me.Member.Name.FirstLower(); } - if (expr is BinaryExpression be && be.NodeType == ExpressionType.Add) + if (expr is BinaryExpression be) { - var a = ToJavascriptToString(param, be.Left); - var b = ToJavascriptToString(param, be.Right); + if (be.NodeType == ExpressionType.Add && be.Type == typeof(string)) + { - if (a != null && b != null) - return "(" + a + " + " + b + ")"; + var a = ToJavascriptToString(param, be.Left); + var b = ToJavascriptToString(param, be.Right); - return null; + if (a != null && b != null) + return "(" + a + " + " + b + ")"; + + return null; + } + else + { + var a = ToJavascript(param, be.Left); + var b = ToJavascript(param, be.Right); + + var op = ToJsOperator(be.NodeType); + + if (a != null && op != null && b != null) + { + return a + op + b; + } + + return null; + } } if (expr is MethodCallExpression mc) @@ -74,6 +102,35 @@ internal class LambdaToJavascriptConverter if (mc.Method.Name == "ToString") return ToJavascriptToString(param, mc.Object!, mc.TryGetArgument("format") is ConstantExpression format ? (string)format.Value! : null); + if (mc.Method.Name == "GetType" && mc.Object != null && mc.Object.Type.IsIEntity()) + { + var obj = ToJavascript(param, mc.Object!); + if (obj == null) + return null; + + return "getTypeInfo(" + obj + ")"; + } + + if (mc.Method.IsExtensionMethod() && mc.Arguments.Only()?.Type == typeof(Type)) + { + var obj = ToJavascript(param, mc.Arguments.SingleEx()); + if (obj == null) + return null; + + if (mc.Method.Name == nameof(DescriptionManager.NiceName)) + return obj + ".niceName"; + + + if (mc.Method.Name == nameof(DescriptionManager.NicePluralName)) + return obj + ".nicePluralName"; + + if (mc.Method.Name == nameof(Reflector.NewNiceName)) + return "newNiceName(" + obj + ")"; + + return null; + } + + if (mc.Method.DeclaringType == typeof(DateTime)) { switch (mc.Method.Name) @@ -129,6 +186,23 @@ internal class LambdaToJavascriptConverter return null; } + private static string? ToJsOperator(ExpressionType nodeType) + { + return nodeType switch + { + ExpressionType.Add => "+", + ExpressionType.Subtract => "-", + ExpressionType.Equal => "==", + ExpressionType.NotEqual => "!=", + ExpressionType.LessThan => "<", + ExpressionType.LessThanOrEqual => "<", + ExpressionType.GreaterThan => ">", + ExpressionType.GreaterThanOrEqual => ">=", + ExpressionType.Coalesce => "??", + _ => null + }; + } + private static string? ToJavascriptToString(ParameterExpression param, Expression expr, string? format = null) { var r = ToJavascript(param, expr); diff --git a/Signum.React/Scripts/Reflection.ts b/Signum.React/Scripts/Reflection.ts index 3ca55b1ff3..d58673080c 100644 --- a/Signum.React/Scripts/Reflection.ts +++ b/Signum.React/Scripts/Reflection.ts @@ -1,6 +1,6 @@ import { DateTime, DateTimeFormatOptions, Duration, DurationObjectUnits, Settings } from 'luxon'; import { Dic } from './Globals'; -import { ModifiableEntity, Entity, Lite, MListElement, ModelState, MixinEntity } from './Signum.Entities'; //ONLY TYPES or Cyclic problems in Webpack! +import type { ModifiableEntity, Entity, Lite, MListElement, ModelState, MixinEntity } from './Signum.Entities'; //ONLY TYPES or Cyclic problems in Webpack! import { ajaxGet } from './Services'; import { MList } from "./Signum.Entities"; import * as AppContext from './AppContext'; @@ -1506,8 +1506,6 @@ export interface ISymbol { let missingSymbols: ISymbol[] = []; - - function getMember(key: string): MemberInfo | undefined { if (!key.contains(".")) diff --git a/Signum.React/Scripts/Signum.Entities.t4s b/Signum.React/Scripts/Signum.Entities.t4s index 05defe9ddc..45f4e14a69 100644 --- a/Signum.React/Scripts/Signum.Entities.t4s +++ b/Signum.React/Scripts/Signum.Entities.t4s @@ -79,6 +79,10 @@ export function registerToString(type: Type, toSt import * as Reflection from './Reflection' +export function newNiceName(ti: Reflection.TypeInfo) { + return NormalWindowMessage.New0_G.niceToString().forGenderAndNumber(ti.gender).formatWith(ti.niceName); +} + function getOrCreateToStringFunction(type: string) { let f = toStringDictionary[type]; if (f || f === null) @@ -94,6 +98,7 @@ function getOrCreateToStringFunction(type: string) { const numberToString = Reflection.numberToString; const dateToString = Reflection.dateToString; const timeToString = Reflection.timeToString; + const getTypeInfo = Reflection.getTypeInfo; f = ti && ti.toStringFunction ? eval("(" + ti.toStringFunction + ")") : null; } catch (e) { diff --git a/Signum.React/Scripts/Signum.Entities.ts b/Signum.React/Scripts/Signum.Entities.ts index d5552e155a..ef5751419c 100644 --- a/Signum.React/Scripts/Signum.Entities.ts +++ b/Signum.React/Scripts/Signum.Entities.ts @@ -86,6 +86,10 @@ export function registerToString(type: Type, toSt import * as Reflection from './Reflection' +export function newNiceName(ti: Reflection.TypeInfo) { + return NormalWindowMessage.New0_G.niceToString().forGenderAndNumber(ti.gender).formatWith(ti.niceName); +} + function getOrCreateToStringFunction(type: string) { let f = toStringDictionary[type]; if (f || f === null) @@ -101,6 +105,7 @@ function getOrCreateToStringFunction(type: string) { const numberToString = Reflection.numberToString; const dateToString = Reflection.dateToString; const timeToString = Reflection.timeToString; + const getTypeInfo = Reflection.getTypeInfo; f = ti && ti.toStringFunction ? eval("(" + ti.toStringFunction + ")") : null; } catch (e) { @@ -355,7 +360,6 @@ export module JavascriptMessage { export module LiteMessage { export const IdNotValid = new MessageKey("LiteMessage", "IdNotValid"); export const InvalidFormat = new MessageKey("LiteMessage", "InvalidFormat"); - export const New_G = new MessageKey("LiteMessage", "New_G"); export const Type0NotFound = new MessageKey("LiteMessage", "Type0NotFound"); export const ToStr = new MessageKey("LiteMessage", "ToStr"); } diff --git a/Signum.Test/LinqProvider/GetTypeAndNewTest.cs b/Signum.Test/LinqProvider/GetTypeAndNewTest.cs new file mode 100644 index 0000000000..5ec8a2092e --- /dev/null +++ b/Signum.Test/LinqProvider/GetTypeAndNewTest.cs @@ -0,0 +1,107 @@ +using Signum.Utilities.DataStructures; +using System.IO; +using System.Text; +using Xunit.Abstractions; + +namespace Signum.Test.LinqProvider; + +public class TestOutputTextWriter : TextWriter +{ + public int Lines = 0; + + public ITestOutputHelper OutputHelper; + + public TestOutputTextWriter(ITestOutputHelper outputHelper) + { + OutputHelper = outputHelper; + } + + public override void Write(char[] buffer, int index, int count) + { + Lines++; + OutputHelper.WriteLine(new String(buffer, index, count)); + } + + public override void Write(string? value) + { + Lines++; + OutputHelper.WriteLine(value); + } + + public override Encoding Encoding + { + get { return Encoding.Default; } + } +} + +public class GetTypeAndNewTest +{ + public GetTypeAndNewTest(ITestOutputHelper output) + { + MusicStarter.StartAndLoad(); + Connector.CurrentLogger = new TestOutputTextWriter(output); + } + + [Fact] + public void TestGetType() + { + var list = (from f in Database.Query() + where f.GetType() == typeof(ArtistEntity) + select new { f.Name }).ToList(); + + Assert.NotEmpty(list); + } + + [Fact] + public void TestGetTypeFullName() + { + var list = (from f in Database.Query() + where f.GetType().FullName == typeof(ArtistEntity).FullName + select new { f.Name }).ToList(); + + Assert.NotEmpty(list); + } + + + [Fact] + public void TestGetTypeIBFullName() + { + var list = (from f in Database.Query() + where f.Author.GetType().FullName == typeof(ArtistEntity).FullName + select new { f.Name }).ToList(); + + Assert.NotEmpty(list); + } + + [Fact] + public void TestGetTypeNiceName() + { + var list = (from f in Database.Query() + where f.GetType().NiceName() == typeof(ArtistEntity).NiceName() + select new { f.Name }).ToList(); + + Assert.NotEmpty(list); + } + + [Fact] + public void TestGetTypeIBNiceName() + { + var list = (from f in Database.Query() + where f.Author.GetType().NiceName() == typeof(ArtistEntity).NiceName() + select new { f.Name }).ToList(); + + Assert.NotEmpty(list); + } + + [Fact] + public void TestIsNew() + { + var list = (from f in Database.Query() + where (f.IsNew ? "New" : "Old") == "Old" + select new { f.Name }).ToList(); + + Assert.NotEmpty(list); + } + + +}