diff --git a/Signum.Engine.Extensions/Cache/CacheLogic.cs b/Signum.Engine.Extensions/Cache/CacheLogic.cs index 6ae142ff53..de6fd39ea1 100644 --- a/Signum.Engine.Extensions/Cache/CacheLogic.cs +++ b/Signum.Engine.Extensions/Cache/CacheLogic.cs @@ -541,21 +541,20 @@ public override IEnumerable GetAllIds() return cachedTable.GetAllIds(); } - public override string GetToString(PrimaryKey id) + public override object GetLiteModel(PrimaryKey id, Type modelType, IRetriever retriever) { AssertEnabled(); - return cachedTable.GetToString(id); + return cachedTable.GetLiteModel(id, modelType, retriever); } - public override string? TryGetToString(PrimaryKey? id) + public override object? TryGetLiteModel(PrimaryKey? id, Type modelType, IRetriever retriever) { AssertEnabled(); - return cachedTable.TryGetToString(id!.Value)!; + return cachedTable.TryGetLiteModel(id!.Value, modelType, retriever)!; } - public override bool Exists(PrimaryKey id) { AssertEnabled(); @@ -589,6 +588,7 @@ public override List RequestByBackReference(IRetriever retriever, Expressi return ids.Select(id => retriever.Complete(id, e => this.Complete(e, retriever))!).ToList(); } + public Type Type { diff --git a/Signum.Engine.Extensions/Cache/CachedLiteTable.cs b/Signum.Engine.Extensions/Cache/CachedLiteTable.cs new file mode 100644 index 0000000000..3ef1c05d51 --- /dev/null +++ b/Signum.Engine.Extensions/Cache/CachedLiteTable.cs @@ -0,0 +1,257 @@ +using Signum.Utilities.Reflection; +using Signum.Engine.Linq; +using System.Data; +using Signum.Entities.Internal; +using Signum.Engine.Connection; + +namespace Signum.Engine.Cache; + +class CachedLiteTable : CachedTableBase where T : Entity +{ + public override IColumn? ParentColumn { get; set; } + + Table table; + + Alias currentAlias; + string lastPartialJoin; + string? remainingJoins; + + Func rowReader = null!; + ResetLazy> rows = null!; + Func idGetter; + Dictionary liteModelConstructors = null!; + + SemiCachedController? semiCachedController; + + public Dictionary GetRows() + { + return rows.Value; + } + + public CachedLiteTable(ICacheLogicController controller, AliasGenerator aliasGenerator, string lastPartialJoin, string? remainingJoins) + : base(controller) + { + this.table = Schema.Current.Table(typeof(T)); + + HashSet columns = new HashSet { table.PrimaryKey }; + + foreach (var modelType in Lite.GetAllLiteModelTypes(typeof(T))) + { + var modelConstructor = Lite.GetModelConstructorExpression(typeof(T), modelType); + + ToStringColumnsFinderVisitor.GatherColumns(modelConstructor, this.table, columns); + } + + var ctr = this.Constructor = new CachedTableConstructor(this, aliasGenerator, columns.ToList()); + + this.lastPartialJoin = lastPartialJoin; + this.remainingJoins = remainingJoins; + this.currentAlias = aliasGenerator.NextTableAlias(table.Name.Name); + var isPostgres = Schema.Current.Settings.IsPostgres; + + //Query + using (ObjectName.OverrideOptions(new ObjectNameOptions { AvoidDatabaseName = true })) + { + string select = "SELECT {0}\r\nFROM {1} {2}\r\n".FormatWith( + ctr.columns.ToString(c => currentAlias + "." + c.Name.SqlEscape(isPostgres), ", "), + table.Name.ToString(), + currentAlias.ToString()); + + select += this.lastPartialJoin + currentAlias + "." + table.PrimaryKey.Name.SqlEscape(isPostgres) + "\r\n" + this.remainingJoins; + + query = new SqlPreCommandSimple(select); + } + + //Reader + { + rowReader = ctr.GetRowReader(); + + idGetter = ctr.GetPrimaryKeyGetter((IColumn)table.PrimaryKey); + } + + rows = new ResetLazy>(() => + { + return SqlServerRetry.Retry(() => + { + CacheLogic.AssertSqlDependencyStarted(); + + Dictionary result = new Dictionary(); + + using (MeasureLoad()) + using (Connector.Override(Connector.Current.ForDatabase(table.Name.Schema?.Database))) + using (var tr = Transaction.ForceNew(IsolationLevel.ReadCommitted)) + { + if (CacheLogic.LogWriter != null) + CacheLogic.LogWriter.WriteLine("Load {0}".FormatWith(GetType().TypeName())); + + Connector.Current.ExecuteDataReaderOptionalDependency(query, OnChange, fr => + { + var obj = rowReader(fr); + result[idGetter(obj)] = obj; //Could be repeated joins + }); + tr.Commit(); + } + + return result; + }); + }, mode: LazyThreadSafetyMode.ExecutionAndPublication); + + if (!CacheLogic.WithSqlDependency) //Always semi + { + semiCachedController = new SemiCachedController(this); + } + } + + public override void SchemaCompleted() + { + this.liteModelConstructors = Lite.GetAllLiteModelTypes(typeof(T)) + .ToDictionary(modelType => modelType, modelType => + { + var modelConstructor = Lite.GetModelConstructorExpression(typeof(T), modelType); + var cachedModelConstructor = LiteModelExpressionVisitor.giGetCachedLiteModelConstructor.GetInvoker(typeof(T), modelType)(this.Constructor, modelConstructor); + return cachedModelConstructor; + }); + + if (this.subTables != null) + foreach (var item in this.subTables) + item.SchemaCompleted(); + } + + protected override void Reset() + { + if (rows == null) + return; + + if (CacheLogic.LogWriter != null ) + CacheLogic.LogWriter.WriteLine((rows.IsValueCreated ? "RESET {0}" : "Reset {0}").FormatWith(GetType().TypeName())); + + rows.Reset(); + } + + protected override void Load() + { + if (rows == null) + return; + + rows.Load(); + } + + + public Lite GetLite(PrimaryKey id, IRetriever retriever, Type modelType) + { + Interlocked.Increment(ref hits); + + var model = liteModelConstructors.GetOrThrow(modelType).GetModel(id, retriever); + + var lite = Lite.Create(id, model); + retriever.ModifiablePostRetrieving((LiteImp)lite); + return lite; + } + + public override int? Count + { + get { return rows.IsValueCreated ? rows.Value.Count : (int?)null; } + } + + public override Type Type + { + get { return typeof(Lite); } + } + + public override ITable Table + { + get { return table; } + } + + class ToStringColumnsFinderVisitor : ExpressionVisitor + { + ParameterExpression param; + + HashSet columns; + + Table table; + + public ToStringColumnsFinderVisitor(ParameterExpression param, HashSet columns, Table table) + { + this.param = param; + this.columns = columns; + this.table = table; + } + + public static Expression GatherColumns(LambdaExpression lambda, Table table, HashSet columns) + { + ToStringColumnsFinderVisitor toStr = new ToStringColumnsFinderVisitor( + lambda.Parameters.SingleEx(), + columns, + table + ); + + var result = toStr.Visit(lambda.Body); + + return result; + } + + + + static MethodInfo miMixin = ReflectionTools.GetMethodInfo((Entity e) => e.Mixin()).GetGenericMethodDefinition(); + + protected override Expression VisitMember(MemberExpression node) + { + if (node.Expression == param) + { + var field = table.GetField(node.Member); + var column = GetColumn(field); + columns.Add(column); + return node; + } + + if (node.Expression is MethodCallExpression me && me.Method.IsInstantiationOf(miMixin)) + { + var type = me.Method.GetGenericArguments()[0]; + var mixin = table.Mixins!.GetOrThrow(type); + var field = mixin.GetField(node.Member); + var column = GetColumn(field); + columns.Add(column); + return node; + } + + return base.VisitMember(node); + } + + protected override Expression VisitMethodCall(MethodCallExpression node) + { + var obj = base.Visit(node.Object); + var args = base.Visit(node.Arguments); + + LambdaExpression? lambda = ExpressionCleaner.GetFieldExpansion(obj?.Type, node.Method); + + if (lambda != null) + { + var replace = ExpressionReplacer.Replace(Expression.Invoke(lambda, obj == null ? args : args.PreAnd(obj))); + + return this.Visit(replace); + } + + if (node.Object == param && node.Method.Name == nameof(node.ToString)) + { + columns.Add(this.table.ToStrColumn!); + return node; + } + + return base.VisitMethodCall(node); + } + + private IColumn GetColumn(Field field) + { + if (field is FieldPrimaryKey || field is FieldValue || field is FieldTicks) + return (IColumn)field; + + throw new InvalidOperationException("{0} not supported when caching the ToString for a Lite of a transacional entity ({1})".FormatWith(field.GetType().TypeName(), this.table.Type.TypeName())); + } + } + + internal override bool Contains(PrimaryKey primaryKey) + { + return this.rows.Value.ContainsKey(primaryKey); + } +} diff --git a/Signum.Engine.Extensions/Cache/CachedTable.cs b/Signum.Engine.Extensions/Cache/CachedTable.cs index f452a70795..adc5526cac 100644 --- a/Signum.Engine.Extensions/Cache/CachedTable.cs +++ b/Signum.Engine.Extensions/Cache/CachedTable.cs @@ -1,134 +1,11 @@ using System.Collections.Concurrent; -using Signum.Utilities.Reflection; using Signum.Engine.Linq; using System.Data; using Signum.Entities.Reflection; -using Signum.Entities.Internal; using Signum.Engine.Connection; -using Microsoft.Data.SqlClient; namespace Signum.Engine.Cache; -public abstract class CachedTableBase -{ - public abstract ITable Table { get; } - - public abstract IColumn? ParentColumn { get; set; } - - internal List? subTables; - public List? SubTables { get { return subTables; } } - protected SqlPreCommandSimple query = null!; - internal ICacheLogicController controller; - internal CachedTableConstructor Constructor = null!; - - internal CachedTableBase(ICacheLogicController controller) - { - this.controller = controller; - } - - protected void OnChange(object sender, SqlNotificationEventArgs args) - { - try - { - if (args.Info == SqlNotificationInfo.Invalid && - args.Source == SqlNotificationSource.Statement && - args.Type == SqlNotificationType.Subscribe) - throw new InvalidOperationException("Invalid query for SqlDependency") { Data = { { "query", query.PlainSql() } } }; - - if (args.Info == SqlNotificationInfo.PreviousFire) - throw new InvalidOperationException("The same transaction that loaded the data is invalidating it! Table: {0} SubTables: {1} ". - FormatWith(Table, subTables?.Select(e=>e.Table).ToString(","))) { Data = { { "query", query.PlainSql() } } }; - - if (CacheLogic.LogWriter != null) - CacheLogic.LogWriter.WriteLine("Change {0}".FormatWith(GetType().TypeName())); - - Reset(); - - Interlocked.Increment(ref invalidations); - - controller.OnChange(this, args); - } - catch (Exception e) - { - e.LogException(); - } - } - - public void ResetAll(bool forceReset) - { - if (CacheLogic.LogWriter != null) - CacheLogic.LogWriter.WriteLine("ResetAll {0}".FormatWith(GetType().TypeName())); - - Reset(); - - if (forceReset) - { - invalidations = 0; - hits = 0; - loads = 0; - sumLoadTime = 0; - } - else - { - Interlocked.Increment(ref invalidations); - } - - if (subTables != null) - foreach (var st in subTables) - st.ResetAll(forceReset); - } - - public abstract void SchemaCompleted(); - - internal void LoadAll() - { - Load(); - - if (subTables != null) - foreach (var st in subTables) - st.LoadAll(); - } - - protected abstract void Load(); - protected abstract void Reset(); - - public abstract Type Type { get; } - - public abstract int? Count { get; } - - int invalidations; - public int Invalidations { get { return invalidations; } } - - protected int hits; - public int Hits { get { return hits; } } - - int loads; - public int Loads { get { return loads; } } - - long sumLoadTime; - public TimeSpan SumLoadTime - { - get { return TimeSpan.FromMilliseconds(sumLoadTime / PerfCounter.FrequencyMilliseconds); } - } - - protected IDisposable MeasureLoad() - { - long start = PerfCounter.Ticks; - - return new Disposable(() => - { - sumLoadTime += (PerfCounter.Ticks - start); - Interlocked.Increment(ref loads); - }); - } - - - internal static readonly MethodInfo ToStringMethod = ReflectionTools.GetMethodInfo((object o) => o.ToString()); - - internal abstract bool Contains(PrimaryKey primaryKey); -} - - class CachedTable : CachedTableBase where T : Entity @@ -146,19 +23,18 @@ public Dictionary GetRows() Action completer; Expression> completerExpression; Func idGetter; - Expression> toStrGetterExpression = null!; - Func toStrGetter = null!; + Dictionary liteModelConstructors = null!; public override IColumn? ParentColumn { get; set; } internal SemiCachedController? semiCachedController; - public CachedTable(ICacheLogicController controller, AliasGenerator? aliasGenerator, string? lastPartialJoin, string? remainingJoins) + public CachedTable(ICacheLogicController controller, AliasGenerator aliasGenerator, string? lastPartialJoin, string? remainingJoins) : base(controller) { this.table = Schema.Current.Table(typeof(T)); - CachedTableConstructor ctr = this.Constructor = new CachedTableConstructor(this, aliasGenerator); + CachedTableConstructor ctr = this.Constructor = new CachedTableConstructor(this, aliasGenerator, table.Columns.Values.ToList()); var isPostgres = Schema.Current.Settings.IsPostgres; //Query using (ObjectName.OverrideOptions(new ObjectNameOptions { AvoidDatabaseName = true })) @@ -231,14 +107,22 @@ public CachedTable(ICacheLogicController controller, AliasGenerator? aliasGenera public override void SchemaCompleted() { - toStrGetterExpression = ToStringExpressionVisitor.GetToString(this.Constructor, s => s.ToString()); - toStrGetter = toStrGetterExpression.Compile(); + this.liteModelConstructors = Lite.GetAllLiteModelTypes(typeof(T)) + .ToDictionary(modelType => modelType, modelType => + { + var modelConstructor = Lite.GetModelConstructorExpression(typeof(T), modelType); + var cachedModelConstructor = LiteModelExpressionVisitor.giGetCachedLiteModelConstructor.GetInvoker(typeof(T), modelType)(this.Constructor, modelConstructor); + return cachedModelConstructor; + }); + + if (this.subTables != null) foreach (var item in this.subTables) item.SchemaCompleted(); } + protected override void Reset() { if (CacheLogic.LogWriter != null) @@ -259,10 +143,7 @@ protected override void Load() rows.Load(); } - public string GetToString(PrimaryKey id) - { - return toStrGetter(id); - } + public object GetRow(PrimaryKey id) { @@ -274,14 +155,19 @@ public object GetRow(PrimaryKey id) return origin; } - public string? TryGetToString(PrimaryKey id) + public object GetLiteModel(PrimaryKey id, Type modelType, IRetriever retriever) + { + return this.liteModelConstructors.GetOrThrow(modelType).GetModel(id, retriever); + } + + public object? TryGetLiteModel(PrimaryKey id, Type modelType, IRetriever retriever) { Interlocked.Increment(ref hits); var origin = this.GetRows().TryGetC(id); if (origin == null) return null; - return toStrGetter(id); + return this.liteModelConstructors.GetOrThrow(modelType).GetModel(id, retriever); } public bool Exists(PrimaryKey id) @@ -403,487 +289,25 @@ private IColumn GetColumn(MemberInfo[] members) } } - -class CachedTableMList : CachedTableBase +public interface ICachedLiteModelConstructor { - public override IColumn? ParentColumn { get; set; } - - TableMList table; - - ResetLazy>> relationalRows; - - static ParameterExpression result = Expression.Parameter(typeof(T)); - - Func rowReader; - Expression.RowIdElement>> activatorExpression; - Func.RowIdElement> activator; - Func parentIdGetter; - Func rowIdGetter; - - public CachedTableMList(ICacheLogicController controller, TableMList table, AliasGenerator? aliasGenerator, string lastPartialJoin, string? remainingJoins) - : base(controller) - { - this.table = table; - var isPostgres = Schema.Current.Settings.IsPostgres; - CachedTableConstructor ctr = this.Constructor= new CachedTableConstructor(this, aliasGenerator); - - //Query - using (ObjectName.OverrideOptions(new ObjectNameOptions { AvoidDatabaseName = true })) - { - string select = "SELECT\r\n{0}\r\nFROM {1} {2}\r\n".FormatWith( - ctr.table.Columns.Values.ToString(c => ctr.currentAlias + "." + c.Name.SqlEscape(isPostgres), ",\r\n"), - table.Name.ToString(), - ctr.currentAlias!.ToString()); - - ctr.remainingJoins = lastPartialJoin + ctr.currentAlias + "." + table.BackReference.Name.SqlEscape(isPostgres) + "\r\n" + remainingJoins; - - query = new SqlPreCommandSimple(select); - } - - //Reader - { - rowReader = ctr.GetRowReader(); - } - - //Completer - { - List instructions = new List - { - Expression.Assign(ctr.origin, Expression.Convert(CachedTableConstructor.originObject, ctr.tupleType)), - Expression.Assign(result, ctr.MaterializeField(table.Field)) - }; - var ci = typeof(MList.RowIdElement).GetConstructor(new []{typeof(T), typeof(PrimaryKey), typeof(int?)})!; - - var order = table.Order == null ? Expression.Constant(null, typeof(int?)) : - ctr.GetTupleProperty(table.Order).Nullify(); - - instructions.Add(Expression.New(ci, result, CachedTableConstructor.NewPrimaryKey(ctr.GetTupleProperty(table.PrimaryKey)), order)); - - var block = Expression.Block(typeof(MList.RowIdElement), new[] { ctr.origin, result }, instructions); - - activatorExpression = Expression.Lambda.RowIdElement>>(block, CachedTableConstructor.originObject, CachedTableConstructor.retriever); - - activator = activatorExpression.Compile(); - - parentIdGetter = ctr.GetPrimaryKeyGetter(table.BackReference); - rowIdGetter = ctr.GetPrimaryKeyGetter(table.PrimaryKey); - } - - relationalRows = new ResetLazy>>(() => - { - return SqlServerRetry.Retry(() => - { - CacheLogic.AssertSqlDependencyStarted(); - - Dictionary> result = new Dictionary>(); - - using (MeasureLoad()) - using (Connector.Override(Connector.Current.ForDatabase(table.Name.Schema?.Database))) - using (var tr = Transaction.ForceNew(IsolationLevel.ReadCommitted)) - { - if (CacheLogic.LogWriter != null) - CacheLogic.LogWriter.WriteLine("Load {0}".FormatWith(GetType().TypeName())); - - Connector.Current.ExecuteDataReaderOptionalDependency(query, OnChange, fr => - { - object obj = rowReader(fr); - PrimaryKey parentId = parentIdGetter(obj); - var dic = result.TryGetC(parentId); - if (dic == null) - result[parentId] = dic = new Dictionary(); - - dic[rowIdGetter(obj)] = obj; - }); - tr.Commit(); - } - - return result; - }); - }, mode: LazyThreadSafetyMode.ExecutionAndPublication); - } - - protected override void Reset() - { - if (CacheLogic.LogWriter != null) - CacheLogic.LogWriter.WriteLine((relationalRows.IsValueCreated ? "RESET {0}" : "Reset {0}").FormatWith(GetType().TypeName())); - - - relationalRows.Reset(); - } - - protected override void Load() - { - relationalRows.Load(); - } - - public MList GetMList(PrimaryKey id, IRetriever retriever) - { - Interlocked.Increment(ref hits); - - MList result; - var dic = relationalRows.Value.TryGetC(id); - if (dic == null) - result = new MList(); - else - { - result = new MList(dic.Count); - var innerList = ((IMListPrivate)result).InnerList; - foreach (var obj in dic.Values) - { - innerList.Add(activator(obj, retriever)); - } - ((IMListPrivate)result).ExecutePostRetrieving(null!); - - } - - CachedTableConstructor.resetModifiedAction(retriever, result); - - return result; - } - - public override int? Count - { - get { return relationalRows.IsValueCreated ? relationalRows.Value.Count : (int?)null; } - } - - public override Type Type - { - get { return typeof(MList); } - } - - public override ITable Table - { - get { return table; } - } - - internal override bool Contains(PrimaryKey primaryKey) - { - throw new InvalidOperationException("CacheMListTable does not implements contains"); - } - - public override void SchemaCompleted() - { - if (this.subTables != null) - foreach (var item in this.subTables) - item.SchemaCompleted(); - } + object GetModel(PrimaryKey pk, IRetriever retriever); } - -class CachedLiteTable : CachedTableBase where T : Entity +public class CachedLiteModelConstructor : ICachedLiteModelConstructor + where M : notnull { - public override IColumn? ParentColumn { get; set; } - - Table table; - - Alias currentAlias; - string lastPartialJoin; - string? remainingJoins; - - Func> rowReader = null!; - ResetLazy> toStrings = null!; - - SemiCachedController? semiCachedController; - - public CachedLiteTable(ICacheLogicController controller, AliasGenerator aliasGenerator, string lastPartialJoin, string? remainingJoins) - : base(controller) - { - this.table = Schema.Current.Table(typeof(T)); - this.lastPartialJoin = lastPartialJoin; - this.remainingJoins = remainingJoins; - this.currentAlias = aliasGenerator.NextTableAlias(table.Name.Name); - - if (!CacheLogic.WithSqlDependency) - { - semiCachedController = new SemiCachedController(this); - } - } - - public override void SchemaCompleted() - { - List columns = new List { table.PrimaryKey }; - - ParameterExpression reader = Expression.Parameter(typeof(FieldReader)); - - var expression = ToStringExpressionVisitor.GetToString(table, reader, columns); - var isPostgres = Schema.Current.Settings.IsPostgres; - //Query - using (ObjectName.OverrideOptions(new ObjectNameOptions { AvoidDatabaseName = true })) - { - string select = "SELECT {0}\r\nFROM {1} {2}\r\n".FormatWith( - columns.ToString(c => currentAlias + "." + c.Name.SqlEscape(isPostgres), ", "), - table.Name.ToString(), - currentAlias.ToString()); - - select += this.lastPartialJoin + currentAlias + "." + table.PrimaryKey.Name.SqlEscape(isPostgres) + "\r\n" + this.remainingJoins; - - query = new SqlPreCommandSimple(select); - } - - //Reader - { - var kvpConstructor = Expression.New(CachedTableConstructor.ciKVPIntString, - CachedTableConstructor.NewPrimaryKey(FieldReader.GetExpression(reader, 0, this.table.PrimaryKey.Type)), - expression); - - rowReader = Expression.Lambda>>(kvpConstructor, reader).Compile(); - } - - toStrings = new ResetLazy>(() => - { - return SqlServerRetry.Retry(() => - { - CacheLogic.AssertSqlDependencyStarted(); - - Dictionary result = new Dictionary(); - - using (MeasureLoad()) - using (Connector.Override(Connector.Current.ForDatabase(table.Name.Schema?.Database))) - using (var tr = Transaction.ForceNew(IsolationLevel.ReadCommitted)) - { - if (CacheLogic.LogWriter != null) - CacheLogic.LogWriter.WriteLine("Load {0}".FormatWith(GetType().TypeName())); - - Connector.Current.ExecuteDataReaderOptionalDependency(query, OnChange, fr => - { - var kvp = rowReader(fr); - result[kvp.Key] = kvp.Value; - }); - tr.Commit(); - } - - return result; - }); - }, mode: LazyThreadSafetyMode.ExecutionAndPublication); - - if (this.subTables != null) - foreach (var item in this.subTables) - item.SchemaCompleted(); - } - - protected override void Reset() - { - if (toStrings == null) - return; - - if (CacheLogic.LogWriter != null ) - CacheLogic.LogWriter.WriteLine((toStrings.IsValueCreated ? "RESET {0}" : "Reset {0}").FormatWith(GetType().TypeName())); - - toStrings.Reset(); - } - - protected override void Load() - { - if (toStrings == null) - return; - - toStrings.Load(); - } - + Expression> Expression; //For Debugging + Func Function; - public Lite GetLite(PrimaryKey id, IRetriever retriever) - { - Interlocked.Increment(ref hits); - - var lite = (LiteImp)Lite.Create(id, toStrings.Value[id]); - - return retriever.ModifiablePostRetrieving(lite)!; - } - - public override int? Count - { - get { return toStrings.IsValueCreated ? toStrings.Value.Count : (int?)null; } - } - - public override Type Type + public CachedLiteModelConstructor(Expression> expression) { - get { return typeof(Lite); } - } - - public override ITable Table - { - get { return table; } - } - - class ToStringExpressionVisitor : ExpressionVisitor - { - ParameterExpression param; - ParameterExpression reader; - - List columns; - - Table table; - - public ToStringExpressionVisitor(ParameterExpression param, ParameterExpression reader, List columns, Table table) - { - this.param = param; - this.reader = reader; - this.columns = columns; - this.table = table; - } - - public static Expression GetToString(Table table, ParameterExpression reader, List columns) - { - LambdaExpression lambda = ExpressionCleaner.GetFieldExpansion(table.Type, CachedTableBase.ToStringMethod)!; - - if (lambda == null) - { - columns.Add(table.ToStrColumn!); - - return FieldReader.GetExpression(reader, columns.Count - 1, typeof(string)); - } - - var cleanLambda = (LambdaExpression)ExpressionCleaner.Clean(lambda, ExpressionEvaluator.PartialEval, shortCircuit: false)!; - - ToStringExpressionVisitor toStr = new ToStringExpressionVisitor( - cleanLambda.Parameters.SingleEx(), - reader, - columns, - table - ); - - var result = toStr.Visit(cleanLambda.Body); - - return result; - } - - protected override Expression VisitUnary(UnaryExpression node) - { - if (node.NodeType == ExpressionType.Convert) - { - var obj = Visit(node.Operand); - - return Expression.Convert(obj, node.Type); - } - - return base.VisitUnary(node); - } - - static MethodInfo miMixin = ReflectionTools.GetMethodInfo((Entity e) => e.Mixin()).GetGenericMethodDefinition(); - - protected override Expression VisitMember(MemberExpression node) - { - if (node.Expression == param) - { - var field = table.GetField(node.Member); - var column = GetColumn(field); - columns.Add(column); - return FieldReader.GetExpression(reader, columns.Count - 1, column.Type); - } - - if (node.Expression is MethodCallExpression me && me.Method.IsInstantiationOf(miMixin)) - { - var type = me.Method.GetGenericArguments()[0]; - var mixin = table.Mixins!.GetOrThrow(type); - var field = mixin.GetField(node.Member); - var column = GetColumn(field); - columns.Add(column); - return FieldReader.GetExpression(reader, columns.Count - 1, column.Type); - } - - return base.VisitMember(node); - } - - private IColumn GetColumn(Field field) - { - if (field is FieldPrimaryKey || field is FieldValue || field is FieldTicks) - return (IColumn)field; - - throw new InvalidOperationException("{0} not supported when caching the ToString for a Lite of a transacional entity ({1})".FormatWith(field.GetType().TypeName(), this.table.Type.TypeName())); - } - } - - internal override bool Contains(PrimaryKey primaryKey) - { - return this.toStrings.Value.ContainsKey(primaryKey); - } -} - -public class SemiCachedController where T : Entity -{ - CachedTableBase cachedTable; - - public SemiCachedController(CachedTableBase cachedTable) - { - this.cachedTable = cachedTable; - - CacheLogic.semiControllers.GetOrCreate(typeof(T)).Add(cachedTable); - - var ee = Schema.Current.EntityEvents(); - ee.Saving += ident => - { - if (ident.IsGraphModified && !ident.IsNew) - { - cachedTable.LoadAll(); - - if (cachedTable.Contains(ident.Id)) - DisableAndInvalidate(); - } - }; - //ee.PreUnsafeDelete += query => DisableAndInvalidate(); - ee.PreUnsafeUpdate += (update, entityQuery) => { DisableAndInvalidateMassive(entityQuery); return null; }; - ee.PreUnsafeInsert += (query, constructor, entityQuery) => - { - if (constructor.Body.Type.IsInstantiationOf(typeof(MListElement<,>))) - DisableAndInvalidateMassive(entityQuery); - - return constructor; - }; - ee.PreUnsafeMListDelete += (mlistQuery, entityQuery) => { DisableAndInvalidateMassive(entityQuery); return null; }; - ee.PreBulkInsert += inMListTable => - { - if (inMListTable) - DisableAndInvalidateMassive(null); - }; - } - - public int? MassiveInvalidationCheckLimit = 500; - - void DisableAndInvalidateMassive(IQueryable? elements) - { - var asssumeAsInvalidation = CacheLogic.assumeMassiveChangesAsInvalidations.Value?.TryGetS(typeof(T)); - - if (asssumeAsInvalidation == false) - { - - } - else if(asssumeAsInvalidation == true) - { - DisableAndInvalidate(); - } - else if (asssumeAsInvalidation == null) //Default - { - if (MassiveInvalidationCheckLimit != null && elements != null) - { - var ids = elements.Select(a => a.Id).Distinct().Take(MassiveInvalidationCheckLimit.Value).ToList(); - if (ids.Count == MassiveInvalidationCheckLimit.Value) - throw new InvalidOperationException($"MassiveInvalidationCheckLimit reached when trying to determine if the massive operation will affect the semi-cached instances of {typeof(T).TypeName()}."); - - cachedTable.LoadAll(); - - if (ids.Any(cachedTable.Contains)) - DisableAndInvalidate(); - else - return; - } - else - { - throw new InvalidOperationException($"Impossible to determine if the massive operation will affect the semi-cached instances of {typeof(T).TypeName()}. Execute CacheLogic.AssumeMassiveChangesAsInvalidations to desanbiguate."); - } - } - } - - void DisableAndInvalidate() - { - CacheLogic.DisableAllConnectedTypesInTransaction(this.cachedTable.controller.Type); - - Transaction.PostRealCommit -= Transaction_PostRealCommit; - Transaction.PostRealCommit += Transaction_PostRealCommit; + this.Expression = expression; + this.Function = expression.Compile(); } - void Transaction_PostRealCommit(Dictionary obj) + public object GetModel(PrimaryKey pk, IRetriever retriever) { - cachedTable.ResetAll(forceReset: false); - CacheLogic.NotifyInvalidateAllConnectedTypes(this.cachedTable.controller.Type); + return Function(pk, retriever); } } diff --git a/Signum.Engine.Extensions/Cache/CachedTableBase.cs b/Signum.Engine.Extensions/Cache/CachedTableBase.cs new file mode 100644 index 0000000000..890c762023 --- /dev/null +++ b/Signum.Engine.Extensions/Cache/CachedTableBase.cs @@ -0,0 +1,124 @@ +using Signum.Utilities.Reflection; +using System.Data; +using Microsoft.Data.SqlClient; + +namespace Signum.Engine.Cache; + +public abstract class CachedTableBase +{ + public abstract ITable Table { get; } + + public abstract IColumn? ParentColumn { get; set; } + + internal List? subTables; + public List? SubTables { get { return subTables; } } + protected SqlPreCommandSimple query = null!; + internal ICacheLogicController controller; + internal CachedTableConstructor Constructor = null!; + + internal CachedTableBase(ICacheLogicController controller) + { + this.controller = controller; + } + + protected void OnChange(object sender, SqlNotificationEventArgs args) + { + try + { + if (args.Info == SqlNotificationInfo.Invalid && + args.Source == SqlNotificationSource.Statement && + args.Type == SqlNotificationType.Subscribe) + throw new InvalidOperationException("Invalid query for SqlDependency") { Data = { { "query", query.PlainSql() } } }; + + if (args.Info == SqlNotificationInfo.PreviousFire) + throw new InvalidOperationException("The same transaction that loaded the data is invalidating it! Table: {0} SubTables: {1} ". + FormatWith(Table, subTables?.Select(e=>e.Table).ToString(","))) { Data = { { "query", query.PlainSql() } } }; + + if (CacheLogic.LogWriter != null) + CacheLogic.LogWriter.WriteLine("Change {0}".FormatWith(GetType().TypeName())); + + Reset(); + + Interlocked.Increment(ref invalidations); + + controller.OnChange(this, args); + } + catch (Exception e) + { + e.LogException(); + } + } + + public void ResetAll(bool forceReset) + { + if (CacheLogic.LogWriter != null) + CacheLogic.LogWriter.WriteLine("ResetAll {0}".FormatWith(GetType().TypeName())); + + Reset(); + + if (forceReset) + { + invalidations = 0; + hits = 0; + loads = 0; + sumLoadTime = 0; + } + else + { + Interlocked.Increment(ref invalidations); + } + + if (subTables != null) + foreach (var st in subTables) + st.ResetAll(forceReset); + } + + public abstract void SchemaCompleted(); + + internal void LoadAll() + { + Load(); + + if (subTables != null) + foreach (var st in subTables) + st.LoadAll(); + } + + protected abstract void Load(); + protected abstract void Reset(); + + public abstract Type Type { get; } + + public abstract int? Count { get; } + + int invalidations; + public int Invalidations { get { return invalidations; } } + + protected int hits; + public int Hits { get { return hits; } } + + int loads; + public int Loads { get { return loads; } } + + long sumLoadTime; + public TimeSpan SumLoadTime + { + get { return TimeSpan.FromMilliseconds(sumLoadTime / PerfCounter.FrequencyMilliseconds); } + } + + protected IDisposable MeasureLoad() + { + long start = PerfCounter.Ticks; + + return new Disposable(() => + { + sumLoadTime += (PerfCounter.Ticks - start); + Interlocked.Increment(ref loads); + }); + } + + + internal static readonly MethodInfo ToStringMethod = ReflectionTools.GetMethodInfo((object o) => o.ToString()); + + internal abstract bool Contains(PrimaryKey primaryKey); +} diff --git a/Signum.Engine.Extensions/Cache/CachedTableConstructor.cs b/Signum.Engine.Extensions/Cache/CachedTableConstructor.cs index 5d7870ec76..a918c28130 100644 --- a/Signum.Engine.Extensions/Cache/CachedTableConstructor.cs +++ b/Signum.Engine.Extensions/Cache/CachedTableConstructor.cs @@ -8,34 +8,33 @@ internal class CachedTableConstructor { public CachedTableBase cachedTable; public ITable table; + public List columns; public ParameterExpression origin; - public AliasGenerator? aliasGenerator; + public AliasGenerator aliasGenerator; public Alias? currentAlias; public Type tupleType; public string? remainingJoins; - public CachedTableConstructor(CachedTableBase cachedTable, AliasGenerator? aliasGenerator) + public CachedTableConstructor(CachedTableBase cachedTable, AliasGenerator aliasGenerator, List columns) { this.cachedTable = cachedTable; this.table = cachedTable.Table; + this.columns = columns; - if (aliasGenerator != null) - { - this.aliasGenerator = aliasGenerator; - this.currentAlias = aliasGenerator.NextTableAlias(table.Name.Name); - } + this.aliasGenerator = aliasGenerator; + this.currentAlias = aliasGenerator.NextTableAlias(table.Name.Name); - this.tupleType = TupleReflection.TupleChainType(table.Columns.Values.Select(GetColumnType)); + this.tupleType = TupleReflection.TupleChainType(this.columns.Select(GetColumnType)); this.origin = Expression.Parameter(tupleType, "origin"); } public Expression GetTupleProperty(IColumn column) { - return TupleReflection.TupleChainProperty(origin, table.Columns.Values.IndexOf(column)); + return TupleReflection.TupleChainProperty(origin, columns.IndexOf(column)); } internal string CreatePartialInnerJoin(IColumn column) @@ -53,7 +52,7 @@ internal Func GetRowReader() ParameterExpression reader = Expression.Parameter(typeof(FieldReader)); var tupleConstructor = TupleReflection.TupleChainConstructor( - table.Columns.Values.Select((c, i) => FieldReader.GetExpression(reader, i, GetColumnType(c))) + columns.Select((c, i) => FieldReader.GetExpression(reader, i, GetColumnType(c))) ); return Expression.Lambda>(tupleConstructor, reader).Compile(); @@ -61,7 +60,7 @@ internal Func GetRowReader() internal Func GetPrimaryKeyGetter(IColumn column) { - var access = TupleReflection.TupleChainProperty(Expression.Convert(originObject, tupleType), table.Columns.Values.IndexOf(column)); + var access = TupleReflection.TupleChainProperty(Expression.Convert(originObject, tupleType), columns.IndexOf(column)); var primaryKey = NewPrimaryKey(access); @@ -70,7 +69,7 @@ internal Func GetPrimaryKeyGetter(IColumn column) internal Func GetPrimaryKeyNullableGetter(IColumn column) { - var access = TupleReflection.TupleChainProperty(Expression.Convert(originObject, tupleType), table.Columns.Values.IndexOf(column)); + var access = TupleReflection.TupleChainProperty(Expression.Convert(originObject, tupleType), columns.IndexOf(column)); var primaryKey = WrapPrimaryKey(access); @@ -85,7 +84,7 @@ internal static Expression NewPrimaryKey(Expression expression) } - static GenericInvoker> ciCachedTable = + static GenericInvoker> ciCachedTable = new((controller, aliasGenerator, lastPartialJoin, remainingJoins) => new CachedTable(controller, aliasGenerator, lastPartialJoin, remainingJoins)); @@ -93,7 +92,7 @@ internal static Expression NewPrimaryKey(Expression expression) new((controller, aliasGenerator, lastPartialJoin, remainingJoins) => new CachedLiteTable(controller, aliasGenerator, lastPartialJoin, remainingJoins)); - static GenericInvoker> ciCachedTableMList = + static GenericInvoker> ciCachedTableMList = new((controller, relationalTable, aliasGenerator, lastPartialJoin, remainingJoins) => new CachedTableMList(controller, relationalTable, aliasGenerator, lastPartialJoin, remainingJoins)); @@ -115,20 +114,20 @@ public Expression MaterializeField(Field field) var nullRef = Expression.Constant(null, field.FieldType); bool isLite = fr.IsLite; - if (field is FieldReference) + if (field is FieldReference fr2) { IColumn column = (IColumn)field; - return GetEntity(isLite, column, field.FieldType.CleanType()); + return GetEntity(isLite, column, field.FieldType.CleanType(), fr2.CustomLiteModelType); } if (field is FieldImplementedBy ib) { var call = ib.ImplementationColumns.Aggregate((Expression)nullRef, (acum, kvp) => { - IColumn column = (IColumn)kvp.Value; + var column = kvp.Value; - Expression entity = GetEntity(isLite, column, kvp.Key); + Expression entity = GetEntity(isLite, column, kvp.Key, column.CustomLiteModelType); return Expression.Condition(Expression.NotEqual(WrapPrimaryKey(GetTupleProperty(column)), NullId), Expression.Convert(entity, field.FieldType), @@ -165,64 +164,74 @@ public Expression MaterializeField(Field field) if (field is FieldEmbedded fe) { - var bindings = new List(); - var embParam = Expression.Parameter(fe.FieldType); - bindings.Add(Expression.Assign(embParam, Expression.New(fe.FieldType))); - bindings.Add(Expression.Call(retriever, miModifiablePostRetrieving.MakeGenericMethod(embParam.Type), embParam)); - - foreach (var f in fe.EmbeddedFields.Values) - { - Expression value = MaterializeField(f.Field); - var assigment = Expression.Assign(Expression.Field(embParam, f.FieldInfo), value); - bindings.Add(assigment); - } + return MaterializeEmbedded(fe); + } - if (fe.Mixins != null) - { - foreach (var mixin in fe.Mixins.Values) - { - ParameterExpression mixParam = Expression.Parameter(mixin.FieldType); - var mixBlock = MaterializeMixin(embParam, mixin, mixParam); - bindings.Add(mixBlock); - } - } - bindings.Add(embParam); + if (field is FieldMList mListField) + { + return MaterializeMList(mListField); + } - Expression block = Expression.Block(new[] { embParam }, bindings); + throw new InvalidOperationException("Unexpected {0}".FormatWith(field.GetType().Name)); + } - if (fe.HasValue == null) - return block; + private Expression MaterializeMList(FieldMList mListField) + { + var idColumn = columns.OfType().First(); - return Expression.Condition( - Expression.Equal(GetTupleProperty(fe.HasValue), Expression.Constant(true)), - block, - Expression.Constant(null, field.FieldType)); - } + string lastPartialJoin = CreatePartialInnerJoin(idColumn); + Type elementType = mListField.FieldType.ElementType()!; - if (field is FieldMList mListField) - { - var idColumn = table.Columns.Values.OfType().First(); + CachedTableBase ctb = ciCachedTableMList.GetInvoker(elementType)(cachedTable.controller, mListField.TableMList, aliasGenerator, lastPartialJoin, remainingJoins); - string lastPartialJoin = CreatePartialInnerJoin(idColumn); + if (cachedTable.subTables == null) + cachedTable.subTables = new List(); - Type elementType = field.FieldType.ElementType()!; + cachedTable.subTables.Add(ctb); - CachedTableBase ctb = ciCachedTableMList.GetInvoker(elementType)(cachedTable.controller, mListField.TableMList, aliasGenerator, lastPartialJoin, remainingJoins); + return Expression.Call(Expression.Constant(ctb), ctb.GetType().GetMethod(nameof(CachedTableMList.GetMList))!, NewPrimaryKey(GetTupleProperty(idColumn)), retriever); + } - if (cachedTable.subTables == null) - cachedTable.subTables = new List(); + internal Expression MaterializeEmbedded(FieldEmbedded fe) + { + var bindings = new List(); + var embParam = Expression.Parameter(fe.FieldType); + bindings.Add(Expression.Assign(embParam, Expression.New(fe.FieldType))); + bindings.Add(Expression.Call(retriever, miModifiablePostRetrieving.MakeGenericMethod(embParam.Type), embParam)); - cachedTable.subTables.Add(ctb); + foreach (var f in fe.EmbeddedFields.Values) + { + Expression value = MaterializeField(f.Field); + var assigment = Expression.Assign(Expression.Field(embParam, f.FieldInfo), value); + bindings.Add(assigment); + } - return Expression.Call(Expression.Constant(ctb), ctb.GetType().GetMethod(nameof(CachedTableMList.GetMList))!, NewPrimaryKey(GetTupleProperty(idColumn)), retriever); + if (fe.Mixins != null) + { + foreach (var mixin in fe.Mixins.Values) + { + ParameterExpression mixParam = Expression.Parameter(mixin.FieldType); + var mixBlock = MaterializeMixin(embParam, mixin, mixParam); + bindings.Add(mixBlock); + } } - throw new InvalidOperationException("Unexpected {0}".FormatWith(field.GetType().Name)); + bindings.Add(embParam); + + Expression block = Expression.Block(new[] { embParam }, bindings); + + if (fe.HasValue == null) + return block; + + return Expression.Condition( + Expression.Equal(GetTupleProperty(fe.HasValue), Expression.Constant(true)), + block, + Expression.Constant(null, fe.FieldType)); } - private Expression GetEntity(bool isLite, IColumn column, Type type) + internal Expression GetEntity(bool isLite, IColumn column, Type type, Type? customLiteModelType) { Expression id = GetTupleProperty(column); @@ -233,8 +242,10 @@ private Expression GetEntity(bool isLite, IColumn column, Type type) { case CacheType.Cached: { + var modelType = customLiteModelType ?? Lite.DefaultModelType(type); + lite = Expression.Call(retriever, miRequestLite.MakeGenericMethod(type), - Lite.NewExpression(type, NewPrimaryKey(id.UnNullify()), Expression.Constant(null, typeof(string)))); + Expression.New(Lite.GetLiteConstructorFromCache(type, modelType), NewPrimaryKey(id.UnNullify()), Expression.Constant(null, modelType))); lite = Expression.Call(retriever, miModifiablePostRetrieving.MakeGenericMethod(typeof(LiteImp)), lite.TryConvert(typeof(LiteImp))).TryConvert(lite.Type); @@ -253,7 +264,12 @@ private Expression GetEntity(bool isLite, IColumn column, Type type) ctb.ParentColumn = column; - lite = Expression.Call(Expression.Constant(ctb), ctb.GetType().GetMethod("GetLite")!, NewPrimaryKey(id.UnNullify()), retriever); + var modelType = customLiteModelType ?? Lite.DefaultModelType(type); + + lite = Expression.Call(Expression.Constant(ctb), ctb.GetType().GetMethod(nameof(CachedLiteTable.GetLite))!, + NewPrimaryKey(id.UnNullify()), + retriever, + Expression.Constant(modelType)); break; } @@ -322,9 +338,6 @@ public static Lite GetIBALite(Schema schema, PrimaryKey typeId, IComparabl public static MemberExpression peModified = Expression.Property(retriever, ReflectionTools.GetPropertyInfo((IRetriever me) => me.ModifiedState)); - public static ConstructorInfo ciKVPIntString = ReflectionTools.GetConstuctorInfo(() => new KeyValuePair(1, "")); - - public static Action resetModifiedAction; static CachedTableConstructor() @@ -348,6 +361,11 @@ Expression GetMixin(ParameterExpression me, Type mixinType) internal BlockExpression MaterializeEntity(ParameterExpression me, Table table) { + if(table.Name.Name == "Employee") + { + + } + List instructions = new List(); instructions.Add(Expression.Assign(origin, Expression.Convert(CachedTableConstructor.originObject, tupleType))); diff --git a/Signum.Engine.Extensions/Cache/CachedTableMList.cs b/Signum.Engine.Extensions/Cache/CachedTableMList.cs new file mode 100644 index 0000000000..9c2ef805ae --- /dev/null +++ b/Signum.Engine.Extensions/Cache/CachedTableMList.cs @@ -0,0 +1,170 @@ +using Signum.Engine.Linq; +using System.Data; +using Signum.Engine.Connection; + +namespace Signum.Engine.Cache; + +class CachedTableMList : CachedTableBase +{ + public override IColumn? ParentColumn { get; set; } + + TableMList table; + + ResetLazy>> relationalRows; + + static ParameterExpression result = Expression.Parameter(typeof(T)); + + Func rowReader; + Expression.RowIdElement>> activatorExpression; + Func.RowIdElement> activator; + Func parentIdGetter; + Func rowIdGetter; + + public CachedTableMList(ICacheLogicController controller, TableMList table, AliasGenerator aliasGenerator, string lastPartialJoin, string? remainingJoins) + : base(controller) + { + this.table = table; + var isPostgres = Schema.Current.Settings.IsPostgres; + CachedTableConstructor ctr = this.Constructor = new CachedTableConstructor(this, aliasGenerator, table.Columns.Values.ToList()); + + //Query + using (ObjectName.OverrideOptions(new ObjectNameOptions { AvoidDatabaseName = true })) + { + string select = "SELECT\r\n{0}\r\nFROM {1} {2}\r\n".FormatWith( + ctr.table.Columns.Values.ToString(c => ctr.currentAlias + "." + c.Name.SqlEscape(isPostgres), ",\r\n"), + table.Name.ToString(), + ctr.currentAlias!.ToString()); + + ctr.remainingJoins = lastPartialJoin + ctr.currentAlias + "." + table.BackReference.Name.SqlEscape(isPostgres) + "\r\n" + remainingJoins; + + query = new SqlPreCommandSimple(select); + } + + //Reader + { + rowReader = ctr.GetRowReader(); + } + + //Completer + { + List instructions = new List + { + Expression.Assign(ctr.origin, Expression.Convert(CachedTableConstructor.originObject, ctr.tupleType)), + Expression.Assign(result, ctr.MaterializeField(table.Field)) + }; + var ci = typeof(MList.RowIdElement).GetConstructor(new []{typeof(T), typeof(PrimaryKey), typeof(int?)})!; + + var order = table.Order == null ? Expression.Constant(null, typeof(int?)) : + ctr.GetTupleProperty(table.Order).Nullify(); + + instructions.Add(Expression.New(ci, result, CachedTableConstructor.NewPrimaryKey(ctr.GetTupleProperty(table.PrimaryKey)), order)); + + var block = Expression.Block(typeof(MList.RowIdElement), new[] { ctr.origin, result }, instructions); + + activatorExpression = Expression.Lambda.RowIdElement>>(block, CachedTableConstructor.originObject, CachedTableConstructor.retriever); + + activator = activatorExpression.Compile(); + + parentIdGetter = ctr.GetPrimaryKeyGetter(table.BackReference); + rowIdGetter = ctr.GetPrimaryKeyGetter(table.PrimaryKey); + } + + relationalRows = new ResetLazy>>(() => + { + return SqlServerRetry.Retry(() => + { + CacheLogic.AssertSqlDependencyStarted(); + + Dictionary> result = new Dictionary>(); + + using (MeasureLoad()) + using (Connector.Override(Connector.Current.ForDatabase(table.Name.Schema?.Database))) + using (var tr = Transaction.ForceNew(IsolationLevel.ReadCommitted)) + { + if (CacheLogic.LogWriter != null) + CacheLogic.LogWriter.WriteLine("Load {0}".FormatWith(GetType().TypeName())); + + Connector.Current.ExecuteDataReaderOptionalDependency(query, OnChange, fr => + { + object obj = rowReader(fr); + PrimaryKey parentId = parentIdGetter(obj); + var dic = result.TryGetC(parentId); + if (dic == null) + result[parentId] = dic = new Dictionary(); + + dic[rowIdGetter(obj)] = obj; + }); + tr.Commit(); + } + + return result; + }); + }, mode: LazyThreadSafetyMode.ExecutionAndPublication); + } + + protected override void Reset() + { + if (CacheLogic.LogWriter != null) + CacheLogic.LogWriter.WriteLine((relationalRows.IsValueCreated ? "RESET {0}" : "Reset {0}").FormatWith(GetType().TypeName())); + + + relationalRows.Reset(); + } + + protected override void Load() + { + relationalRows.Load(); + } + + public MList GetMList(PrimaryKey id, IRetriever retriever) + { + Interlocked.Increment(ref hits); + + MList result; + var dic = relationalRows.Value.TryGetC(id); + if (dic == null) + result = new MList(); + else + { + result = new MList(dic.Count); + var innerList = ((IMListPrivate)result).InnerList; + foreach (var obj in dic.Values) + { + innerList.Add(activator(obj, retriever)); + } + ((IMListPrivate)result).ExecutePostRetrieving(null!); + + } + + CachedTableConstructor.resetModifiedAction(retriever, result); + + return result; + } + + public override int? Count + { + get { return relationalRows.IsValueCreated ? relationalRows.Value.Count : (int?)null; } + } + + public override Type Type + { + get { return typeof(MList); } + } + + public override ITable Table + { + get { return table; } + } + + internal override bool Contains(PrimaryKey primaryKey) + { + throw new InvalidOperationException("CacheMListTable does not implements contains"); + } + + public override void SchemaCompleted() + { + if (this.subTables != null) + foreach (var item in this.subTables) + item.SchemaCompleted(); + } +} diff --git a/Signum.Engine.Extensions/Cache/SemiCachedController.cs b/Signum.Engine.Extensions/Cache/SemiCachedController.cs new file mode 100644 index 0000000000..360b5ba495 --- /dev/null +++ b/Signum.Engine.Extensions/Cache/SemiCachedController.cs @@ -0,0 +1,92 @@ +using System.Data; + +namespace Signum.Engine.Cache; + +public class SemiCachedController where T : Entity +{ + CachedTableBase cachedTable; + + public SemiCachedController(CachedTableBase cachedTable) + { + this.cachedTable = cachedTable; + + CacheLogic.semiControllers.GetOrCreate(typeof(T)).Add(cachedTable); + + var ee = Schema.Current.EntityEvents(); + ee.Saving += ident => + { + if (ident.IsGraphModified && !ident.IsNew) + { + cachedTable.LoadAll(); + + if (cachedTable.Contains(ident.Id)) + DisableAndInvalidate(); + } + }; + //ee.PreUnsafeDelete += query => DisableAndInvalidate(); + ee.PreUnsafeUpdate += (update, entityQuery) => { DisableAndInvalidateMassive(entityQuery); return null; }; + ee.PreUnsafeInsert += (query, constructor, entityQuery) => + { + if (constructor.Body.Type.IsInstantiationOf(typeof(MListElement<,>))) + DisableAndInvalidateMassive(entityQuery); + + return constructor; + }; + ee.PreUnsafeMListDelete += (mlistQuery, entityQuery) => { DisableAndInvalidateMassive(entityQuery); return null; }; + ee.PreBulkInsert += inMListTable => + { + if (inMListTable) + DisableAndInvalidateMassive(null); + }; + } + + public int? MassiveInvalidationCheckLimit = 500; + + void DisableAndInvalidateMassive(IQueryable? elements) + { + var asssumeAsInvalidation = CacheLogic.assumeMassiveChangesAsInvalidations.Value?.TryGetS(typeof(T)); + + if (asssumeAsInvalidation == false) + { + + } + else if(asssumeAsInvalidation == true) + { + DisableAndInvalidate(); + } + else if (asssumeAsInvalidation == null) //Default + { + if (MassiveInvalidationCheckLimit != null && elements != null) + { + var ids = elements.Select(a => a.Id).Distinct().Take(MassiveInvalidationCheckLimit.Value).ToList(); + if (ids.Count == MassiveInvalidationCheckLimit.Value) + throw new InvalidOperationException($"MassiveInvalidationCheckLimit reached when trying to determine if the massive operation will affect the semi-cached instances of {typeof(T).TypeName()}."); + + cachedTable.LoadAll(); + + if (ids.Any(cachedTable.Contains)) + DisableAndInvalidate(); + else + return; + } + else + { + throw new InvalidOperationException($"Impossible to determine if the massive operation will affect the semi-cached instances of {typeof(T).TypeName()}. Execute CacheLogic.AssumeMassiveChangesAsInvalidations to desanbiguate."); + } + } + } + + void DisableAndInvalidate() + { + CacheLogic.DisableAllConnectedTypesInTransaction(this.cachedTable.controller.Type); + + Transaction.PostRealCommit -= Transaction_PostRealCommit; + Transaction.PostRealCommit += Transaction_PostRealCommit; + } + + void Transaction_PostRealCommit(Dictionary obj) + { + cachedTable.ResetAll(forceReset: false); + CacheLogic.NotifyInvalidateAllConnectedTypes(this.cachedTable.controller.Type); + } +} diff --git a/Signum.Engine.Extensions/Cache/ToStringExpressionVisitor.cs b/Signum.Engine.Extensions/Cache/ToStringExpressionVisitor.cs index d7ea5ad2aa..986b52cf76 100644 --- a/Signum.Engine.Extensions/Cache/ToStringExpressionVisitor.cs +++ b/Signum.Engine.Extensions/Cache/ToStringExpressionVisitor.cs @@ -1,22 +1,27 @@ using Signum.Entities.Reflection; using Signum.Utilities.DataStructures; using Signum.Utilities.Reflection; +using System.Diagnostics.CodeAnalysis; namespace Signum.Engine.Cache; -class ToStringExpressionVisitor : ExpressionVisitor +class LiteModelExpressionVisitor : ExpressionVisitor { Dictionary replacements = new Dictionary(); CachedEntityExpression root; - public ToStringExpressionVisitor(ParameterExpression param, CachedEntityExpression root) + public LiteModelExpressionVisitor(ParameterExpression param, CachedEntityExpression root) { this.root = root; this.replacements = new Dictionary { { param, root } }; } - public static Expression> GetToString(CachedTableConstructor constructor, Expression> lambda) + public static GenericInvoker> giGetCachedLiteModelConstructor = + new((ctc, la) => GetCachedLiteModelConstructor(ctc, (Expression>)la)); + public static CachedLiteModelConstructor GetCachedLiteModelConstructor(CachedTableConstructor constructor, Expression> lambda) + where T : Entity + where M : notnull { Table table = (Table)constructor.table; @@ -27,13 +32,25 @@ public static Expression> GetToString(CachedTableCon var pk = Expression.Parameter(typeof(PrimaryKey), "pk"); - var root = new CachedEntityExpression(pk, typeof(T), constructor, null, null); + var root = new CachedEntityExpression(pk, typeof(T), false, constructor, column: null, previousConstructor: null); - var visitor = new ToStringExpressionVisitor(param, root); + var tab = Expression.Constant(constructor.cachedTable); + var origin = Expression.Convert(Expression.Property(Expression.Call(tab, "GetRows", null), "Item", pk), constructor.tupleType); - var result = visitor.Visit(lambda.Body); + var result = new LiteModelExpressionVisitor(param, root).Visit(lambda.Body); + var result2 = new CachedEntityRemoverVisitor().Visit(result); - return Expression.Lambda>(result, pk); + var block = Expression.Block( + variables: new[] { constructor.origin }, + expressions: new[] { + Expression.Assign(constructor.origin, origin), + result2 + } + ); + + var lambdaExpression = Expression.Lambda>(block, pk, CachedTableConstructor.retriever); + + return new CachedLiteModelConstructor(lambdaExpression); } protected override Expression VisitMember(MemberExpression node) @@ -80,19 +97,13 @@ private Expression BindMember(CachedEntityExpression n, Field field, Expression? { Expression body = GetField(field, n.Constructor, prevPrimaryKey); - ConstantExpression tab = Expression.Constant(n.Constructor.cachedTable, typeof(CachedTable<>).MakeGenericType(((Table)n.Constructor.table).Type)); - - Expression origin = Expression.Convert(Expression.Property(Expression.Call(tab, "GetRows", null), "Item", n.PrimaryKey.UnNullify()), n.Constructor.tupleType); - - var result = ExpressionReplacer.Replace(body, new Dictionary { { n.Constructor.origin, origin } }); - if (!n.PrimaryKey.Type.IsNullable()) - return result; + return body; return Expression.Condition( Expression.Equal(n.PrimaryKey, Expression.Constant(null, n.PrimaryKey.Type)), - Expression.Constant(null, result.Type.Nullify()), - result.Nullify()); + Expression.Constant(null, body.Type.Nullify()), + body.Nullify()); } private Expression GetField(Field field, CachedTableConstructor constructor, Expression? previousPrimaryKey) @@ -145,12 +156,12 @@ private Expression GetField(Field field, CachedTableConstructor constructor, Exp if (field is FieldEmbedded fe) { - return new CachedEntityExpression(previousPrimaryKey!, fe.FieldType, constructor, fe, null); + return new CachedEntityExpression(previousPrimaryKey!,constructor, fe); } if (field is FieldMixin fm) { - return new CachedEntityExpression(previousPrimaryKey!, fm.FieldType, constructor, null, fm); + return new CachedEntityExpression(previousPrimaryKey!, constructor, fm); } if (field is FieldMList) @@ -171,7 +182,7 @@ private Expression GetEntity(bool isLite, IColumn column, Type entityType, Cache CacheLogic.GetCachedTable(entityType).Constructor : constructor.cachedTable.SubTables!.SingleEx(a => a.ParentColumn == column).Constructor; - return new CachedEntityExpression(pk, entityType, typeConstructor, null, null); + return new CachedEntityExpression(pk, entityType, isLite, typeConstructor, column, constructor); } protected override Expression VisitUnary(UnaryExpression node) @@ -208,8 +219,6 @@ protected override Expression VisitMethodCall(MethodCallExpression node) return node.Update(null!, new Sequence { formatStr, remainging }); } - - } var obj = base.Visit(node.Object); @@ -230,9 +239,9 @@ protected override Expression VisitMethodCall(MethodCallExpression node) ConstantExpression tab = Expression.Constant(ce.Constructor.cachedTable, cachedTableType); - var mi = cachedTableType.GetMethod(nameof(CachedTable.GetToString))!; + var mi = cachedTableType.GetMethod(nameof(CachedTable.GetLiteModel))!; - return Expression.Call(tab, mi, ce.PrimaryKey.UnNullify()); + return Expression.Convert(Expression.Call(tab, mi, ce.PrimaryKey.UnNullify(), Expression.Constant(typeof(string)), CachedTableConstructor.retriever), typeof(string)); } } @@ -354,39 +363,53 @@ public override ExpressionType NodeType { get { return ExpressionType.Extension; } } - public readonly CachedTableConstructor Constructor; public readonly Expression PrimaryKey; public readonly FieldEmbedded? FieldEmbedded; public readonly FieldMixin? FieldMixin; + public readonly IColumn? Column; + public readonly CachedTableConstructor? PreviousConstructor; + public readonly bool isLite; public readonly Type type; - public override Type Type { get { return type; } } + public override Type Type { get { return isLite ? Lite.Generate(type) : type; } } + - public CachedEntityExpression(Expression primaryKey, Type type, CachedTableConstructor constructor, FieldEmbedded? embedded, FieldMixin? mixin) + public CachedEntityExpression(Expression previousPrimaryKey, CachedTableConstructor constructor, FieldEmbedded embedded) + : this(previousPrimaryKey, constructor, embedded.FieldType) { - if (primaryKey == null) - throw new ArgumentNullException(nameof(primaryKey)); + this.FieldEmbedded = embedded; + } - if (primaryKey.Type.UnNullify() != typeof(PrimaryKey)) - throw new InvalidOperationException("primaryKey should be a PrimaryKey"); - if (type.IsEmbeddedEntity()) - { - this.FieldEmbedded = embedded ?? throw new ArgumentNullException(nameof(embedded)); - } - else if (type.IsMixinEntity()) - { - this.FieldMixin = mixin ?? throw new ArgumentNullException(nameof(mixin)); - } - else + public CachedEntityExpression(Expression previousPrimaryKey, CachedTableConstructor constructor, FieldMixin mixin) + : this(previousPrimaryKey, constructor, mixin.FieldType) + { + this.FieldMixin = mixin; + } + + public CachedEntityExpression(Expression primaryKey, Type type, bool isLite, CachedTableConstructor constructor, IColumn? column, CachedTableConstructor? previousConstructor) + : this(primaryKey, constructor, type) + { + this.isLite = isLite; + + if (column != null) { - if (((Table)constructor.table).Type != type.CleanType()) - throw new InvalidOperationException("Wrong type"); + if (previousConstructor!.table.Columns[column.Name] != column) + throw new InvalidOperationException("Wrong Previous Constructor"); + + this.Column = column; + this.PreviousConstructor = previousConstructor; } + } + + CachedEntityExpression(Expression primaryKey, CachedTableConstructor constructor, Type type) + { + if (primaryKey.Type.UnNullify() != typeof(PrimaryKey)) + throw new InvalidOperationException("primaryKey should be a PrimaryKey"); - this.PrimaryKey = primaryKey; + this.PrimaryKey = primaryKey; this.type = type; this.Constructor = constructor; } @@ -401,7 +424,12 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) if (pk == this.PrimaryKey) return this; - return new CachedEntityExpression(pk, type, Constructor, FieldEmbedded, FieldMixin); + if(this.FieldEmbedded != null) + return new CachedEntityExpression(pk, Constructor, FieldEmbedded); + if (this.FieldMixin != null) + return new CachedEntityExpression(pk, Constructor, FieldMixin); + else + return new CachedEntityExpression(pk, type, isLite, Constructor, Column, PreviousConstructor); } public override string ToString() @@ -409,3 +437,37 @@ public override string ToString() return $"CachedEntityExpression({Type.TypeName()}, {PrimaryKey})"; } } + + +public class CachedEntityRemoverVisitor : ExpressionVisitor +{ + + [return: NotNullIfNotNull("node")] + public override Expression? Visit(Expression? node) + { + if (node is CachedEntityExpression ce) + { + if (ce.FieldEmbedded != null) + return ce.Constructor.MaterializeEmbedded(ce.FieldEmbedded); + + if (ce.FieldMixin != null) + throw new NotImplementedException("Unable to get isolated Mixins from Cache for the Lite Model "); + + if (ce.Column != null) + { + var customModelType = ce.Column switch + { + FieldReference fr => fr.CustomLiteModelType, + ImplementationColumn ic => ic.CustomLiteModelType, + _ => throw new UnexpectedValueException(ce.Column) + }; + + return ce.PreviousConstructor!.GetEntity(ce.isLite, ce.Column, ce.type, customModelType); + } + else + throw new NotImplementedException("Unable to get the full Entity from cache for LiteModel "); + } + + return base.Visit(node); + } +} diff --git a/Signum.Engine.Extensions/Migrations/SqlMigrationRunner.cs b/Signum.Engine.Extensions/Migrations/SqlMigrationRunner.cs index f4c869d84e..1158576597 100644 --- a/Signum.Engine.Extensions/Migrations/SqlMigrationRunner.cs +++ b/Signum.Engine.Extensions/Migrations/SqlMigrationRunner.cs @@ -250,7 +250,7 @@ private static void Execute(MigrationInfo mi) var script = text.Replace(DatabaseNameReplacement, databaseName); - SqlPreCommandExtensions.ExecuteScript(title, text); + SqlPreCommandExtensions.ExecuteScript(title, script); MigrationLogic.EnsureMigrationTable(); diff --git a/Signum.Engine.Extensions/Notes/NoteLogic.cs b/Signum.Engine.Extensions/Notes/NoteLogic.cs index 711de6d8a6..50042fa6bb 100644 --- a/Signum.Engine.Extensions/Notes/NoteLogic.cs +++ b/Signum.Engine.Extensions/Notes/NoteLogic.cs @@ -78,7 +78,7 @@ public static void RegisterNoteType(NoteTypeSymbol noteType) CreatedBy = user ?? UserEntity.Current, Text = text, Title = title, - Target = (Lite)Lite.Create(entity.EntityType, entity.Id, entity.ToString()), + Target = (Lite)entity, NoteType = noteType }.Execute(NoteOperation.Save); } diff --git a/Signum.Engine/Database.cs b/Signum.Engine/Database.cs index 8758f7c8d7..37bad09ff0 100644 --- a/Signum.Engine/Database.cs +++ b/Signum.Engine/Database.cs @@ -256,23 +256,23 @@ public static Task RetrieveAsync(Type type, PrimaryKey id, CancellationT return giRetrieveAsync.GetInvoker(type)(id, token); } - public static Lite? TryRetrieveLite(Type type, PrimaryKey id) + public static Lite? TryRetrieveLite(Type type, PrimaryKey id, Type? modelType = null) { - return giTryRetrieveLite.GetInvoker(type)(id); + return giTryRetrieveLite.GetInvoker(type)(id, modelType); } - public static Lite RetrieveLite(Type type, PrimaryKey id) + public static Lite RetrieveLite(Type type, PrimaryKey id, Type? modelType = null) { - return giRetrieveLite.GetInvoker(type)(id); + return giRetrieveLite.GetInvoker(type)(id, modelType); } - public static Task> RetrieveLiteAsync(Type type, PrimaryKey id, CancellationToken token) + public static Task> RetrieveLiteAsync(Type type, PrimaryKey id, CancellationToken token, Type? modelType = null) { - return giRetrieveLiteAsync.GetInvoker(type)(id, token); + return giRetrieveLiteAsync.GetInvoker(type)(id, token, modelType); } - static readonly GenericInvoker?>> giTryRetrieveLite = new(id => TryRetrieveLite(id)); - public static Lite? TryRetrieveLite(PrimaryKey id) + static readonly GenericInvoker?>> giTryRetrieveLite = new((id, modelType) => TryRetrieveLite(id, modelType)); + public static Lite? TryRetrieveLite(PrimaryKey id, Type? modelType = null) where T : Entity { using (HeavyProfiler.Log("DBRetrieve", () => "TryRetrieveLite<{0}>".FormatWith(typeof(T).TypeName()))) @@ -283,8 +283,17 @@ public static Task> RetrieveLiteAsync(Type type, PrimaryKey id, Can if (cc != null && GetFilterQuery() == null) { if (!cc.Exists(id)) - return null; - return new LiteImp(id, cc.GetToString(id)); + return null; + + using (new EntityCache()) + using (var rr = EntityCache.NewRetriever()) + { + var model = cc.GetLiteModel(id, modelType ?? Lite.DefaultModelType(typeof(T)), rr); + + rr.CompleteAll(); + + return Lite.Create(id, model); + } } var result = Database.Query().Select(a => a.ToLite()).SingleOrDefaultEx(a => a.Id == id); @@ -304,8 +313,8 @@ public static Task> RetrieveLiteAsync(Type type, PrimaryKey id, Can } - static readonly GenericInvoker>> giRetrieveLite = new(id => RetrieveLite(id)); - public static Lite RetrieveLite(PrimaryKey id) + static readonly GenericInvoker>> giRetrieveLite = new((id, modelType) => RetrieveLite(id, modelType)); + public static Lite RetrieveLite(PrimaryKey id, Type? modelType = null) where T : Entity { using (HeavyProfiler.Log("DBRetrieve", () => "RetrieveLite<{0}>".FormatWith(typeof(T).TypeName()))) @@ -315,7 +324,15 @@ public static Lite RetrieveLite(PrimaryKey id) var cc = GetCacheController(); if (cc != null && GetFilterQuery() == null) { - return new LiteImp(id, cc.GetToString(id)); + using (new EntityCache()) + using (var rr = EntityCache.NewRetriever()) + { + var model = cc.GetLiteModel(id, modelType ?? Lite.DefaultModelType(typeof(T)), rr); + + rr.CompleteAll(); + + return Lite.Create(id, model); + } } var result = Database.Query().Select(a => a.ToLite()).SingleOrDefaultEx(a => a.Id == id); @@ -334,9 +351,9 @@ public static Lite RetrieveLite(PrimaryKey id) } } - static readonly GenericInvoker>>> giRetrieveLiteAsync = - new((id, token) => RetrieveLiteAsync(id, token)); - public static async Task> RetrieveLiteAsync(PrimaryKey id, CancellationToken token) + static readonly GenericInvoker>>> giRetrieveLiteAsync = + new((id, token, modelType) => RetrieveLiteAsync(id, token, modelType)); + public static async Task> RetrieveLiteAsync(PrimaryKey id, CancellationToken token, Type? modelType = null) where T : Entity { using (HeavyProfiler.Log("DBRetrieve", () => "RetrieveLiteAsync<{0}>".FormatWith(typeof(T).TypeName()))) @@ -346,7 +363,15 @@ public static async Task> RetrieveLiteAsync(PrimaryKey id, Cancellati var cc = GetCacheController(); if (cc != null && GetFilterQuery() == null) { - return new LiteImp(id, cc.GetToString(id)); + using (new EntityCache()) + using (var rr = EntityCache.NewRetriever()) + { + var model = cc.GetLiteModel(id, modelType ?? Lite.DefaultModelType(typeof(T)), rr); + + await rr.CompleteAllAsync(token); + + return Lite.Create(id, model); + } } var result = await Database.Query().Select(a => a.ToLite()).SingleOrDefaultAsync(a => a.Id == id, token); @@ -365,24 +390,24 @@ public static async Task> RetrieveLiteAsync(PrimaryKey id, Cancellati } } - public static Lite FillToString(this Lite lite) where T : class, IEntity + public static Lite FillLiteModel(this Lite lite) where T : class, IEntity { - lite.SetToString(GetToStr(lite.EntityType, lite.Id)); + lite.SetModel(GetLiteModel(lite.EntityType, lite.Id, lite.ModelType)); return lite; } - public static async Task> FillToStringAsync(this Lite lite, CancellationToken token) where T : class, IEntity + public static async Task> FillLiteModelAsync(this Lite lite, CancellationToken token) where T : class, IEntity { - lite.SetToString(await GetToStrAsync(lite.EntityType, lite.Id, token)); + lite.SetModel(await GetLiteModelAsync(lite.EntityType, lite.Id, token, lite.ModelType)); return lite; } - public static string GetToStr(Type type, PrimaryKey id) => giGetToStr.GetInvoker(type)(id); - static readonly GenericInvoker> giGetToStr = new(id => GetToStr(id)); - public static string GetToStr(PrimaryKey id) + public static object GetLiteModel(Type type, PrimaryKey id, Type? modelType = null) => giGetLiteModel.GetInvoker(type)(id, modelType); + static readonly GenericInvoker> giGetLiteModel = new((id, modelType) => GetLiteModel(id, modelType)); + public static object GetLiteModel(PrimaryKey id, Type? modelType) where T : Entity { try @@ -391,7 +416,17 @@ public static string GetToStr(PrimaryKey id) { var cc = GetCacheController(); if (cc != null && GetFilterQuery() == null) - return cc.GetToString(id); + { + using (new EntityCache()) + using (var rr = EntityCache.NewRetriever()) + { + var model = cc.GetLiteModel(id, modelType ?? Lite.DefaultModelType(typeof(T)), rr); + + rr.CompleteAll(); + + return model; + } + } return Database.Query().Where(a => a.Id == id).Select(a => a.ToString()).FirstEx(); } @@ -406,10 +441,10 @@ public static string GetToStr(PrimaryKey id) - public static Task GetToStrAsync(Type type, PrimaryKey id, CancellationToken token) => giGetToStrAsync.GetInvoker(type)(id, token); - static readonly GenericInvoker>> giGetToStrAsync = - new((id, token) => GetToStrAsync(id, token)); - public static async Task GetToStrAsync(PrimaryKey id, CancellationToken token) + public static Task GetLiteModelAsync(Type type, PrimaryKey id, CancellationToken token, Type? modelType) => giGetToStrAsync.GetInvoker(type)(id, token, modelType); + static readonly GenericInvoker>> giGetToStrAsync = + new((id, token, modelType) => GetLiteModelAsync(id, token, modelType)); + public static async Task GetLiteModelAsync(PrimaryKey id, CancellationToken token, Type? modelType = null) where T : Entity { try @@ -418,7 +453,17 @@ public static async Task GetToStrAsync(PrimaryKey id, CancellationTok { var cc = GetCacheController(); if (cc != null && GetFilterQuery() == null) - return cc.GetToString(id); + { + using (new EntityCache()) + using (var rr = EntityCache.NewRetriever()) + { + var model = cc.GetLiteModel(id, modelType ?? Lite.DefaultModelType(typeof(T)), rr); + + await rr.CompleteAllAsync(token); + + return model; + } + } return await Database.Query().Where(a => a.Id == id).Select(a => a.ToString()).FirstAsync(token); } @@ -606,8 +651,8 @@ public static async Task> RetrieveAllAsync(Type type, CancellationT } - static readonly GenericInvoker> giRetrieveAllLite = new(() => Database.RetrieveAllLite()); - public static List> RetrieveAllLite() + static readonly GenericInvoker> giRetrieveAllLite = new(modelType => Database.RetrieveAllLite(modelType)); + public static List> RetrieveAllLite(Type? modelType = null) where T : Entity { try @@ -617,7 +662,17 @@ public static List> RetrieveAllLite() var cc = GetCacheController(); if (cc != null && GetFilterQuery() == null) { - return cc.GetAllIds().Select(id => (Lite)new LiteImp(id, cc.GetToString(id))).ToList(); + var mt = modelType ?? Lite.DefaultModelType(typeof(T)); + + using (new EntityCache()) + using (var rr = EntityCache.NewRetriever()) + { + var model = cc.GetAllIds().Select(id => Lite.Create(id, cc.GetLiteModel(id, mt, rr))).ToList(); + + rr.CompleteAll(); + + return model; + } } return Database.Query().Select(e => e.ToLite()).ToList(); @@ -633,7 +688,7 @@ public static List> RetrieveAllLite() static readonly GenericInvoker>> giRetrieveAllLiteAsync = new(token => Database.RetrieveAllLiteAsyncIList(token)); static Task RetrieveAllLiteAsyncIList(CancellationToken token) where T : Entity => RetrieveAllLiteAsync(token).ContinueWith(r => (IList)r.Result); - public static async Task>> RetrieveAllLiteAsync(CancellationToken token) + public static async Task>> RetrieveAllLiteAsync(CancellationToken token, Type? modelType = null) where T : Entity { try @@ -643,7 +698,17 @@ public static async Task>> RetrieveAllLiteAsync(CancellationToke var cc = GetCacheController(); if (cc != null && GetFilterQuery() == null) { - return cc.GetAllIds().Select(id => (Lite)new LiteImp(id, cc.GetToString(id))).ToList(); + var mt = modelType ?? Lite.DefaultModelType(typeof(T)); + + using (new EntityCache()) + using (var rr = EntityCache.NewRetriever()) + { + var model = cc.GetAllIds().Select(id => Lite.Create(id, cc.GetLiteModel(id, mt, rr))).ToList(); + + await rr.CompleteAllAsync(token); + + return model; + } } return await Database.Query().Select(e => e.ToLite()).ToListAsync(token); @@ -656,12 +721,12 @@ public static async Task>> RetrieveAllLiteAsync(CancellationToke } } - public static List> RetrieveAllLite(Type type) + public static List> RetrieveAllLite(Type type, Type? modelType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); - IList list = giRetrieveAllLite.GetInvoker(type)(); + IList list = giRetrieveAllLite.GetInvoker(type)(modelType); return list.Cast>().ToList(); } @@ -869,9 +934,9 @@ public static async Task> RetrieveListAsync(Type type, List().ToList(); } - static readonly GenericInvoker, IList>> giRetrieveListLite = - new(ids => RetrieveListLite(ids)); - public static List> RetrieveListLite(List ids) + static readonly GenericInvoker, Type?, IList>> giRetrieveListLite = + new((ids, modelType) => RetrieveListLite(ids, modelType)); + public static List> RetrieveListLite(List ids, Type? modelType = null) where T : Entity { using (HeavyProfiler.Log("DBRetrieve", () => "List>".FormatWith(typeof(T).TypeName()))) @@ -882,7 +947,17 @@ public static List> RetrieveListLite(List ids) var cc = GetCacheController(); if (cc != null && GetFilterQuery() == null) { - return ids.Select(id => (Lite)new LiteImp(id, cc.GetToString(id))).ToList(); + var mt = modelType ?? Lite.DefaultModelType(typeof(T)); + + using (new EntityCache()) + using (var rr = EntityCache.NewRetriever()) + { + var model = ids.Select(id => Lite.Create(id, cc.GetLiteModel(id, mt, rr))).ToList(); + + rr.CompleteAll(); + + return model; + } } var retrieved = ids.Chunk(Schema.Current.Settings.MaxNumberOfParameters).SelectMany(gr => Database.Query().Where(a => gr.Contains(a.Id)).Select(a => a.ToLite())).ToDictionary(a => a.Id); @@ -899,7 +974,7 @@ public static List> RetrieveListLite(List ids) static readonly GenericInvoker, CancellationToken, Task>> giRetrieveListLiteAsync = new((ids, token) => RetrieveListLiteAsyncIList(ids, token)); static Task RetrieveListLiteAsyncIList(List ids, CancellationToken token) where T : Entity => RetrieveListLiteAsync(ids, token).ContinueWith(t => (IList)t.Result); - public static async Task>> RetrieveListLiteAsync(List ids, CancellationToken token) + public static async Task>> RetrieveListLiteAsync(List ids, CancellationToken token, Type? modelType = null) where T : Entity { using (HeavyProfiler.Log("DBRetrieve", () => "List>".FormatWith(typeof(T).TypeName()))) @@ -910,7 +985,17 @@ public static async Task>> RetrieveListLiteAsync(List(); if (cc != null && GetFilterQuery() == null) { - return ids.Select(id => (Lite)new LiteImp(id, cc.GetToString(id))).ToList(); + var mt = modelType ?? Lite.DefaultModelType(typeof(T)); + + using (new EntityCache()) + using (var rr = EntityCache.NewRetriever()) + { + var model = ids.Select(id => Lite.Create(id, cc.GetLiteModel(id, mt, rr))).ToList(); + + await rr.CompleteAllAsync(token); + + return model; + } } var tasks = ids.Chunk(Schema.Current.Settings.MaxNumberOfParameters) @@ -930,12 +1015,12 @@ public static async Task>> RetrieveListLiteAsync(List> RetrieveListLite(Type type, List ids) + public static List> RetrieveListLite(Type type, List ids, Type? modelType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); - IList list = giRetrieveListLite.GetInvoker(type).Invoke(ids); + IList list = giRetrieveListLite.GetInvoker(type).Invoke(ids, modelType); return list.Cast>().ToList(); } diff --git a/Signum.Engine/Json/LiteJsonConverter.cs b/Signum.Engine/Json/LiteJsonConverter.cs index 8d39cc1a03..eb00a95ee4 100644 --- a/Signum.Engine/Json/LiteJsonConverter.cs +++ b/Signum.Engine/Json/LiteJsonConverter.cs @@ -28,10 +28,26 @@ public override void Write(Utf8JsonWriter writer, Lite value, JsonSerializerO writer.WriteString("EntityType", TypeLogic.GetCleanName(lite.EntityType)); + if (lite.ModelType != Lite.DefaultModelType(lite.EntityType)) + writer.WriteString("ModelType", Lite.ModelTypeToString(lite.ModelType)); + writer.WritePropertyName("id"); JsonSerializer.Serialize(writer, lite.IdOrNull?.Object, lite.IdOrNull?.Object.GetType() ?? typeof(object), options); - writer.WriteString("toStr", lite.ToString()); + if(lite.Model != null) + { + if(lite.Model is string str) + writer.WriteString("model", str); + else + { + writer.WritePropertyName("model"); + + var pr = PropertyRoute.Root(lite.Model.GetType()); + var model = (ModelEntity)lite.Model; + using (EntityJsonContext.SetCurrentPropertyRouteAndEntity((pr, model, null))) + JsonSerializer.Serialize(writer, model, options); + } + } if (lite.EntityOrNull != null) { @@ -53,9 +69,10 @@ public override void Write(Utf8JsonWriter writer, Lite value, JsonSerializerO reader.Assert(JsonTokenType.StartObject); - string? toString = null; + object? model = null; string? idObj = null; string? typeStr = null; + string? modelTypeStr = null; Entity? entity = null; reader.Read(); @@ -64,7 +81,7 @@ public override void Write(Utf8JsonWriter writer, Lite value, JsonSerializerO var propName = reader.GetString(); switch (propName) { - case "toStr": reader.Read(); toString = reader.GetString(); break; + case "EntityType": reader.Read(); typeStr = reader.GetString(); break; case "id": { reader.Read(); @@ -78,11 +95,24 @@ public override void Write(Utf8JsonWriter writer, Lite value, JsonSerializerO break; } - case "EntityType": reader.Read(); typeStr = reader.GetString(); break; + case "ModelType": reader.Read(); modelTypeStr = reader.GetString(); break; + case "model": + reader.Read(); + if (reader.TokenType == JsonTokenType.String) + model = reader.GetString(); + else + { + using (EntityJsonConverterFactory.SetPath(".model")) + { + var converter = (JsonConverterWithExisting)options.GetConverter(typeof(ModelEntity)); + model = converter.Read(ref reader, typeof(ModelEntity), options, (ModelEntity?)existingValue?.Model); + } + } + break; case "entity": + reader.Read(); using (EntityJsonConverterFactory.SetPath(".entity")) { - reader.Read(); var converter = (JsonConverterWithExisting)options.GetConverter(typeof(Entity)); entity = converter.Read(ref reader, typeof(Entity), options, (Entity?)(IEntity?)existingValue?.EntityOrNull); } @@ -99,10 +129,19 @@ public override void Write(Utf8JsonWriter writer, Lite value, JsonSerializerO PrimaryKey? idOrNull = idObj == null ? (PrimaryKey?)null : PrimaryKey.Parse(idObj, type); + + Type modelType = modelTypeStr == null ? Lite.DefaultModelType(type) : Lite.ParseModelType(type, modelTypeStr); + if (entity == null) - return (Lite)Lite.Create(type, idOrNull!.Value, toString!); + { + return model != null ? + (Lite)Lite.Create(type, idOrNull!.Value, model) : + (Lite)Lite.Create(type, idOrNull!.Value, modelType); + } - var result = (Lite)entity.ToLiteFat(toString); + var result = model != null ? + (Lite)entity.ToLiteFat(model) : + (Lite)entity.ToLiteFat(modelType); ; if (result.EntityType != type) throw new InvalidOperationException("Types don't match"); diff --git a/Signum.Engine/Linq/DbExpressions.Signum.cs b/Signum.Engine/Linq/DbExpressions.Signum.cs index 0884a41caf..89b6c87615 100644 --- a/Signum.Engine/Linq/DbExpressions.Signum.cs +++ b/Signum.Engine/Linq/DbExpressions.Signum.cs @@ -295,12 +295,13 @@ protected override Expression Accept(DbExpressionVisitor visitor) internal class LiteReferenceExpression : DbExpression { - public bool LazyToStr; + public bool LazyModel; public bool EagerEntity; public readonly Expression Reference; //Fie, ImplementedBy, ImplementedByAll or Constant to NullEntityExpression - public readonly Expression? CustomToStr; //Not readonly + public readonly Expression? CustomModelExpression; + public readonly ReadOnlyDictionary? CustomModelTypes; - public LiteReferenceExpression(Type type, Expression reference, Expression? customToStr, bool lazyToStr, bool eagerEntity) : + public LiteReferenceExpression(Type type, Expression reference, Expression? customModelExpression, ReadOnlyDictionary? customModelTypes, bool lazyModel, bool eagerEntity) : base(DbExpressionType.LiteReference, type) { Type? cleanType = Lite.Extract(type); @@ -308,17 +309,24 @@ public LiteReferenceExpression(Type type, Expression reference, Expression? cust if (cleanType != reference.Type) throw new ArgumentException("The type {0} is not the Lite version of {1}".FormatWith(type.TypeName(), reference.Type.TypeName())); + if (customModelExpression != null && customModelTypes != null) + throw new InvalidOperationException($"{nameof(customModelExpression)} and {nameof(customModelTypes)} are incompatible"); + this.Reference = reference; - this.CustomToStr = customToStr; + this.CustomModelExpression = customModelExpression; + this.CustomModelTypes = customModelTypes; - this.LazyToStr = lazyToStr; + this.LazyModel = lazyModel; this.EagerEntity = eagerEntity; } public override string ToString() { - return "({0}).ToLite({1})".FormatWith(Reference.ToString(), CustomToStr == null ? null : ("customToStr: " + CustomToStr.ToString())); + return "({0}).ToLite({1})".FormatWith(Reference.ToString(), + CustomModelExpression != null ? ("custmoModelExpression: " + CustomModelExpression.ToString()) : + CustomModelTypes != null ? ("custmoModelTypes: {\n" + CustomModelTypes.ToString(kvp => kvp.Key.TypeName() + ": " + kvp.Value.TypeName(), "\n, ").Indent(4) + "\n}") : + null); } protected override Expression Accept(DbExpressionVisitor visitor) @@ -331,37 +339,71 @@ internal LiteReferenceExpression WithExpandLite(ExpandLite expandLite) switch (expandLite) { case ExpandLite.EntityEager: - return new LiteReferenceExpression(this.Type, this.Reference, this.CustomToStr, lazyToStr: false, eagerEntity: true); - case ExpandLite.ToStringEager: - return new LiteReferenceExpression(this.Type, this.Reference, this.CustomToStr, lazyToStr: false, eagerEntity: false); - case ExpandLite.ToStringLazy: - return new LiteReferenceExpression(this.Type, this.Reference, this.CustomToStr, lazyToStr: true, eagerEntity: false); - case ExpandLite.ToStringNull: - return new LiteReferenceExpression(this.Type, this.Reference, Expression.Constant(null, typeof(string)), lazyToStr: true, eagerEntity: false); + return new LiteReferenceExpression(this.Type, this.Reference, this.CustomModelExpression, this.CustomModelTypes, lazyModel: false, eagerEntity: true); + case ExpandLite.ModelEager: + return new LiteReferenceExpression(this.Type, this.Reference, this.CustomModelExpression, this.CustomModelTypes, lazyModel: false, eagerEntity: false); + case ExpandLite.ModelLazy: + return new LiteReferenceExpression(this.Type, this.Reference, this.CustomModelExpression, this.CustomModelTypes, lazyModel: true, eagerEntity: false); + case ExpandLite.ModelNull: + return new LiteReferenceExpression(this.Type, this.Reference, Expression.Constant(null, typeof(string)), null, lazyModel: true, eagerEntity: false); default: throw new NotImplementedException(); } } } +public struct ExpressionOrType +{ + public readonly Expression? EagerExpression; + public readonly Type? LazyModelType; + + public ExpressionOrType(Type lazyModelType) + { + LazyModelType = lazyModelType; + EagerExpression = null; + } + + public ExpressionOrType(Expression eagerExpression) + { + LazyModelType = null; + EagerExpression = eagerExpression; + } + + public override string ToString() + { + return LazyModelType?.TypeName() ?? EagerExpression!.ToString(); + } +} + internal class LiteValueExpression : DbExpression { public readonly Expression TypeId; public readonly PrimaryKeyExpression Id; - public readonly Expression? ToStr; + public readonly Expression? CustomModelExpression; + public readonly ReadOnlyDictionary? Models; - public LiteValueExpression(Type type, Expression typeId, PrimaryKeyExpression id, Expression? toStr) : + public LiteValueExpression(Type type, Expression typeId, PrimaryKeyExpression id, Expression? customModelExpression, ReadOnlyDictionary? models) : base(DbExpressionType.LiteValue, type) { this.TypeId = typeId ?? throw new ArgumentNullException(nameof(typeId)); this.Id = id ?? throw new ArgumentNullException(nameof(id)); - this.ToStr = toStr; + + if (customModelExpression != null && models != null) + throw new InvalidOperationException($"{nameof(customModelExpression)} and {models} are incomatible"); + + this.CustomModelExpression = customModelExpression; + this.Models = models; } public override string ToString() { - return $"new Lite<{Type.CleanType().TypeName()}>({TypeId},{Id},{ToStr})"; + + var lastPart = CustomModelExpression != null ? ("custmoModelExpression: " + CustomModelExpression.ToString()) : + Models != null ? ("models: {\n" + Models.ToString(kvp => kvp.Key.TypeName() + ": " + kvp.Value.ToString(), "\n, ").Indent(4) + "\n}") : + null; + + return $"new Lite<{Type.CleanType().TypeName()}>({TypeId},{Id}, {lastPart})"; } protected override Expression Accept(DbExpressionVisitor visitor) diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs index 956140379b..ff2e94afdb 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionComparer.cs @@ -452,14 +452,17 @@ protected virtual bool CompareImplementedByAll(ImplementedByAllExpression a, Imp protected virtual bool CompareLiteReference(LiteReferenceExpression a, LiteReferenceExpression b) { - return Compare(a.Reference, b.Reference) && Compare(a.CustomToStr, b.CustomToStr); + return Compare(a.Reference, b.Reference) + && Compare(a.CustomModelExpression, b.CustomModelExpression) + && CompareDictionaries(a.CustomModelTypes, b.CustomModelTypes, (at, bt) => at == bt); } protected virtual bool CompareLiteValue(LiteValueExpression a, LiteValueExpression b) { return Compare(a.Id, b.Id) - && Compare(a.ToStr, b.ToStr) - && Compare(a.TypeId, b.TypeId); + && Compare(a.TypeId, b.TypeId) + && Compare(a.CustomModelExpression, b.CustomModelExpression) + && CompareDictionaries(a.Models, b.Models, (a1, b1) => a1.LazyModelType == b1.LazyModelType && Compare(a1.EagerExpression, b1.EagerExpression)); } protected virtual bool CompareTypeFieldInit(TypeEntityExpression a, TypeEntityExpression b) diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs index 88e2f971bd..5d50728654 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs @@ -172,7 +172,7 @@ protected internal override Expression VisitImplementedBy(ImplementedByExpressio private static Expression GetImplmentedById(ImplementedByExpression ib) { return ib.Implementations.IsEmpty() ? new SqlConstantExpression(null, typeof(int?)) : - ib.Implementations.Select(a => a.Value.ExternalId.Value).Aggregate((id1, id2) => Expression.Coalesce(id1, id2)); + ib.Implementations.Select(a => a.Value.ExternalId.Value).Aggregate((id1, id2) => Expression.Coalesce(id1, id2)); } protected internal override Expression VisitTypeImplementedBy(TypeImplementedByExpression typeIb) diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs index b586c5a12e..dfa95b7043 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs @@ -60,9 +60,9 @@ protected internal virtual ColumnAssignment VisitColumnAssigment(ColumnAssignmen protected internal virtual Expression VisitLiteReference(LiteReferenceExpression lite) { var newRef = Visit(lite.Reference); - var newToStr = Visit(lite.CustomToStr); - if (newRef != lite.Reference || newToStr != lite.CustomToStr) - return new LiteReferenceExpression(lite.Type, newRef, newToStr, lite.LazyToStr, lite.EagerEntity); + var newModelExpression = Visit(lite.CustomModelExpression); + if (newRef != lite.Reference || newModelExpression != lite.CustomModelExpression) + return new LiteReferenceExpression(lite.Type, newRef, newModelExpression, lite.CustomModelTypes, lite.LazyModel, lite.EagerEntity); return lite; } @@ -70,9 +70,22 @@ protected internal virtual Expression VisitLiteValue(LiteValueExpression lite) { var newTypeId = Visit(lite.TypeId); var newId = (PrimaryKeyExpression)Visit(lite.Id); - var newToStr = Visit(lite.ToStr); - if (newTypeId != lite.TypeId || newId != lite.Id || newToStr != lite.ToStr) - return new LiteValueExpression(lite.Type, newTypeId, newId, newToStr); + var customModelExpression = Visit(lite.CustomModelExpression); + var models = lite.Models == null ? null : Visit(lite.Models, eos => + { + if (eos.LazyModelType != null) + return eos; + + var newExpr = Visit(eos.EagerExpression); + + if (newExpr != eos.EagerExpression) + return new ExpressionOrType(newExpr!); + + return eos; + }); + + if (newTypeId != lite.TypeId || newId != lite.Id || customModelExpression != lite.CustomModelExpression || models != lite.Models) + return new LiteValueExpression(lite.Type, newTypeId, newId, customModelExpression, models); return lite; } @@ -89,14 +102,13 @@ protected internal virtual Expression VisitTypeEntity(TypeEntityExpression typeF [DebuggerStepThrough] protected static ReadOnlyDictionary Visit(ReadOnlyDictionary dictionary, Func newValue) where K : notnull - where V : class { Dictionary? alternate = null; foreach (var k in dictionary.Keys) { V item = dictionary[k]; V newItem = newValue(item); - if (alternate == null && item != newItem) + if (alternate == null && !Equals(item, newItem)) { alternate = new Dictionary(); foreach (var k2 in dictionary.Keys.TakeWhile(k2 => !k2.Equals(k))) diff --git a/Signum.Engine/Linq/ExpressionVisitor/EntityCompleter.cs b/Signum.Engine/Linq/ExpressionVisitor/EntityCompleter.cs index 00206c1f7d..e305043160 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/EntityCompleter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/EntityCompleter.cs @@ -33,59 +33,60 @@ protected internal override Expression VisitLiteReference(LiteReferenceExpressio var id = binder.GetId(lite.Reference); var typeId = binder.GetEntityType(lite.Reference); - var toStr = LiteToString(lite, typeId); - //var toStr2 = Visit(toStr); //AdditionalBinding in embedded requires it, but makes problems in many other lites in Nominator - return new LiteValueExpression(lite.Type, typeId, id, toStr); - } + if (lite.CustomModelExpression != null) + return new LiteValueExpression(lite.Type, typeId, id, lite.CustomModelExpression, null); - private Expression? LiteToString(LiteReferenceExpression lite, Expression typeId) - { - if (lite.CustomToStr != null) - return Visit(lite.CustomToStr); + var models = GetModels(lite, typeId); - if (lite.Reference is ImplementedByAllExpression) - return null; + //var model2 = Visit(model); //AdditionalBinding in embedded requires it, but makes problems in many other lites in Nominator - if (lite.LazyToStr) - return null; + return new LiteValueExpression(lite.Type, typeId, id, null, models.ToReadOnly()); + } - if (IsCacheable(typeId)) - return null; + private Dictionary GetModels(LiteReferenceExpression lite, Expression typeId) + { + if (lite.Reference is ImplementedByAllExpression iba) + { + if (lite.CustomModelTypes != null) + return lite.CustomModelTypes.ToDictionary(a => a.Key, a => new ExpressionOrType(a.Value)); + + return new Dictionary(); + } if (lite.Reference is EntityExpression entityExp) { - if (entityExp.AvoidExpandOnRetrieving) - return null; + var modelType = lite.CustomModelTypes?.TryGetC(entityExp.Type) ?? Lite.DefaultModelType(entityExp.Type); - return binder.BindMethodCall(Expression.Call(entityExp, EntityExpression.ToStringMethod)); + return new Dictionary + { + { entityExp.Type, lite.LazyModel || entityExp.AvoidExpandOnRetrieving ? new ExpressionOrType(modelType) : new ExpressionOrType(GetModel(entityExp, modelType)) } + }; } if (lite.Reference is ImplementedByExpression ibe) { - if (ibe.Implementations.Any(imp => imp.Value.AvoidExpandOnRetrieving)) - return null; - - return ibe.Implementations.Values.Select(ee => - new When(SmartEqualizer.NotEqualNullable(ee.ExternalId, QueryBinder.NullId(ee.ExternalId.ValueType)), - binder.BindMethodCall(Expression.Call(ee, EntityExpression.ToStringMethod))) - ).ToCondition(typeof(string)); + return ibe.Implementations.Values.ToDictionary(imp => imp.Type, + imp => + { + var modelType = lite.CustomModelTypes?.TryGetC(imp.Type) ?? Lite.DefaultModelType(imp.Type); + return lite.LazyModel || imp.AvoidExpandOnRetrieving ? new ExpressionOrType(modelType) : new ExpressionOrType(GetModel(imp, modelType)); + }); } - return binder.BindMethodCall(Expression.Call(lite.Reference, EntityExpression.ToStringMethod)); + return new Dictionary(); //Could be more accurate to preserve model in liteA ?? liteB or condition ? liteA : liteB } - private static bool IsCacheable(Expression newTypeId) + private Expression GetModel(EntityExpression entityExp, Type modelType) { + //if (modelType == typeof(string)) + // return binder.BindMethodCall(Expression.Call(entityExp, EntityExpression.ToStringMethod)); - if (newTypeId is TypeEntityExpression tfie) - return IsCached(tfie.TypeValue); - + var mce = Lite.GetModelConstructorExpression(entityExp.Type, modelType); - if (newTypeId is TypeImplementedByExpression tibe) - return tibe.TypeImplementations.All(t => IsCached(t.Key)); + var bound = binder.Visit(Expression.Invoke(mce, entityExp)); - return false; + return Visit(bound); } protected internal override Expression VisitEntity(EntityExpression ee) diff --git a/Signum.Engine/Linq/ExpressionVisitor/GroupEntityCleaner.cs b/Signum.Engine/Linq/ExpressionVisitor/GroupEntityCleaner.cs index 64f4f128f3..68b109b673 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/GroupEntityCleaner.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/GroupEntityCleaner.cs @@ -45,7 +45,7 @@ protected override Expression VisitBinary(BinaryExpression node) { if (node.Left.Type.IsLite() || node.Right.Type.IsLite()) return this.Visit(new LiteReferenceExpression(node.Type, - Expression.Coalesce(GetLiteEntity(node.Left), GetLiteEntity(node.Right)), null, false, false)); + Expression.Coalesce(GetLiteEntity(node.Left), GetLiteEntity(node.Right)), null, null, false, false)); if (typeof(IEntity).IsAssignableFrom(node.Left.Type) || typeof(IEntity).IsAssignableFrom(node.Right.Type)) { @@ -62,7 +62,7 @@ protected override Expression VisitConditional(ConditionalExpression node) { if (node.IfTrue.Type.IsLite() || node.IfTrue.Type.IsLite()) return this.Visit(new LiteReferenceExpression(node.Type, - Expression.Condition(node.Test, GetLiteEntity(node.IfTrue), GetLiteEntity(node.IfFalse)), null, false, false)); + Expression.Condition(node.Test, GetLiteEntity(node.IfTrue), GetLiteEntity(node.IfFalse)), null, null, false, false)); if (typeof(IEntity).IsAssignableFrom(node.IfTrue.Type) || typeof(IEntity).IsAssignableFrom(node.IfFalse.Type)) { diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs index 3792a5fb17..4c45e8abf6 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs @@ -172,13 +172,18 @@ protected override Expression VisitMethodCall(MethodCallExpression m) return new ImplementedByExpression(ib.Type, strategy, ib.Implementations); } - else if (m.Method.DeclaringType == typeof(Lite) && m.Method.Name == "ToLite") + else if (m.Method.DeclaringType == typeof(Lite) && (m.Method.Name == "ToLite" || m.Method.Name == "ToLiteFat")) { - Expression? toStr = Visit(m.TryGetArgument("toStr")); //could be null - var entity = Visit(m.GetArgument("entity")); var converted = EntityCasting(entity, Lite.Extract(m.Type)!)!; - return MakeLite(converted, toStr); + + + Expression? model = Visit(m.TryGetArgument("model")); //could be null + Expression? modelType = Visit(m.TryGetArgument("modelType")); //could be null + + return new LiteReferenceExpression(Lite.Generate(entity.Type), entity, model, + modelType == null ? null : ToTypeDictionary(modelType, entity.Type), + false, eagerEntity: m.Method.Name == "ToLiteFat"); } else if (m.Method.DeclaringType!.IsInstantiationOf(typeof(EnumEntity<>)) && m.Method.Name == "ToEnum") { @@ -231,7 +236,24 @@ protected override Expression VisitMethodCall(MethodCallExpression m) return BindMethodCall(result); } + private ReadOnlyDictionary? ToTypeDictionary(Expression? modelType, Type entityType) + { + if (modelType == null) + return null; + if (modelType is ConstantExpression ce) + { + if (ce.IsNull()) + return null; + + if(ce.Value is Type t) + { + return new Dictionary { { entityType, t } }.ToReadOnly(); + } + } + + throw new NotImplementedException("Not implemented " + modelType.ToString()); + } private Expression BindExpandEntity(Expression source, LambdaExpression entitySelector, ExpandEntity expandEntity) { @@ -1297,7 +1319,8 @@ Expression GetExpressionOrder(EntityExpression exp) Expression[] results = expr switch { - LiteReferenceExpression lite => lite.Reference is ImplementedByAllExpression iba ? iba.Ids.Values.PreAnd(iba.TypeId).ToArray() : + LiteReferenceExpression lite => + lite.Reference is ImplementedByAllExpression iba ? iba.Ids.Values.PreAnd(iba.TypeId).ToArray() : lite.Reference is EntityExpression e ? new[] { GetExpressionOrder(e), e.ExternalId } : lite.Reference is ImplementedByExpression ib ? ib.Implementations.Values.SelectMany(e => new[] { GetExpressionOrder(e), e.ExternalId }).ToArray() : throw new NotImplementedException(""), @@ -1584,7 +1607,7 @@ public Expression BindMethodCall(MethodCallExpression m) } else if (source is LiteReferenceExpression lite) { - var toStr = lite.CustomToStr ?? BindMethodCall(Expression.Call(lite.Reference, EntityExpression.ToStringMethod)); + var toStr = BindMethodCall(Expression.Call(lite.Reference, EntityExpression.ToStringMethod)); return toStr; } @@ -2393,7 +2416,7 @@ public Expression SimplifyRedundandConverts(UnaryExpression unary) Expression entity = EntityCasting(lite.Reference, Lite.Extract(uType)!)!; - return MakeLite(entity, lite.CustomToStr); + return new LiteReferenceExpression(Lite.Generate(entity.Type), entity, lite.CustomModelExpression, lite.CustomModelTypes, false, false); } return null; @@ -2916,9 +2939,9 @@ internal static PrimaryKeyExpression NullId(Type type) return new PrimaryKeyExpression(new SqlConstantExpression(null, type.Nullify())); } - public static Expression MakeLite(Expression entity, Expression? customToStr) + public static Expression MakeLite(Expression entity, Dictionary? customModelTypes = null) { - return new LiteReferenceExpression(Lite.Generate(entity.Type), entity, customToStr, false, false); + return new LiteReferenceExpression(Lite.Generate(entity.Type), entity, null, customModelTypes?.ToReadOnly(), false, false); } public PrimaryKeyExpression GetId(Expression expression) @@ -3576,7 +3599,7 @@ protected override Expression VisitMethodCall(MethodCallExpression node) { var type = node.Method.GetGenericArguments()[0]; var id = ToPrimaryKey(node.Arguments[0]); - return new LiteReferenceExpression(Lite.Generate(type), new EntityExpression(type, id, null, null, null, null, null, false), null, false, false); + return new LiteReferenceExpression(Lite.Generate(type), new EntityExpression(type, id, null, null, null, null, null, false), null, null, false, false); } if (node.Method.IsInstantiationOf(miSetId)) @@ -3612,7 +3635,7 @@ protected override Expression VisitConditional(ConditionalExpression c) using (this.OverrideColExpression(col.Reference)) { var entity = CombineConditional(test, l.Reference, r.Reference)!; - return new LiteReferenceExpression(Lite.Generate(entity.Type), entity, null, false, false); + return new LiteReferenceExpression(Lite.Generate(entity.Type), entity, null, null, false, false); } }); } @@ -3700,7 +3723,7 @@ protected override Expression VisitBinary(BinaryExpression b) using (this.OverrideColExpression(col.Reference)) { var entity = CombineCoalesce(l.Reference, r.Reference)!; - return new LiteReferenceExpression(Lite.Generate(entity!.Type), entity, null, false, false); + return new LiteReferenceExpression(Lite.Generate(entity!.Type), entity, null, null, false, false); } }); } @@ -3849,7 +3872,7 @@ protected internal override Expression VisitLiteReference(LiteReferenceExpressio var newRef = this.OverrideColExpression(reference).Using(_ => Visit(lite.Reference)); if (newRef != lite.Reference) - return new LiteReferenceExpression(Lite.Generate(newRef.Type), newRef, null, false, false); + return new LiteReferenceExpression(Lite.Generate(newRef.Type), newRef, null, null, false, false); return lite; } @@ -3941,7 +3964,7 @@ colExpression is ImplementedByExpression || lite == null ? Expression.Constant(null, type) : Expression.Constant(lite.Id.Object, type), lite?.EntityType); - return new LiteReferenceExpression(colLite.Type, entity, null, false, false); + return new LiteReferenceExpression(colLite.Type, entity, null, null, false, false); } } diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs index 80306cff82..9b6f370d08 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryFormatter.cs @@ -1023,7 +1023,7 @@ protected internal override Expression VisitLiteValue(LiteValueExpression lite) protected internal override Expression VisitLiteReference(LiteReferenceExpression lite) { - return base.VisitLiteReference(lite); + throw InvalidSqlExpression(lite); } protected override Expression VisitInvocation(InvocationExpression iv) diff --git a/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs b/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs index 1c8213a6e0..0c2ff15eaf 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs @@ -857,7 +857,7 @@ static Expression NotEqualToNull(PrimaryKeyExpression exp) EntityExpression ere = new EntityExpression(lite.EntityType, new PrimaryKeyExpression(id), null, null, null, null, null, false); - return new LiteReferenceExpression(Lite.Generate(lite.EntityType), ere, null, false, false); + return new LiteReferenceExpression(Lite.Generate(lite.EntityType), ere, null, null, false, false); } return null; diff --git a/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs b/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs index cae0e12400..90acaacfe8 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs @@ -3,6 +3,8 @@ using Signum.Entities.Basics; using Signum.Entities.Internal; using Signum.Utilities.DataStructures; +using Signum.Engine.Basics; +using System.Collections.ObjectModel; namespace Signum.Engine.Linq; @@ -407,15 +409,37 @@ private MethodCallExpression SchemaGetType(TypeImplementedByAllExpression typeIb return Expression.Call(Expression.Constant(Schema.Current), miGetType, Visit(typeIba.TypeColumn).UnNullify()); } + //EagerEntity protected internal override Expression VisitLiteReference(LiteReferenceExpression lite) { var reference = Visit(lite.Reference); - var toStr = Visit(lite.CustomToStr); + var model = Visit(lite.CustomModelExpression); - return Lite.ToLiteFatInternalExpression(reference, toStr ?? Expression.Constant(null, typeof(string))); + return Expression.Call(miToLiteFatInternal.MakeGenericMethod(reference.Type), + reference, + model ?? Expression.Constant(null, typeof(object)), + Expression.Constant(lite.CustomModelTypes, typeof(ReadOnlyDictionary))); } + static MethodInfo miToLiteFatInternal = ReflectionTools.GetMethodInfo(() => ToLiteFatInternal(null, null, null!)).GetGenericMethodDefinition(); + static Lite? ToLiteFatInternal(T? entity, object? model, IReadOnlyDictionary? modelTypeDictionary) + where T : class, IEntity + { + if (entity == null) + return null; + + + if (model != null) + return entity.ToLiteFat(model); + + + var modelType = modelTypeDictionary?.TryGetC(entity.GetType()) ?? Lite.DefaultModelType(entity.GetType()); + + return entity.ToLiteFat(modelType); + } + + protected internal override Expression VisitLiteValue(LiteValueExpression lite) { var id = Visit(NullifyColumn(lite.Id)); @@ -423,63 +447,118 @@ protected internal override Expression VisitLiteValue(LiteValueExpression lite) if (id == null) return Expression.Constant(null, lite.Type); - var toStr = Visit(lite.ToStr); + var customModel = Visit(lite.CustomModelExpression); var typeId = lite.TypeId; - var toStringOrNull = toStr ?? Expression.Constant(null, typeof(string)); + Expression GetEagerModel(Type entityType, out bool requiresRequest) + { + requiresRequest = false; + if (customModel != null) + return customModel; + + var eot = lite.Models!.GetOrThrow(entityType); + + if (eot.EagerExpression != null) + return Visit(eot.EagerExpression); + requiresRequest = true; + + return Expression.Constant(null, eot.LazyModelType!); + } Expression nothing = Expression.Constant(null, lite.Type); - Expression liteConstructor; if (typeId is TypeEntityExpression tee) { Type type = tee.TypeValue; - liteConstructor = Expression.Condition(Expression.NotEqual(id, NullId), - Expression.Convert(Lite.NewExpression(type, id, toStringOrNull), lite.Type), + var model = GetEagerModel(type, out var requiresRequest); + + var liteConstructor = Expression.Convert(Lite.NewExpression(type, id, model), lite.Type); + + return Expression.Condition(Expression.NotEqual(id, NullId), + requiresRequest ? RequestLite(liteConstructor) : PostRetrieving(liteConstructor), nothing); } else if (typeId is TypeImplementedByExpression tib) { - liteConstructor = tib.TypeImplementations.Aggregate(nothing, + var result = tib.TypeImplementations.Aggregate(nothing, (acum, ti) => - { - var visitId = Visit(NullifyColumn(ti.Value)); - return Expression.Condition(Expression.NotEqual(visitId, NullId), - Expression.Convert(Lite.NewExpression(ti.Key, visitId, toStringOrNull), lite.Type), acum); - }); + { + var visitId = Visit(NullifyColumn(ti.Value)); + var model = GetEagerModel(ti.Key, out var requiresRequest); + + var liteConstructor = Lite.NewExpression(ti.Key, visitId, model); + + return Expression.Condition(Expression.NotEqual(visitId, NullId), + Expression.Convert(requiresRequest ? RequestLite(liteConstructor) : PostRetrieving(liteConstructor), lite.Type), + acum); + }); + + return result; } else if (typeId is TypeImplementedByAllExpression tiba) { + var uid = id.UnNullify(); + var tid = Visit(NullifyColumn(tiba.TypeColumn)); - liteConstructor = Expression.Convert(Expression.Call(miTryLiteCreate, Expression.Constant(Schema.Current), tid, id.Nullify(), toStringOrNull), lite.Type); + var typeFromId = Expression.Call(Expression.Constant(Schema.Current), miGetTypeFromId, tid.UnNullify()); + if (customModel != null) + return Expression.Condition(Expression.Equal(tid, NullId), nothing, + PostRetrieving(Expression.Convert( + Expression.Call(miLiteCreateModel, typeFromId, uid, customModel), + lite.Type))); + + var baseCase = Expression.Condition(Expression.Equal(tid, NullId), nothing, + RequestLite( + Expression.Convert( + Expression.Call(miLiteCreateModelType, typeFromId, uid, Expression.Call(miGetDefaultModelType, typeFromId)), + lite.Type))); + + var result = lite.Models!.Aggregate((Expression)baseCase, + (acum, kvp) => + { + var model = GetEagerModel(kvp.Key, out var requiresRequest); + + var liteExpression = requiresRequest ? + RequestLite(Expression.Call(miLiteCreateModelType, typeFromId, uid, Expression.Constant(model.Type))) : + PostRetrieving(Expression.Call(miLiteCreateModel, typeFromId, uid, model)); + + return Expression.Condition(Expression.Equal(tid, Expression.Constant(TypeLogic.TypeToId.GetOrThrow(kvp.Key))), + Expression.Convert(liteExpression, lite.Type), + acum); + }); + + return result; } else { - liteConstructor = Expression.Condition(Expression.NotEqual(id.Nullify(), NullId), - Expression.Convert(Expression.Call(miLiteCreate, Visit(typeId), id.UnNullify(), toStringOrNull), lite.Type), - nothing); - } + var type = Visit(typeId); - if (toStr != null) - return Expression.Call(retriever, miModifiablePostRetrieving.MakeGenericMethod(typeof(LiteImp)), liteConstructor.TryConvert(typeof(LiteImp))).TryConvert(liteConstructor.Type); - else - return Expression.Call(retriever, miRequestLite.MakeGenericMethod(Lite.Extract(lite.Type)!), liteConstructor); + var constructor = customModel != null ? + PostRetrieving(Expression.Call(miLiteCreateModel, type, id.UnNullify(), customModel)) : + RequestLite(Expression.Call(miLiteCreateModelType, type, id.UnNullify(), Expression.Call(miGetDefaultModelType, type))); //Maybe could be optimized + + return Expression.Condition(Expression.NotEqual(id.Nullify(), NullId), + Expression.Convert(constructor, lite.Type), + nothing); + } } - static readonly MethodInfo miTryLiteCreate = ReflectionTools.GetMethodInfo(() => TryLiteCreate(null!, null, null!, null!)); + static MethodInfo miGetDefaultModelType = ReflectionTools.GetMethodInfo(() => Lite.DefaultModelType(null!)); - static Lite? TryLiteCreate(Schema schema, PrimaryKey? typeId, PrimaryKey? id, string toString) + private static MethodCallExpression RequestLite(Expression liteConstructor) { - if (typeId == null) - return null; - - Type type = schema.GetType(typeId.Value); + return Expression.Call(retriever, miRequestLite.MakeGenericMethod(Lite.Extract(liteConstructor.Type)!), liteConstructor); + } - return Lite.Create(type, id!.Value, toString); + private static Expression PostRetrieving(Expression liteConstructor) + { + return Expression.Call(retriever, miModifiablePostRetrieving.MakeGenericMethod(typeof(LiteImp)), liteConstructor.TryConvert(typeof(LiteImp))).TryConvert(liteConstructor.Type); } - static MethodInfo miLiteCreate = ReflectionTools.GetMethodInfo(() => Lite.Create(null!, 0, null)); + static readonly MethodInfo miGetTypeFromId = ReflectionTools.GetMethodInfo((Schema s) => s.GetType(1)); + static MethodInfo miLiteCreateModel = ReflectionTools.GetMethodInfo(() => Lite.Create(null!, 0, (object)null!)); + static MethodInfo miLiteCreateModelType = ReflectionTools.GetMethodInfo(() => Lite.Create(null!, 0, (Type)null!)); protected internal override Expression VisitMListElement(MListElementExpression mle) { diff --git a/Signum.Engine/LinqExpandHints.cs b/Signum.Engine/LinqExpandHints.cs index 49d7875464..b58f5b49c0 100644 --- a/Signum.Engine/LinqExpandHints.cs +++ b/Signum.Engine/LinqExpandHints.cs @@ -26,9 +26,9 @@ public enum ExpandLite { EntityEager, //Default, - ToStringEager, - ToStringLazy, - ToStringNull, + ModelEager, + ModelLazy, + ModelNull, } public enum ExpandEntity diff --git a/Signum.Engine/Patterns/VirtualMList.cs b/Signum.Engine/Patterns/VirtualMList.cs index 872b862acf..ecd53ffabb 100644 --- a/Signum.Engine/Patterns/VirtualMList.cs +++ b/Signum.Engine/Patterns/VirtualMList.cs @@ -134,10 +134,10 @@ public static FluentInclude WithVirtualMList(this FluentInclude fi, { sb.Schema.EntityEvents().RegisterBinding>(mListField, shouldSet: () => !defLazyRetrieve && !VirtualMList.ShouldAvoidMListType(typeof(L)), - valueExpression: (e, rowId) => Database.Query().DisableQueryFilter().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMListWithOrder(), + valueExpression: (e, rowId) => Database.Query().DisableQueryFilter().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ModelLazy).ToVirtualMListWithOrder(), valueFunction: (e, rowId, retriever) => Schema.Current.CacheController()!.Enabled ? Schema.Current.CacheController()!.RequestByBackReference(retriever, backReference, e.ToLite()).ToVirtualMListWithOrder(): - Database.Query().DisableQueryFilter().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMListWithOrder() + Database.Query().DisableQueryFilter().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ModelLazy).ToVirtualMListWithOrder() ); } @@ -145,10 +145,10 @@ public static FluentInclude WithVirtualMList(this FluentInclude fi, { sb.Schema.EntityEvents().RegisterBinding(mListField, shouldSet: () => !defLazyRetrieve && !VirtualMList.ShouldAvoidMListType(typeof(L)), - valueExpression: (e, rowId) => Database.Query().DisableQueryFilter().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMList(), + valueExpression: (e, rowId) => Database.Query().DisableQueryFilter().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ModelLazy).ToVirtualMList(), valueFunction: (e, rowId, retriever) => Schema.Current.CacheController()!.Enabled ? Schema.Current.CacheController()!.RequestByBackReference(retriever, backReference, e.ToLite()).ToVirtualMList() : - Database.Query().DisableQueryFilter().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMList() + Database.Query().DisableQueryFilter().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ModelLazy).ToVirtualMList() ); } @@ -282,7 +282,7 @@ public static FluentInclude WithVirtualMListInitializeOnly(this FluentI sb.Schema.EntityEvents().RegisterBinding(mListField, shouldSet: () => false, - valueExpression: (e, rowId) => Database.Query().DisableQueryFilter().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ToStringLazy).ToVirtualMListWithOrder() + valueExpression: (e, rowId) => Database.Query().DisableQueryFilter().Where(line => backReference.Evaluate(line).Is(e.ToLite())).ExpandLite(line => backReference.Evaluate(line), ExpandLite.ModelLazy).ToVirtualMListWithOrder() ); sb.Schema.EntityEvents().Saving += (T e) => diff --git a/Signum.Engine/Retriever.cs b/Signum.Engine/Retriever.cs index ff7c61a919..6b3f6363ae 100644 --- a/Signum.Engine/Retriever.cs +++ b/Signum.Engine/Retriever.cs @@ -40,7 +40,7 @@ public RealRetriever(EntityCache.RealEntityCache entityCache) EntityCache.RealEntityCache entityCache; Dictionary<(Type type, PrimaryKey id), Entity> retrieved = new Dictionary<(Type type, PrimaryKey id), Entity>(); Dictionary>? requests; - Dictionary<(Type type, PrimaryKey id), List>>? liteRequests; + Dictionary<(Type type, Type modelType, PrimaryKey id), List>>? liteRequests; List modifiablePostRetrieving = new List(); bool TryGetRequest((Type type, PrimaryKey id) key, [NotNullWhen(true)]out Entity? value) @@ -139,13 +139,13 @@ bool TryGetRequest((Type type, PrimaryKey id) key, [NotNullWhen(true)]out Entity ICacheController? cc = Schema.Current.CacheController(lite.EntityType); if (cc != null && cc.Enabled) { - lite.SetToString(cc.TryGetToString(lite.Id) ?? ("[" + EngineMessage.EntityWithType0AndId1NotFound.NiceToString().FormatWith(lite.EntityType.NiceName(), lite.Id) + "]")); + lite.SetModel(cc.TryGetLiteModel(lite.Id, lite.ModelType, this) ?? Lite.GetNotFoundModel(lite)); return lite; } - var tuple = (type: lite.EntityType, id: lite.Id); + var tuple = (type: lite.EntityType, modelType: lite.ModelType, id: lite.Id); if (liteRequests == null) - liteRequests = new Dictionary<(Type type, PrimaryKey id), List>>(); + liteRequests = new Dictionary<(Type type, Type modelType, PrimaryKey id), List>>(); liteRequests.GetOrCreate(tuple).Add(lite); return lite; } @@ -214,21 +214,20 @@ public async Task CompleteAllPrivate(CancellationToken? token) if (liteRequests != null) { - { - List<(Type type, PrimaryKey id)>? toRemove = null; + List<(Type type, Type modelType, PrimaryKey id)>? toRemove = null; foreach (var item in liteRequests) { - var entity = retrieved.TryGetC(item.Key); + var entity = retrieved.TryGetC((item.Key.type, item.Key.id)); if (entity != null) { - var toStr = entity.ToString(); + var toStr = Lite.GetModel(entity, item.Key.modelType); foreach (var lite in item.Value) - lite.SetToString(toStr); + lite.SetModel(toStr); if (toRemove == null) - toRemove = new List<(Type type, PrimaryKey id)>(); + toRemove = new(); toRemove.Add(item.Key); } @@ -240,16 +239,16 @@ public async Task CompleteAllPrivate(CancellationToken? token) while (liteRequests.Count > 0) { - var group = liteRequests.GroupBy(a => a.Key.type).FirstEx(); + var group = liteRequests.GroupBy(a => (a.Key.type, a.Key.modelType)).FirstEx(); - var dic = await giGetStrings.GetInvoker(group.Key)(group.Select(a => a.Key.id).ToList(), token); + var dic = await giLiteModels.GetInvoker(group.Key.type, group.Key.modelType)(group.Select(a => a.Key.id).ToList(), this, token); foreach (var item in group) { - var toStr = dic.TryGetC(item.Key.id) ?? ("[" + EngineMessage.EntityWithType0AndId1NotFound.NiceToString().FormatWith(item.Key.type.NiceName(), item.Key.id) + "]"); + var model = dic.TryGetCN(item.Key.id) ?? Lite.GetNotFoundModel(item.Value.FirstEx()); foreach (var lite in item.Value) { - lite.SetToString(toStr); + lite.SetModel(model); } } @@ -293,20 +292,22 @@ public async Task CompleteAllPrivate(CancellationToken? token) } - static readonly GenericInvoker, CancellationToken?, Task>>> giGetStrings = - new((ids, token) => GetStrings(ids, token)); - static async Task> GetStrings(List ids, CancellationToken? token) where T : Entity + static readonly GenericInvoker, IRetriever, CancellationToken?, Task>>> giLiteModels = + new((ids, retriever, token) => GetLiteModels(ids, retriever, token)); + static async Task> GetLiteModels(List ids, IRetriever retriever, CancellationToken? token) where T : Entity { ICacheController? cc = Schema.Current.CacheController(typeof(T)); if (cc != null && cc.Enabled) { cc.Load(); - return ids.ToDictionary(a => a, a => cc.TryGetToString(a)!); + return ids.ToDictionary(a => a, a => cc.TryGetLiteModel(a, typeof(M), retriever)); } - else if (token != null) + + var modelExpression = Lite.GetModelConstructorExpression(); + if (token != null) { var tasks = ids.Chunk(Schema.Current.Settings.MaxNumberOfParameters) - .Select(gr => Database.Query().Where(e => gr.Contains(e.Id)).Select(a => KeyValuePair.Create(a.Id, a.ToString())).ToListAsync(token!.Value)) + .Select(gr => Database.Query().Where(e => gr.Contains(e.Id)).Select(a => KeyValuePair.Create(a.Id, (object?)modelExpression.Evaluate(a))).ToListAsync(token!.Value)) .ToList(); var list = await Task.WhenAll(tasks); @@ -317,7 +318,7 @@ static async Task> GetStrings(List else { var dic = ids.Chunk(Schema.Current.Settings.MaxNumberOfParameters) - .SelectMany(gr => Database.Query().Where(e => gr.Contains(e.Id)).Select(a => KeyValuePair.Create(a.Id, a.ToString()))) + .SelectMany(gr => Database.Query().Where(e => gr.Contains(e.Id)).Select(a => KeyValuePair.Create(a.Id, (object?)modelExpression.Evaluate(a)))) .ToDictionaryEx(); return dic; diff --git a/Signum.Engine/Schema/Schema.Basics.cs b/Signum.Engine/Schema/Schema.Basics.cs index 34d811bdd4..fb1d2be9cb 100644 --- a/Signum.Engine/Schema/Schema.Basics.cs +++ b/Signum.Engine/Schema/Schema.Basics.cs @@ -306,7 +306,7 @@ public List GeneratAllIndexes() if (ui == null || ui.AvoidAttachToUniqueIndexes) return ix; - return new UniqueTableIndex(ui.Table, ui.Columns.Concat(attachedFields).ToArray()) + return new UniqueTableIndex(ui.Table, ui.Columns.Union(attachedFields).ToArray()) { Where = ui.Where }; @@ -917,6 +917,8 @@ public partial class FieldReference : Field, IColumn, IFieldReference public bool AvoidForeignKey { get; set; } public bool IsLite { get; internal set; } + public Type? CustomLiteModelType { get; internal set; } + public bool AvoidExpandOnRetrieving { get; set; } public string? Default { get; set; } @@ -974,6 +976,7 @@ public bool ClearEntityOnSaving } } + internal override IEnumerable TablesMList() { return Enumerable.Empty(); @@ -1093,9 +1096,12 @@ public partial class FieldImplementedByAll : Field, IFieldReference public bool AvoidExpandOnRetrieving { get; internal set; } - public Dictionary IdColumns { get; set; } + public Dictionary IdColumns { get; set; } + public ImplementationColumn TypeColumn { get; set; } + public Dictionary? CustomLiteModelTypes; + public FieldImplementedByAll(PropertyRoute route, IEnumerable columnIds, ImplementationColumn columnType) : base(route) { this.IdColumns = columnIds.ToDictionaryEx(a => a.Type.UnNullify()); @@ -1170,6 +1176,7 @@ public partial class ImplementationColumn : IColumn public Type Type => this.Nullable.ToBool() ? ReferenceTable.PrimaryKey.Type.Nullify() : ReferenceTable.PrimaryKey.Type; public bool AvoidForeignKey { get; set; } public string? Default { get; set; } + public Type? CustomLiteModelType { get; internal set; } public ImplementationColumn(string name, Table referenceTable) { diff --git a/Signum.Engine/Schema/Schema.Expressions.cs b/Signum.Engine/Schema/Schema.Expressions.cs index 13d9e34a27..c835132f98 100644 --- a/Signum.Engine/Schema/Schema.Expressions.cs +++ b/Signum.Engine/Schema/Schema.Expressions.cs @@ -226,8 +226,12 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, var result = new EntityExpression(cleanType, new PrimaryKeyExpression(new ColumnExpression(this.Type.Nullify(), tableAlias, Name)), period, null, null, null, null, AvoidExpandOnRetrieving); - if(this.IsLite) - return QueryBinder.MakeLite(result, null); + if (this.IsLite) { + + var customModelTypes = this.CustomLiteModelType == null ? null : new Dictionary { { cleanType, this.CustomLiteModelType } }; + + return QueryBinder.MakeLite(result, customModelTypes); + } else return result; } @@ -293,7 +297,12 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, var result = new ImplementedByExpression(IsLite ? Lite.Extract(FieldType)! : FieldType, SplitStrategy, implementations); if (this.IsLite) - return QueryBinder.MakeLite(result, null); + { + var customModelTypes = ImplementationColumns.Any(ic => ic.Value.CustomLiteModelType != null) ? + ImplementationColumns.Where(a => a.Value.CustomLiteModelType != null).ToDictionaryEx(a => a.Key, a => a.Value.CustomLiteModelType!) : null; + + return QueryBinder.MakeLite(result, customModelTypes); + } else return result; } @@ -310,7 +319,7 @@ internal override Expression GetExpression(Alias tableAlias, QueryBinder binder, period); if (this.IsLite) - return QueryBinder.MakeLite(result, null); + return QueryBinder.MakeLite(result, CustomLiteModelTypes); else return result; } diff --git a/Signum.Engine/Schema/Schema.cs b/Signum.Engine/Schema/Schema.cs index e29e8c75bc..2749735fbc 100644 --- a/Signum.Engine/Schema/Schema.cs +++ b/Signum.Engine/Schema/Schema.cs @@ -852,8 +852,8 @@ public interface ICacheController void Complete(Entity entity, IRetriever retriver); - string GetToString(PrimaryKey id); - string? TryGetToString(PrimaryKey? id); + object GetLiteModel(PrimaryKey id, Type modelType, IRetriever retriver); + object? TryGetLiteModel(PrimaryKey? id, Type modelType, IRetriever retriver); } public class InvalidateEventArgs : EventArgs { } @@ -876,9 +876,9 @@ void ICacheController.Complete(Entity entity, IRetriever retriver) public abstract bool Exists(PrimaryKey id); - public abstract string GetToString(PrimaryKey id); + public abstract object GetLiteModel(PrimaryKey id, Type modelType, IRetriever retriver); - public abstract string? TryGetToString(PrimaryKey? id); + public abstract object? TryGetLiteModel(PrimaryKey? id, Type modelType, IRetriever retriver); public abstract List RequestByBackReference(IRetriever retriever, Expression?>> backReference, Lite lite) where R : Entity; diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs index 1cf4fac989..1e0a7ba768 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs @@ -647,14 +647,20 @@ protected virtual FieldEnum GenerateFieldEnum(ITable table, PropertyRoute route, protected virtual FieldReference GenerateFieldReference(ITable table, PropertyRoute route, NameSequence name, bool forceNull) { - var referenceTable = Include(Lite.Extract(route.Type) ?? route.Type, route); + var entityType = Lite.Extract(route.Type) ?? route.Type; + + + var referenceTable = Include(entityType, route); var nullable = Settings.GetIsNullable(route, forceNull); + var isLite = route.Type.IsLite(); + return new FieldReference(route, null, name.ToString(), referenceTable) { Nullable = nullable, - IsLite = route.Type.IsLite(), + IsLite = isLite, + CustomLiteModelType = !isLite ? null : Settings.FieldAttributes(route)?.OfType().SingleOrDefaultEx()?.LiteModelType, AvoidForeignKey = Settings.FieldAttribute(route) != null, AvoidExpandOnRetrieving = Settings.FieldAttribute(route) != null, Default = Settings.FieldAttribute(route)?.GetDefault(Settings.IsPostgres) @@ -677,6 +683,8 @@ protected virtual FieldImplementedBy GenerateFieldImplementedBy(ITable table, Pr bool avoidForeignKey = Settings.FieldAttribute(route) != null; + var isLite = route.Type.IsLite(); + var implementations = types.ToDictionary(t => t, t => { var rt = Include(t, route); @@ -684,6 +692,7 @@ protected virtual FieldImplementedBy GenerateFieldImplementedBy(ITable table, Pr string impName = name.Add(TypeLogic.GetCleanName(t)).ToString(); return new ImplementationColumn(impName, referenceTable: rt) { + CustomLiteModelType = !isLite ? null : Settings.FieldAttributes(route)?.OfType().SingleOrDefaultEx(a => a.ForEntityType == t)?.LiteModelType, Nullable = nullable, AvoidForeignKey = avoidForeignKey, }; @@ -691,8 +700,8 @@ protected virtual FieldImplementedBy GenerateFieldImplementedBy(ITable table, Pr return new FieldImplementedBy(route, implementations) { + IsLite = isLite, SplitStrategy = strategy, - IsLite = route.Type.IsLite(), AvoidExpandOnRetrieving = Settings.FieldAttribute(route) != null }.Do(f => f.UniqueIndex = f.GenerateUniqueIndex(table, Settings.FieldAttribute(route))); } diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs index 6480730ce1..4268616027 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs @@ -23,7 +23,7 @@ public SchemaSettings() public int MaxNumberOfParameters = 2000; public int MaxNumberOfStatementsInSaveQueries = 16; - public List ImplementedByAllPrimaryKeyTypes = new List { typeof(int) }; + public HashSet ImplementedByAllPrimaryKeyTypes = new HashSet { typeof(int) }; public int ImplementedByAllStringSize = 40; public ConcurrentDictionary FieldAttributesCache = new ConcurrentDictionary(); diff --git a/Signum.Entities.Extensions/Authorization/PermissionSymbol.cs b/Signum.Entities.Extensions/Authorization/PermissionSymbol.cs index 58e19472c6..765009f495 100644 --- a/Signum.Entities.Extensions/Authorization/PermissionSymbol.cs +++ b/Signum.Entities.Extensions/Authorization/PermissionSymbol.cs @@ -1,6 +1,7 @@ namespace Signum.Entities.Authorization; +[EntityKind(EntityKind.SystemString, EntityData.Master, IsLowPopulation = true)] public class PermissionSymbol : Symbol { private PermissionSymbol() { } diff --git a/Signum.Entities.Extensions/Basics/TypeConditionSymbol.cs b/Signum.Entities.Extensions/Basics/TypeConditionSymbol.cs index 7244d6fef8..35ac8aa812 100644 --- a/Signum.Entities.Extensions/Basics/TypeConditionSymbol.cs +++ b/Signum.Entities.Extensions/Basics/TypeConditionSymbol.cs @@ -1,6 +1,7 @@ - + namespace Signum.Entities.Basics; +[EntityKind(EntityKind.SystemString, EntityData.Master, IsLowPopulation = true)] public class TypeConditionSymbol : Symbol { private TypeConditionSymbol() { } diff --git a/Signum.Entities.Extensions/Chart/ChartScript.cs b/Signum.Entities.Extensions/Chart/ChartScript.cs index 7b5bbad01f..eb19adbf37 100644 --- a/Signum.Entities.Extensions/Chart/ChartScript.cs +++ b/Signum.Entities.Extensions/Chart/ChartScript.cs @@ -2,6 +2,7 @@ namespace Signum.Entities.Chart; +[EntityKind(EntityKind.SystemString, EntityData.Master, IsLowPopulation = true)] public class ChartScriptSymbol : Symbol { private ChartScriptSymbol() { } diff --git a/Signum.Entities.Extensions/Files/FileTypeSymbol.cs b/Signum.Entities.Extensions/Files/FileTypeSymbol.cs index 14c1702c88..f808c02435 100644 --- a/Signum.Entities.Extensions/Files/FileTypeSymbol.cs +++ b/Signum.Entities.Extensions/Files/FileTypeSymbol.cs @@ -1,6 +1,7 @@ namespace Signum.Entities.Files; +[EntityKind(EntityKind.SystemString, EntityData.Master, IsLowPopulation = true)] public class FileTypeSymbol : Symbol { private FileTypeSymbol() { } diff --git a/Signum.Entities.Extensions/Templating/TemplatingModelConverterSymbole.cs b/Signum.Entities.Extensions/Templating/TemplatingModelConverterSymbole.cs index 806df2a208..06f89ce59c 100644 --- a/Signum.Entities.Extensions/Templating/TemplatingModelConverterSymbole.cs +++ b/Signum.Entities.Extensions/Templating/TemplatingModelConverterSymbole.cs @@ -1,6 +1,7 @@ - + namespace Signum.Entities.Templating; +[EntityKind(EntityKind.SystemString, EntityData.Master, IsLowPopulation = true)] public class ModelConverterSymbol : Symbol { private ModelConverterSymbol() { } diff --git a/Signum.Entities.Extensions/Word/WordTemplate.cs b/Signum.Entities.Extensions/Word/WordTemplate.cs index c5de33d56d..2c9852e22a 100644 --- a/Signum.Entities.Extensions/Word/WordTemplate.cs +++ b/Signum.Entities.Extensions/Word/WordTemplate.cs @@ -101,6 +101,7 @@ public enum WordTemplateMessage WordReport, } +[EntityKind(EntityKind.SystemString, EntityData.Master, IsLowPopulation = true)] public class WordTransformerSymbol : Symbol { private WordTransformerSymbol() { } @@ -111,6 +112,7 @@ public WordTransformerSymbol(Type declaringType, string fieldName) : } } +[EntityKind(EntityKind.SystemString, EntityData.Master, IsLowPopulation = true)] public class WordConverterSymbol : Symbol { private WordConverterSymbol() { } diff --git a/Signum.Entities/FieldAttributes.cs b/Signum.Entities/FieldAttributes.cs index 6ea4c90944..1037b9265f 100644 --- a/Signum.Entities/FieldAttributes.cs +++ b/Signum.Entities/FieldAttributes.cs @@ -444,6 +444,18 @@ public CombineStrategyAttribute(CombineStrategy strategy) } } +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] +public sealed class LiteModelAttribute : Attribute +{ + public Type LiteModelType { get; private set; } + public Type? ForEntityType { get; set; } + + public LiteModelAttribute(Type liteModel) + { + this.LiteModelType = liteModel; + } +} + public enum CombineStrategy { Union, diff --git a/Signum.Entities/Lite.cs b/Signum.Entities/Lite.cs index 256c48397a..b0f68aa679 100644 --- a/Signum.Entities/Lite.cs +++ b/Signum.Entities/Lite.cs @@ -45,6 +45,15 @@ public interface Lite : IComparable, IComparable> /// Type EntityType { get; } + /// + /// The type of the model (typically typeof(string)) + /// + Type ModelType { get; } + + /// + /// The model of the lite (typically the ToString() evaluation) + /// + object? Model { get; } /// /// Removes the reference to the full entity, maiking the lite more lightweight @@ -57,9 +66,9 @@ public interface Lite : IComparable, IComparable> void SetEntity(Entity ei); /// - /// Sets the toStr of the entity. Not checked in anyway. + /// Sets the model of the entity. Not checked in anyway. /// - void SetToString(string toStr); + void SetModel(object? model); /// /// Copies the Id from the entity to this lite instance. Typically used after saving by the framework. @@ -83,18 +92,71 @@ public interface Lite : IComparable, IComparable> Lite Clone(); } + +public interface ILiteModelConstructor +{ + public bool IsDefault { get; set; } + public Type EntityType { get; } + public Type ModelType { get; } + + ILiteModelConstructor Clone(bool isDefault); + + LambdaExpression GetConstructorExpression(); + + object? NotFoundModel(Lite lite); +} + +public class LiteModelConstructor : ILiteModelConstructor + where T : Entity +{ + public LiteModelConstructor(bool isDefault, Expression> constructorExpression, Func constructorFunction) + { + IsDefault = isDefault; + ConstructorExpression = constructorExpression; + ConstructorFunction = constructorFunction; + } + + public LiteModelConstructor(bool isDefault, Expression> constructorExpression) + { + IsDefault = isDefault; + ConstructorExpression = constructorExpression; + ConstructorFunction = constructorExpression.Compile(); + } + + public bool IsDefault { get; set; } + public Type EntityType => typeof(T); + public Type ModelType => typeof(M); + public Expression> ConstructorExpression { get; } + public Func ConstructorFunction { get; } + + public Func, M>? NotFoundModel { get; set; } + + public ILiteModelConstructor Clone(bool isDefault) => new LiteModelConstructor(isDefault, ConstructorExpression, ConstructorFunction); + + object? ILiteModelConstructor.NotFoundModel(Lite lite) + { + if (NotFoundModel == null) + return null; + + return NotFoundModel.Invoke((Lite)lite); + } + + public LambdaExpression GetConstructorExpression() => this.ConstructorExpression; +} + public static class Lite { public static Type BaseImplementationType = typeof(LiteImp); - static GenericInvoker>> giNewLite = - new((id, str) => new LiteImp(id, str)); + static GenericInvoker>> giNewLite = + new((id, m) => new LiteImp(id, (string?)m)); - static GenericInvoker>> giNewLiteFat = - new((entity, str) => new LiteImp(entity, str)); + static GenericInvoker>> giNewLiteFat = + new((entity, m) => new LiteImp(entity, (string?)m)); public static Type Generate(Type identificableType) { + return typeof(Lite<>).MakeGenericType(identificableType); } @@ -117,6 +179,7 @@ public static Lite Parse(string liteKey) throw new FormatException(error); } + public static Lite Parse(string liteKey) where T : class, IEntity { return (Lite)Lite.Parse(liteKey); @@ -141,7 +204,7 @@ public static Lite Parse(string liteKey) where T : class, IEntity string? toStr = match.Groups["toStr"].Value.DefaultText(null!); //maybe null - result = giNewLite.GetInvoker(type)(id, toStr); + result = giNewLite.GetInvoker(type, typeof(string))(id, toStr); return null; } @@ -154,12 +217,17 @@ public static Lite Parse(string liteKey) where T : class, IEntity public static Lite Create(Type type, PrimaryKey id) { - return giNewLite.GetInvoker(type)(id, null); + return giNewLite.GetInvoker(type, typeof(string))(id, null); } - public static Lite Create(Type type, PrimaryKey id, string? toStr) + public static Lite Create(Type type, PrimaryKey id, object model) { - return giNewLite.GetInvoker(type)(id, toStr); + return giNewLite.GetInvoker(type, model.GetType())(id, model); + } + + public static Lite Create(Type type, PrimaryKey id, Type modelType) + { + return giNewLite.GetInvoker(type, modelType)(id, null); } [DebuggerStepThrough] @@ -169,31 +237,69 @@ public static Lite ToLite(this T entity) if (entity.IdOrNull == null) throw new InvalidOperationException("ToLite is not allowed for new entities, use ToLiteFat instead"); - return (Lite)giNewLite.GetInvoker(entity.GetType())(entity.Id, entity.ToString()); + var modelType = Lite.DefaultModelType(entity.GetType()); + + var model = Lite.GetModel(entity, modelType); + + return (Lite)giNewLite.GetInvoker(entity.GetType(), modelType)(entity.Id, model); } [DebuggerStepThrough] - public static Lite ToLite(this T entity, string? toStr) + public static Lite ToLite(this T entity, Type modelType) + where T : class, IEntity + { + if (entity.IdOrNull == null) + throw new InvalidOperationException("ToLite is not allowed for new entities, use ToLiteFat instead"); + + var model = Lite.GetModel(entity, modelType); + + return (Lite)giNewLite.GetInvoker(entity.GetType(), modelType)(entity.Id, model); + } + + public static IEnumerable GetAllLiteModelTypes(Type entityType) + { + var dic = Lite.LiteModelConstructors.TryGetC(entityType); + if (dic == null) + return new[] { typeof(string) }; + + return dic.Keys.PreAnd(typeof(string)); + } + + [DebuggerStepThrough] + public static Lite ToLite(this T entity, object model) where T : class, IEntity { if (entity.IsNew) throw new InvalidOperationException("ToLite is not allowed for new entities, use ToLiteFat instead"); - return (Lite)giNewLite.GetInvoker(entity.GetType())(entity.Id, toStr); + return (Lite)giNewLite.GetInvoker(entity.GetType(), model.GetType())(entity.Id, model); } [DebuggerStepThrough] public static Lite ToLiteFat(this T entity) where T : class, IEntity { - return (Lite)giNewLiteFat.GetInvoker(entity.GetType())((Entity)(IEntity)entity, entity.ToString()); + var modelType = Lite.DefaultModelType(entity.GetType()); + + var model = Lite.GetModel(entity, modelType); + + return (Lite)giNewLiteFat.GetInvoker(entity.GetType(), modelType)((Entity)(IEntity)entity, model); } [DebuggerStepThrough] - public static Lite ToLiteFat(this T entity, string? toStr) + public static Lite ToLiteFat(this T entity, Type modelType) + where T : class, IEntity + { + var model = Lite.GetModel(entity, modelType); + + return (Lite)giNewLiteFat.GetInvoker(entity.GetType(), modelType)((Entity)(IEntity)entity, model); + } + + [DebuggerStepThrough] + public static Lite ToLiteFat(this T entity, object model) where T : class, IEntity { - return (Lite)giNewLiteFat.GetInvoker(entity.GetType())((Entity)(IEntity)entity, toStr ?? entity.ToString()); + return (Lite)giNewLiteFat.GetInvoker(entity.GetType(), model.GetType())((Entity)(IEntity)entity, model); } [DebuggerStepThrough] @@ -206,12 +312,12 @@ public static Lite ToLite(this T entity, bool fat) where T : class, IEntit } [DebuggerStepThrough] - public static Lite ToLite(this T entity, bool fat, string toStr) where T : class, IEntity + public static Lite ToLite(this T entity, bool fat, object model) where T : class, IEntity { if (fat) - return entity.ToLiteFat(toStr); + return entity.ToLiteFat(model); else - return entity.ToLite(toStr); + return entity.ToLite(model); } class IsExpander : IMethodExpander @@ -222,6 +328,123 @@ public Expression Expand(Expression? instance, Expression[] arguments, MethodInf } } + public static Dictionary> LiteModelConstructors = new(); + + public static void RegisterLiteModelConstructor(Expression> constructorExpression, bool isDefault = true, bool isOverride = false) where T : Entity + => RegisterLiteModelConstructor(new LiteModelConstructor(isDefault, constructorExpression), isOverride); + + public static void RegisterLiteModelConstructor(LiteModelConstructor liteModelConstructor, bool isOverride = false) where T : Entity + { + if (typeof(M).IsModelEntity() && typeof(M).GetMethod("ToString", BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)?.GetCustomAttribute(false) == null) + throw new InvalidOperationException($"{typeof(M).TypeName()} needs to implement ToString using [AutoExpressionField] to allow ToString on Lite<{typeof(T).TypeName()}>"); + + var dic = LiteModelConstructors.GetOrCreate(typeof(T)); + + if (dic.ContainsKey(typeof(M)) && !isOverride) + throw new InvalidOperationException($"'{typeof(T).TypeName()}' already has lite model constructor for Model '{typeof(M).TypeName()}'. Consider using isOverride = true"); + + var currentDefault = dic.Values.SingleOrDefault(a => a.IsDefault); + if (liteModelConstructor.IsDefault && currentDefault != null) + { + if (!isOverride) + throw new InvalidOperationException($"'{typeof(T).TypeName()}' already has a default Lite Model Constructor ({currentDefault.ModelType.TypeName()})"); + + dic[typeof(M)] = currentDefault.Clone(isDefault: false); + } + + dic[typeof(M)] = liteModelConstructor; + } + + public static Type DefaultModelType(Type type) + { + return LiteModelConstructors.TryGetC(type)?.Values.SingleOrDefaultEx(a => a.IsDefault)?.ModelType ?? typeof(string); + } + + public static object? GetNotFoundModel(Lite lite) + { + var lmc = LiteModelConstructors.TryGetC(lite.EntityType)?.TryGetC(lite.ModelType); + + if (lmc == null) + { + if (lite.ModelType == typeof(string)) + return ("[" + EngineMessage.EntityWithType0AndId1NotFound.NiceToString().FormatWith(lite.EntityType.NiceName(), lite.Id) + "]"); + + throw new InvalidOperationException($"Entity '{lite.EntityType}' has not registered LiteModelConstructor for '{lite.ModelType}'"); + } + + return lmc.NotFoundModel((Lite)lite); + } + + public static object GetModel(IEntity e, Type modelType) + { + return giGetModel.GetInvoker(e.GetType(), modelType)((Entity)e); + } + + public static string ModelTypeToString(Type modelType) + { + if (modelType == typeof(string)) + return "string"; + + return Reflector.CleanTypeName(modelType); + } + + public static Type ParseModelType(Type entityType, string modelTypeStr) + { + if (modelTypeStr == "string" || + modelTypeStr == "String") + return typeof(string); + + var dic = LiteModelConstructors.TryGetC(entityType); + + var single = dic?.Keys.SingleOrDefaultEx(a => Reflector.CleanTypeName(a) == modelTypeStr); + + if (single == null) + throw new InvalidOperationException($"No Lite Model with name '{modelTypeStr}' is registered for '{entityType.TypeName()}'"); + + return single; + } + + static GenericInvoker> giGetModel = new GenericInvoker>((e) => GetModel(e)); + public static M GetModel(T e) + where T : Entity + { + var lmc = LiteModelConstructors.TryGetC(typeof(T))?.TryGetC(typeof(M)); + + if (lmc == null) + { + if (typeof(M) == typeof(string)) + return (M)(object)e.ToString()!; + + throw new InvalidOperationException($"Entity '{typeof(T).TypeName()}' has not registered LiteModelConstructor for '{typeof(M).TypeName()}'"); + } + + return ((LiteModelConstructor)lmc).ConstructorFunction(e); + } + + public static LambdaExpression GetModelConstructorExpression(Type entityType, Type modelType) => giGetModelConstructorExpression.GetInvoker(entityType, modelType)(); + static readonly GenericInvoker> giGetModelConstructorExpression = new(() => GetModelConstructorExpression()); + public static Expression> GetModelConstructorExpression() + where T : Entity + { + var lmc = LiteModelConstructors.TryGetC(typeof(T))?.TryGetC(typeof(M)); + + if (lmc == null) + { + if (typeof(M) == typeof(string)) + { + Expression> ex = e => e.ToString(); + + return (Expression>)(LambdaExpression)ex; + } + + throw new InvalidOperationException($"Entity '{typeof(T).TypeName()}' has not registered LiteModelConstructor for '{typeof(M).TypeName()}'"); + } + + return ((LiteModelConstructor)lmc).ConstructorExpression; + } + + + [MethodExpander(typeof(IsExpander))] public static bool Is(this T? entity1, T? entity2) @@ -360,45 +583,32 @@ public static Type CleanType(this Type t) public static Lite Create(PrimaryKey id) where T : Entity { - return new LiteImp(id, null); + return new LiteImp(id, null); } - public static Lite Create(PrimaryKey id, string toStr) where T : Entity + public static Lite Create(PrimaryKey id, object model) where T : Entity { - return new LiteImp(id, toStr); - } + if(model == null || model is string) + return new LiteImp(id, (string?)model); - static ConcurrentDictionary ciLiteConstructorId = new ConcurrentDictionary(); - public static ConstructorInfo LiteConstructorId(Type type) - { - return ciLiteConstructorId.GetOrAdd(type, CreateLiteConstructor); + return (Lite)giNewLite.GetInvoker(typeof(T), model?.GetType() ?? typeof(string))(id, model); } - static ConstructorInfo CreateLiteConstructor(Type t) + static ConcurrentDictionary<(Type type, Type modelType), ConstructorInfo> liteConstructorCache = new(); + public static ConstructorInfo GetLiteConstructorFromCache(Type type, Type modelType) { - return typeof(LiteImp<>).MakeGenericType(t).GetConstructor(new[] { typeof(PrimaryKey), typeof(string) })!; + return liteConstructorCache.GetOrAdd((type, modelType), t => CreateLiteConstructor(t.type, t.modelType)); } - - public static NewExpression NewExpression(Type type, Expression id, Expression toString) + static ConstructorInfo CreateLiteConstructor(Type t, Type modelType) { - return Expression.New(Lite.LiteConstructorId(type), id.UnNullify(), toString); + return typeof(LiteImp<,>).MakeGenericType(t, modelType).GetConstructor(new[] { typeof(PrimaryKey), modelType })!; } - static Lite? ToLiteFatInternal(this T? entity, string? toStr) - where T : class, IEntity - { - if (entity == null) - return null; - - return entity.ToLiteFat(toStr); - } - - static MethodInfo miToLiteFatInternal = ReflectionTools.GetMethodInfo(() => ToLiteFatInternal(null, null)).GetGenericMethodDefinition(); - public static Expression ToLiteFatInternalExpression(Expression reference, Expression toString) + public static NewExpression NewExpression(Type type, Expression id, Expression model) { - return Expression.Call(miToLiteFatInternal.MakeGenericMethod(reference.Type), reference, toString); + return Expression.New(Lite.GetLiteConstructorFromCache(type, model.Type), id.UnNullify(), model); } public static Lite ParsePrimaryKey(string id) diff --git a/Signum.Entities/LiteImp.cs b/Signum.Entities/LiteImp.cs index 0ac98aa6ed..de809da2b0 100644 --- a/Signum.Entities/LiteImp.cs +++ b/Signum.Entities/LiteImp.cs @@ -7,19 +7,19 @@ public abstract class LiteImp : Modifiable } -public sealed class LiteImp : LiteImp, Lite +public sealed class LiteImp : LiteImp, Lite where T : Entity { T? entityOrNull; PrimaryKey? id; - string? toStr; + M? model; // Methods private LiteImp() { } - public LiteImp(PrimaryKey id, string? toStr) + public LiteImp(PrimaryKey id, M? model) { if (typeof(T).IsAbstract) throw new InvalidOperationException(typeof(T).Name + " is abstract"); @@ -29,11 +29,11 @@ public LiteImp(PrimaryKey id, string? toStr) + PrimaryKey.Type(typeof(T)).TypeName() + ", not " + id.Object.GetType().TypeName()); this.id = id; - this.toStr = toStr; + this.model = model; this.Modified = ModifiedState.Clean; } - public LiteImp(T entity, string? toStr) + public LiteImp(T entity, M? model) { if (typeof(T).IsAbstract) throw new InvalidOperationException(typeof(T).Name + " is abstract"); @@ -43,7 +43,7 @@ public LiteImp(T entity, string? toStr) this.entityOrNull = entity; this.id = entity.IdOrNull; - this.toStr = toStr; + this.model = model; this.Modified = entity.Modified; } @@ -77,6 +77,11 @@ public Type EntityType get { return typeof(T); } } + public Type ModelType + { + get { return typeof(M); } + } + public PrimaryKey Id { get @@ -92,6 +97,8 @@ public PrimaryKey? IdOrNull get { return id; } } + public object? Model => this.model; + public void SetEntity(Entity ei) { if (id == null) @@ -101,8 +108,8 @@ public void SetEntity(Entity ei) throw new InvalidOperationException("Entities do not match"); this.entityOrNull = (T)ei; - if (ei != null && this.toStr == null) - this.toStr = ei.ToString(); + if (ei != null && this.model == null) + this.model = Lite.GetModel(this.entityOrNull); } public void ClearEntity() @@ -113,7 +120,7 @@ public void ClearEntity() if (id == null) throw new InvalidOperationException("Removing entity not allowed in new Lite"); - this.toStr = this.entityOrNull?.ToString(); + this.model = Lite.GetModel(this.entityOrNull!); this.entityOrNull = null; } @@ -137,7 +144,7 @@ protected internal override void PreSaving(PreSavingContext ctx) if (this.entityOrNull != null) return this.entityOrNull.ToString(); - return this.toStr; + return this.model?.ToString(); } public override bool Equals(object? obj) @@ -151,7 +158,7 @@ public override bool Equals(object? obj) if (GetType() != obj.GetType()) return false; - Lite lite = (LiteImp)obj; + Lite lite = (Lite)obj; if (IdOrNull != null && lite.IdOrNull != null) return Id == lite.Id; else @@ -188,13 +195,13 @@ public int CompareTo(object? obj) throw new InvalidOperationException("obj is not a Lite"); } - public void SetToString(string toStr) + public void SetModel(object? model) { - this.toStr = toStr; + this.model = (M?)model; } public Lite Clone() { - return new LiteImp(Id, toStr); + return new LiteImp(Id, model); } } diff --git a/Signum.React.Extensions.Selenium/LineProxies/EntityBaseProxy.cs b/Signum.React.Extensions.Selenium/LineProxies/EntityBaseProxy.cs index 78fdbec9e0..719066166f 100644 --- a/Signum.React.Extensions.Selenium/LineProxies/EntityBaseProxy.cs +++ b/Signum.React.Extensions.Selenium/LineProxies/EntityBaseProxy.cs @@ -229,9 +229,12 @@ public EntityInfoProxy(string dataEntity) } - public Lite ToLite(string? toString = null) + public Lite ToLite(object? liteModel = null) { - return Lite.Create(this.EntityType, this.IdOrNull!.Value, toString); + if (liteModel == null) + return Lite.Create(this.EntityType, this.IdOrNull!.Value); + else + return Lite.Create(this.EntityType, this.IdOrNull!.Value, liteModel); } public static EntityInfoProxy? Parse(string dataEntity) diff --git a/Signum.React.Extensions.Selenium/LineProxies/EntityComboProxy.cs b/Signum.React.Extensions.Selenium/LineProxies/EntityComboProxy.cs index 84da27cfef..37c2d7f4bf 100644 --- a/Signum.React.Extensions.Selenium/LineProxies/EntityComboProxy.cs +++ b/Signum.React.Extensions.Selenium/LineProxies/EntityComboProxy.cs @@ -43,7 +43,7 @@ public Lite? LiteValue public List?> Options() { return this.ComboElement.Options - .Select(o => Lite.Parse(o.GetAttribute("value"))?.Do(l => l.SetToString(o.Text))) + .Select(o => Lite.Parse(o.GetAttribute("value"))?.Do(l => l.SetModel(o.Text))) .ToList(); } diff --git a/Signum.React.Extensions.Selenium/LineProxies/EntityListCheckBoxProxy.cs b/Signum.React.Extensions.Selenium/LineProxies/EntityListCheckBoxProxy.cs index dfbe5602b4..ad1144e0c6 100644 --- a/Signum.React.Extensions.Selenium/LineProxies/EntityListCheckBoxProxy.cs +++ b/Signum.React.Extensions.Selenium/LineProxies/EntityListCheckBoxProxy.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using OpenQA.Selenium; using Signum.Entities; @@ -23,7 +23,7 @@ public List> GetDataElements() return this.Element.WithLocator(By.CssSelector("label.sf-checkbox-element")).FindElements().Select(e => { var lite = Lite.Parse(e.FindElement(By.CssSelector("input[type=checkbox]")).GetAttribute("name")); - lite.SetToString(e.FindElement(By.CssSelector("span.sf-entitStrip-link")).Text); + lite.SetModel(e.FindElement(By.CssSelector("span.sf-entitStrip-link")).Text); return lite; }).ToList(); } diff --git a/Signum.React.Extensions/Alerts/AlertDropdown.tsx b/Signum.React.Extensions/Alerts/AlertDropdown.tsx index 10e0d0855f..e0ddede0da 100644 --- a/Signum.React.Extensions/Alerts/AlertDropdown.tsx +++ b/Signum.React.Extensions/Alerts/AlertDropdown.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Operations from '@framework/Operations' import * as Finder from '@framework/Finder' -import { is, JavascriptMessage, toLite } from '@framework/Signum.Entities' +import { getToString, is, JavascriptMessage, toLite } from '@framework/Signum.Entities' import { Toast, Button, ButtonGroup } from 'react-bootstrap' import { DateTime } from 'luxon' import { useAPIWithReload, useForceUpdate, useUpdatedRef } from '@framework/Hooks'; @@ -224,7 +224,7 @@ export function AlertToast(p: { alert: AlertEntity, onClose: (e: AlertEntity[]) {AlertsClient.formatText(p.alert.textField || p.alert.textFromAlertType || "", p.alert, p.refresh)} - {p.alert.createdBy && {p.alert.createdBy?.toStr}} + {p.alert.createdBy && {getToString(p.alert.createdBy)}} ); diff --git a/Signum.React.Extensions/Alerts/AlertsClient.tsx b/Signum.React.Extensions/Alerts/AlertsClient.tsx index 8dec1f57be..f3238610df 100644 --- a/Signum.React.Extensions/Alerts/AlertsClient.tsx +++ b/Signum.React.Extensions/Alerts/AlertsClient.tsx @@ -13,7 +13,7 @@ import { andClose } from '@framework/Operations/EntityOperations'; import * as AuthClient from '../Authorization/AuthClient' import { ajaxGet } from '@framework/Services' import * as Finder from '@framework/Finder' -import { Entity, isEntity, isLite, Lite, toLite } from '@framework/Signum.Entities' +import { Entity, getToString, isEntity, isLite, Lite, toLite } from '@framework/Signum.Entities' import { EntityLink } from '@framework/Search' import Alert from './Templates/Alert' import { ISymbol, PropertyRoute, symbolNiceName } from '@framework/Reflection' @@ -145,7 +145,7 @@ export function formatText(text: string, alert: Partial, onNavigate isLite(prop) ? prop : null; if (groups.url) - nodes.push({replacePlaceHolders(groups.text, alert) ?? lite?.toStr}); + nodes.push({replacePlaceHolders(groups.text, alert) ?? getToString(lite)}); else if (lite != null) { if (groups.text) nodes.push({replacePlaceHolders(groups.text, alert)}); diff --git a/Signum.React.Extensions/Authorization/Admin/OperationRulePackControl.tsx b/Signum.React.Extensions/Authorization/Admin/OperationRulePackControl.tsx index 352d80d934..53755ec3ff 100644 --- a/Signum.React.Extensions/Authorization/Admin/OperationRulePackControl.tsx +++ b/Signum.React.Extensions/Authorization/Admin/OperationRulePackControl.tsx @@ -3,7 +3,7 @@ import { Button } from 'react-bootstrap' import { notifySuccess } from '@framework/Operations' import { TypeContext, ButtonsContext, IRenderButtons, ButtonBarElement } from '@framework/TypeContext' import { EntityLine, ValueLine } from '@framework/Lines' -import { OperationSymbol } from '@framework/Signum.Entities' +import { getToString, OperationSymbol } from '@framework/Signum.Entities' import { API } from '../AuthAdminClient' import { OperationRulePack, OperationAllowed, OperationAllowedRule, AuthAdminMessage, PermissionSymbol, AuthEmailMessage } from '../Signum.Entities.Authorization' import { ColorRadio, GrayCheckbox } from './ColoredRadios' @@ -77,7 +77,7 @@ export default React.forwardRef(function OperationRulePackControl({ ctx }: { ctx {ctx.mlistItemCtxs(a => a.rules).map((c, i) => - {c.value.resource!.operation!.toStr} + {getToString(c.value.resource!.operation)} {renderRadio(c.value, "Allow", "green")} diff --git a/Signum.React.Extensions/Authorization/Admin/QueryRulePackControl.tsx b/Signum.React.Extensions/Authorization/Admin/QueryRulePackControl.tsx index f821da6662..cc18287a82 100644 --- a/Signum.React.Extensions/Authorization/Admin/QueryRulePackControl.tsx +++ b/Signum.React.Extensions/Authorization/Admin/QueryRulePackControl.tsx @@ -9,6 +9,7 @@ import { ColorRadio, GrayCheckbox } from './ColoredRadios' import { Button } from 'react-bootstrap' import "./AuthAdmin.css" import { useForceUpdate } from '@framework/Hooks'; +import { getToString } from '@framework/Signum.Entities'; export default React.forwardRef(function QueryRulesPackControl({ ctx }: { ctx: TypeContext }, ref: React.Ref) { @@ -77,7 +78,7 @@ export default React.forwardRef(function QueryRulesPackControl({ ctx }: { ctx: T {ctx.mlistItemCtxs(a => a.rules).orderBy(a => a.value.resource.key).map((c, i) => - {c.value.resource.toStr} + {getToString(c.value.resource)} {renderRadio(c.value, "Allow", "green")} diff --git a/Signum.React.Extensions/Authorization/Admin/TypeRulePackControl.tsx b/Signum.React.Extensions/Authorization/Admin/TypeRulePackControl.tsx index 27ce6f3a52..6a5a75c8f5 100644 --- a/Signum.React.Extensions/Authorization/Admin/TypeRulePackControl.tsx +++ b/Signum.React.Extensions/Authorization/Admin/TypeRulePackControl.tsx @@ -12,7 +12,7 @@ import SelectorModal from '@framework/SelectorModal' import MessageModal from '@framework/Modals/MessageModal' import { getTypeInfo, Binding, GraphExplorer } from '@framework/Reflection' -import { OperationSymbol, ModelEntity, newMListElement, NormalControlMessage } from '@framework/Signum.Entities' +import { OperationSymbol, ModelEntity, newMListElement, NormalControlMessage, getToString } from '@framework/Signum.Entities' import { API, properties, queries, operations } from '../AuthAdminClient' import { TypeRulePack, AuthAdminMessage, PermissionSymbol, TypeAllowed, TypeAllowedRule, @@ -188,7 +188,7 @@ export default React.forwardRef(function TypesRulesPackControl({ ctx }: { ctx: T function handleAddConditionClick(remainig: TypeConditionSymbol[], taac: TypeAllowedAndConditions) { - SelectorModal.chooseElement(remainig, { buttonDisplay: a => a.toStr.tryAfter(".") ?? a.toStr }) + SelectorModal.chooseElement(remainig, { buttonDisplay: a => getToString(a) }) .then(tc => { if (!tc) return; @@ -278,7 +278,7 @@ export default React.forwardRef(function TypesRulesPackControl({ ctx }: { ctx: T {"\u00A0 \u00A0".repeat(i + 1)} handleRemoveConditionClick(tctx.value.allowed, c)}>   - {c.typeCondition.toStr.tryAfter(".") ?? c.typeCondition.toStr} + {getToString(c.typeCondition)} {colorRadio(b, "Write", "green")} diff --git a/Signum.React.Extensions/Authorization/AuthAdminController.cs b/Signum.React.Extensions/Authorization/AuthAdminController.cs index e77d334fad..ba410f4377 100644 --- a/Signum.React.Extensions/Authorization/AuthAdminController.cs +++ b/Signum.React.Extensions/Authorization/AuthAdminController.cs @@ -17,7 +17,7 @@ public class AuthAdminController : ControllerBase public PermissionRulePack GetPermissionRules(string roleId) { BasicPermission.AdminRules.AssertAuthorized(); - var rules = PermissionAuthLogic.GetPermissionRules(Lite.ParsePrimaryKey(roleId).FillToString()); + var rules = PermissionAuthLogic.GetPermissionRules(Lite.ParsePrimaryKey(roleId).FillLiteModel()); CleanChanges(rules); return rules; } @@ -34,7 +34,7 @@ public void SetPermissionRules([Required, FromBody]PermissionRulePack rules) public TypeRulePack GetTypeRules(string roleId) { BasicPermission.AdminRules.AssertAuthorized(); - var rules = TypeAuthLogic.GetTypeRules(Lite.ParsePrimaryKey(roleId).FillToString()); + var rules = TypeAuthLogic.GetTypeRules(Lite.ParsePrimaryKey(roleId).FillLiteModel()); CleanChanges(rules); return rules; } @@ -52,7 +52,7 @@ public void SetTypeRules([Required, FromBody]TypeRulePack rules) public OperationRulePack GetOperationRules(string typeName, string roleId) { BasicPermission.AdminRules.AssertAuthorized(); - var rules = OperationAuthLogic.GetOperationRules(Lite.ParsePrimaryKey(roleId).FillToString(), TypeLogic.GetType(typeName).ToTypeEntity()); + var rules = OperationAuthLogic.GetOperationRules(Lite.ParsePrimaryKey(roleId).FillLiteModel(), TypeLogic.GetType(typeName).ToTypeEntity()); CleanChanges(rules); return rules; } @@ -71,7 +71,7 @@ public void SetOperationRules([Required, FromBody]OperationRulePack rules) public PropertyRulePack GetPropertyRule(string typeName, string roleId) { BasicPermission.AdminRules.AssertAuthorized(); - var rules = PropertyAuthLogic.GetPropertyRules(Lite.ParsePrimaryKey(roleId).FillToString(), TypeLogic.GetType(typeName).ToTypeEntity()); + var rules = PropertyAuthLogic.GetPropertyRules(Lite.ParsePrimaryKey(roleId).FillLiteModel(), TypeLogic.GetType(typeName).ToTypeEntity()); CleanChanges(rules); return rules; } @@ -90,7 +90,7 @@ public void SetPropertyRule([Required, FromBody]PropertyRulePack rules) public QueryRulePack GetQueryRules(string typeName, string roleId) { BasicPermission.AdminRules.AssertAuthorized(); - var rules = QueryAuthLogic.GetQueryRules(Lite.ParsePrimaryKey(roleId).FillToString(), TypeLogic.GetType(typeName).ToTypeEntity()); + var rules = QueryAuthLogic.GetQueryRules(Lite.ParsePrimaryKey(roleId).FillLiteModel(), TypeLogic.GetType(typeName).ToTypeEntity()); CleanChanges(rules); return rules; } diff --git a/Signum.React.Extensions/Authorization/AzureAD/AzureAD.tsx b/Signum.React.Extensions/Authorization/AzureAD/AzureAD.tsx index 8452c5b1ce..bcb1d9c7a7 100644 --- a/Signum.React.Extensions/Authorization/AzureAD/AzureAD.tsx +++ b/Signum.React.Extensions/Authorization/AzureAD/AzureAD.tsx @@ -7,14 +7,22 @@ import { ExternalServiceError } from "../../../Signum.React/Scripts/Services"; import { LoginAuthMessage } from "../Signum.Entities.Authorization"; /* Add this to Index.cshtml - var __azureApplicationId = @Json.Serialize(Starter.Configuration.Value.ActiveDirectory.Azure_ApplicationID); - var __azureTenantId = @Json.Serialize(Starter.Configuration.Value.ActiveDirectory.Azure_DirectoryID); + var __azureApplicationId = @Json.Serialize(TenantLogic.GetCurrentTenant()!.ActiveDirectoryConfiguration.Azure_ApplicationID); + var __azureTenantId = @Json.Serialize(TenantLogic.GetCurrentTenant()!.ActiveDirectoryConfiguration.Azure_DirectoryID); + var __tenantLogo = @Json.Serialize(TenantLogic.GetCurrentTenant()!.Logo.BinaryFile); * */ +interface TenantConfigurationEntityPart { + name: string | undefined; + navbarCSS: string | undefined; + lightNavbarCSS: string | undefined; +} + declare global { interface Window { __azureApplicationId: string | null; __azureTenantId: string | null; + __tenant: TenantConfigurationEntityPart; } } diff --git a/Signum.React.Extensions/Chart/ChartClient.tsx b/Signum.React.Extensions/Chart/ChartClient.tsx index 5bc9e5ba28..55a0a6f509 100644 --- a/Signum.React.Extensions/Chart/ChartClient.tsx +++ b/Signum.React.Extensions/Chart/ChartClient.tsx @@ -4,7 +4,7 @@ import { ajaxGet } from '@framework/Services'; import * as Navigator from '@framework/Navigator' import * as AppContext from '@framework/AppContext' import * as Finder from '@framework/Finder' -import { Entity, Lite, liteKey, MList } from '@framework/Signum.Entities' +import { Entity, getToString, Lite, liteKey, MList } from '@framework/Signum.Entities' import { getQueryKey, getEnumInfo, QueryTokenString, getTypeInfos, tryGetTypeInfos, timeToString, toFormatWithFixes } from '@framework/Reflection' import { FilterOption, OrderOption, OrderOptionParsed, QueryRequest, QueryToken, SubTokensOptions, ResultTable, OrderRequest, OrderType, FilterOptionParsed, hasAggregate, ColumnOption, withoutAggregate @@ -648,7 +648,7 @@ export module API { if (token.type.isLite) return v => { var lite = v as Lite | null; - return String(lite?.toStr ?? ""); + return String(getToString(lite) ?? ""); }; if (token.filterType == "Enum") @@ -746,7 +746,7 @@ export module API { if (!hasAggregates(request)) { const value = (r: ChartRow) => r.entity; const color = (v: Lite | undefined) => !v ? "#555" : null; - const niceName = (v: Lite | undefined) => v?.toStr; + const niceName = (v: Lite | undefined) => getToString(v); const key = (v: Lite | undefined) => v ? liteKey(v) : String(v); cols.insertAt(0, ({ name: "entity", diff --git a/Signum.React.Extensions/Chart/ChartPalette/ChartPaletteClient.tsx b/Signum.React.Extensions/Chart/ChartPalette/ChartPaletteClient.tsx index 9d69b2744b..75fd752814 100644 --- a/Signum.React.Extensions/Chart/ChartPalette/ChartPaletteClient.tsx +++ b/Signum.React.Extensions/Chart/ChartPalette/ChartPaletteClient.tsx @@ -3,7 +3,7 @@ import { ajaxPost, ajaxGet } from '@framework/Services'; import { EntitySettings } from '@framework/Navigator' import * as Navigator from '@framework/Navigator' import * as Finder from '@framework/Finder' -import { Entity, Lite, liteKey } from '@framework/Signum.Entities' +import { Entity, getToString, Lite, liteKey } from '@framework/Signum.Entities' import * as QuickLinks from '@framework/QuickLinks' import { OrderOptionParsed } from '@framework/FindOptions' import * as AuthClient from '../../Authorization/AuthClient' @@ -71,7 +71,7 @@ export function toColorPalete(model: ChartPaletteModel): ColorPalette { if (ti == null || ti.kind == "Enum") { var byName = model.colors.filter(a => a.element.color != null) - .toObject(a => a.element.related.toStr!, a => a.element.color); + .toObject(a => getToString(a.element.related)!, a => a.element.color); return { ...byId, ...byName }; } diff --git a/Signum.React.Extensions/Chart/UserChart/UserChartClient.tsx b/Signum.React.Extensions/Chart/UserChart/UserChartClient.tsx index a03b96bdb3..3c3fcd91d9 100644 --- a/Signum.React.Extensions/Chart/UserChart/UserChartClient.tsx +++ b/Signum.React.Extensions/Chart/UserChart/UserChartClient.tsx @@ -4,7 +4,7 @@ import { EntitySettings } from '@framework/Navigator' import * as AppContext from '@framework/AppContext' import * as Navigator from '@framework/Navigator' import * as Finder from '@framework/Finder' -import { Entity, Lite, liteKey } from '@framework/Signum.Entities' +import { Entity, getToString, Lite, liteKey } from '@framework/Signum.Entities' import * as QuickLinks from '@framework/QuickLinks' import * as AuthClient from '../../Authorization/AuthClient' import { UserChartEntity, ChartPermission, ChartMessage, ChartRequestModel, ChartParameterEmbedded, ChartColumnEmbedded } from '../Signum.Entities.Chart' @@ -39,7 +39,7 @@ export function start(options: { routes: JSX.Element[] }) { API.forEntityType(ctx.lite.EntityType); return promise.then(uqs => - uqs.map(uc => new QuickLinks.QuickLinkAction(liteKey(uc), () => uc.toStr ?? "", e => { + uqs.map(uc => new QuickLinks.QuickLinkAction(liteKey(uc), () => getToString(uc) ?? "", e => { window.open(AppContext.toAbsoluteUrl(`~/userChart/${uc.id}/${liteKey(ctx.lite)}`)); }, { icon: "chart-bar", iconColor: "darkviolet" }))); }); diff --git a/Signum.React.Extensions/Chart/UserChart/UserChartMenu.tsx b/Signum.React.Extensions/Chart/UserChart/UserChartMenu.tsx index 1e40323957..c08d25237b 100644 --- a/Signum.React.Extensions/Chart/UserChart/UserChartMenu.tsx +++ b/Signum.React.Extensions/Chart/UserChart/UserChartMenu.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { Dropdown } from 'react-bootstrap' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { classes } from '@framework/Globals' -import { Lite, toLite, newMListElement, SearchMessage, MList } from '@framework/Signum.Entities' +import { Lite, toLite, newMListElement, SearchMessage, MList, getToString } from '@framework/Signum.Entities' import { is } from '@framework/Signum.Entities' import * as Finder from '@framework/Finder' import * as Navigator from '@framework/Navigator' @@ -41,13 +41,13 @@ export default function UserChartMenu(p: UserChartMenuProps) { setUserCharts(list); const userChart = p.chartRequestView.userChart; - if (userChart && userChart.toStr == null) { + if (userChart && userChart.model == null) { const similar = list.singleOrNull(a => is(a, userChart)); if (similar) { - userChart.toStr = similar.toStr; + userChart.model = similar.model; forceUpdate(); } else { - Navigator.API.fillToStrings(userChart) + Navigator.API.fillLiteModels(userChart) .then(() => forceUpdate()) .done(); } @@ -162,7 +162,7 @@ export default function UserChartMenu(p: UserChartMenuProps) { } const crView = p.chartRequestView; - const labelText = !crView.userChart ? UserChartEntity.nicePluralName() : crView.userChart.toStr + const labelText = !crView.userChart ? UserChartEntity.nicePluralName() : getToString(crView.userChart) var canSave = tryGetOperationInfo(UserChartOperation.Save, UserChartEntity) != null; @@ -187,12 +187,12 @@ export default function UserChartMenu(p: UserChartMenuProps) { }
{userCharts?.map((uc, i) => { - if (filter == undefined || uc.toStr?.search(new RegExp(RegExp.escape(filter), "i")) != -1) + if (filter == undefined || getToString(uc)?.search(new RegExp(RegExp.escape(filter), "i")) != -1) return ( handleSelect(uc)}> - {uc.toStr} + {getToString(uc)} ) })}
diff --git a/Signum.React.Extensions/Chart/UserChart/UserChartPage.tsx b/Signum.React.Extensions/Chart/UserChart/UserChartPage.tsx index b0869ba71d..41b1e2122b 100644 --- a/Signum.React.Extensions/Chart/UserChart/UserChartPage.tsx +++ b/Signum.React.Extensions/Chart/UserChart/UserChartPage.tsx @@ -20,7 +20,7 @@ export default function UserChartPage(p : UserChartPageProps){ const lite = entity == undefined ? undefined : parseLite(entity); - Navigator.API.fillToStrings(lite) + Navigator.API.fillLiteModels(lite) .then(() => Navigator.API.fetchEntity(UserChartEntity, userChartId)) .then(uc => UserChartClient.Converter.toChartRequest(uc, lite) .then(cr => ChartClient.Encoder.chartPathPromise(cr, toLite(uc)))) diff --git a/Signum.React.Extensions/ConcurrentUser/ConcurrentUser.tsx b/Signum.React.Extensions/ConcurrentUser/ConcurrentUser.tsx index acd2242e60..b0cd3c5a1a 100644 --- a/Signum.React.Extensions/ConcurrentUser/ConcurrentUser.tsx +++ b/Signum.React.Extensions/ConcurrentUser/ConcurrentUser.tsx @@ -4,7 +4,7 @@ import * as AppContext from '@framework/AppContext' import { useSignalRCallback, useSignalRConnection, useSignalRGroup } from '../Alerts/useSignalR' import { ConcurrentUserEntity, ConcurrentUserMessage } from './Signum.Entities.ConcurrentUser' import { OverlayTrigger, Popover } from 'react-bootstrap'; -import { Entity, Lite, liteKey, toLite } from '@framework/Signum.Entities' +import { Entity, getToString, Lite, liteKey, toLite } from '@framework/Signum.Entities' import { UserEntity } from '../Authorization/Signum.Entities.Authorization' import { useAPI, useForceUpdate, useUpdatedRef } from '../../Signum.React/Scripts/Hooks' import { GraphExplorer } from '@framework/Reflection' @@ -83,7 +83,7 @@ export default function ConcurrentUser(p: { entity: Entity, onReload: ()=> void style: "warning", message:
-

{ConcurrentUserMessage.LooksLikeSomeoneJustSaved0ToTheDatabase.niceToString().formatHtml({p.entity.toStr})}

+

{ConcurrentUserMessage.LooksLikeSomeoneJustSaved0ToTheDatabase.niceToString().formatHtml({getToString(p.entity)})}

{ConcurrentUserMessage.DoYouWantToReloadIt.niceToString()}

{isModified.current && <> @@ -91,7 +91,7 @@ export default function ConcurrentUser(p: { entity: Entity, onReload: ()=> void {ConcurrentUserMessage.WarningYouWillLostYourCurrentChanges.niceToString()}

- {ConcurrentUserMessage.ConsiderOpening0InANewTabAndApplyYourChangesManually.niceToString().formatHtml({p.entity.toStr})} + {ConcurrentUserMessage.ConsiderOpening0InANewTabAndApplyYourChangesManually.niceToString().formatHtml({getToString(p.entity)})}

} @@ -121,7 +121,7 @@ export default function ConcurrentUser(p: { entity: Entity, onReload: ()=> void {otherUsers?.map((a, i) =>
- {a.user.toStr} ({DateTime.fromISO(a.startTime).toRelative()}) + {getToString(a.user)} ({DateTime.fromISO(a.startTime).toRelative()}) {a.isModified && }
)} @@ -129,22 +129,22 @@ export default function ConcurrentUser(p: { entity: Entity, onReload: ()=> void (ticks !== p.entity.ticks ?
- {ConcurrentUserMessage.YouHaveLocalChangesBut0HasAlreadyBeenSavedInTheDatabaseYouWillNotBeAbleToSaveChanges.niceToString().formatHtml({p.entity.toStr})} - {ConcurrentUserMessage.ConsiderOpening0InANewTabAndApplyYourChangesManually.niceToString().formatHtml({p.entity.toStr})} + {ConcurrentUserMessage.YouHaveLocalChangesBut0HasAlreadyBeenSavedInTheDatabaseYouWillNotBeAbleToSaveChanges.niceToString().formatHtml({getToString(p.entity)})} + {ConcurrentUserMessage.ConsiderOpening0InANewTabAndApplyYourChangesManually.niceToString().formatHtml({getToString(p.entity)})}
: otherUsers.some(u => u.isModified) && isModified.current ?
- {ConcurrentUserMessage.LooksLikeYouAreNotTheOnlyOneCurrentlyModifiying0OnlyTheFirstOneWillBeAbleToSaveChanges.niceToString().formatHtml({p.entity.toStr})} + {ConcurrentUserMessage.LooksLikeYouAreNotTheOnlyOneCurrentlyModifiying0OnlyTheFirstOneWillBeAbleToSaveChanges.niceToString().formatHtml({getToString(p.entity)})}
:
- {ConcurrentUserMessage.YouHaveLocalChangesIn0ThatIsCurrentlyOpenByOtherUsersSoFarNoOneElseHasMadeModifications.niceToString().formatHtml({p.entity.toStr})} + {ConcurrentUserMessage.YouHaveLocalChangesIn0ThatIsCurrentlyOpenByOtherUsersSoFarNoOneElseHasMadeModifications.niceToString().formatHtml({getToString(p.entity)})}
) : ticks !== p.entity.ticks ?
- {ConcurrentUserMessage.ThisIsNotTheLatestVersionOf0.niceToString().formatHtml({p.entity.toStr})} + {ConcurrentUserMessage.ThisIsNotTheLatestVersionOf0.niceToString().formatHtml({getToString(p.entity)})}
: null @@ -174,13 +174,13 @@ export namespace Options { } export function getUserInitials(u: Lite): string { - return u.toStr?.split(" ").map(m => m[0]).filter((a, i) => i < 2).join("").toUpperCase() ?? ""; + return getToString(u)?.split(" ").map(m => m[0]).filter((a, i) => i < 2).join("").toUpperCase() ?? ""; } export function UserCircle(p: { user: Lite, className?: string }) { var color = Options.getUserColor(p.user); return ( - + {getUserInitials(p.user)} ); diff --git a/Signum.React.Extensions/Dashboard/Admin/Dashboard.tsx b/Signum.React.Extensions/Dashboard/Admin/Dashboard.tsx index 6bc6c06d81..6e8006bf74 100644 --- a/Signum.React.Extensions/Dashboard/Admin/Dashboard.tsx +++ b/Signum.React.Extensions/Dashboard/Admin/Dashboard.tsx @@ -32,7 +32,7 @@ export default function Dashboard(p: { ctx: TypeContext }) { var allQueryNames = p.ctx.value.parts.flatMap(a => DashboardClient.getQueryNames(a.element.content)) .distinctBy(a => a.id!.toString()) - .orderBy(a => a.toStr) + .orderBy(a => getToString(a)) .map(a => toLite(a)); diff --git a/Signum.React.Extensions/Dashboard/CachedQueryExecutor.ts b/Signum.React.Extensions/Dashboard/CachedQueryExecutor.ts index 1b5bdfc875..03afaf6d96 100644 --- a/Signum.React.Extensions/Dashboard/CachedQueryExecutor.ts +++ b/Signum.React.Extensions/Dashboard/CachedQueryExecutor.ts @@ -1,6 +1,6 @@ import * as Finder from '@framework/Finder' import { ColumnRequest, FilterOperation, FilterOptionParsed, FilterRequest, FindOptionsParsed, isFilterGroupOptionParsed, isFilterGroupRequest, OrderRequest, Pagination, QueryRequest, QueryToken, QueryValueRequest, ResultRow, ResultTable } from '@framework/FindOptions' -import { Entity, is, Lite } from '@framework/Signum.Entities'; +import { Entity, getToString, is, Lite } from '@framework/Signum.Entities'; import { useFetchAll } from '../../Signum.React/Scripts/Navigator'; import { ignoreErrors } from '../../Signum.React/Scripts/QuickLinks'; import * as ChartClient from '../Chart/ChartClient' @@ -339,12 +339,12 @@ function orderRows(rt: ResultTable, orders: OrderRequest[], parseTokens: { [toke if (o.orderType == "Ascending") { if (pt.filterType == "Lite") - newRows.sort((ra, rb) => { const a = ra.columns[index]; const b = rb.columns[index]; return a == b ? 0 : a == null ? -1 : b == null ? 1 : a.toStr > b.toStr ? 1 : -1 }); + newRows.sort((ra, rb) => { const a = ra.columns[index]; const b = rb.columns[index]; return a == b ? 0 : a == null ? -1 : b == null ? 1 : getToString(a) > getToString(b) ? 1 : -1 }); else newRows.sort((ra, rb) => { const a = ra.columns[index]; const b = rb.columns[index]; return a == b ? 0 : a == null ? -1 : b == null ? 1 : a > b ? 1 : -1 }); } else { if (pt.filterType == "Lite") - newRows.sort((ra, rb) => { const a = ra.columns[index]; const b = rb.columns[index]; return a == b ? 0 : a == null ? 1 : b == null ? -1 : a.toStr > b.toStr ? -1 : 1 }); + newRows.sort((ra, rb) => { const a = ra.columns[index]; const b = rb.columns[index]; return a == b ? 0 : a == null ? 1 : b == null ? -1 : getToString(a) > getToString(b) ? -1 : 1 }); else newRows.sort((ra, rb) => { const a = ra.columns[index]; const b = rb.columns[index]; return a == b ? 0 : a == null ? 1 : b == null ? -1 : a > b ? -1 : 1 }); } diff --git a/Signum.React.Extensions/Dashboard/DashboardClient.tsx b/Signum.React.Extensions/Dashboard/DashboardClient.tsx index 008db700c5..9081f3a6c9 100644 --- a/Signum.React.Extensions/Dashboard/DashboardClient.tsx +++ b/Signum.React.Extensions/Dashboard/DashboardClient.tsx @@ -269,7 +269,7 @@ export function start(options: { routes: JSX.Element[] }) { API.forEntityType(ctx.lite.EntityType); return promise.then(das => - das.map(d => new QuickLinks.QuickLinkAction(liteKey(d), () => d.toStr ?? "", e => { + das.map(d => new QuickLinks.QuickLinkAction(liteKey(d), () => getToString(d) ?? "", e => { AppContext.pushOrOpenInTab(dashboardUrl(d, ctx.lite), e) }, { icon: "tachometer-alt", iconColor: "darkslateblue" }))); }); diff --git a/Signum.React.Extensions/DiffLog/DiffLogClient.tsx b/Signum.React.Extensions/DiffLog/DiffLogClient.tsx index 89947484a2..85556831e5 100644 --- a/Signum.React.Extensions/DiffLog/DiffLogClient.tsx +++ b/Signum.React.Extensions/DiffLog/DiffLogClient.tsx @@ -5,7 +5,7 @@ import { EntitySettings } from '@framework/Navigator' import * as AppContext from '@framework/AppContext' import * as Navigator from '@framework/Navigator' import * as Finder from '@framework/Finder' -import { Lite, Entity } from '@framework/Signum.Entities' +import { Lite, Entity, getToString } from '@framework/Signum.Entities' import { OperationLogEntity } from '@framework/Signum.Entities.Basics' import * as QuickLinks from '@framework/QuickLinks' import { TimeMachineMessage, TimeMachinePermission } from './Signum.Entities.DiffLog'; @@ -147,17 +147,17 @@ export default function TimeMachineLink(p : TimeMachineLinkProps){ const { lite, inSearch, children, ...htmlAtts } = p; if (!Navigator.isViewable(lite.EntityType, { isSearch: p.inSearch })) - return {p.children ?? lite.toStr}; + return {p.children ?? getToString(lite)}; return ( )}> - {children ?? lite.toStr} + {children ?? getToString(lite)} ); } diff --git a/Signum.React.Extensions/DiffLog/Templates/TimeMachinePage.tsx b/Signum.React.Extensions/DiffLog/Templates/TimeMachinePage.tsx index d7b2083207..7aece56516 100644 --- a/Signum.React.Extensions/DiffLog/Templates/TimeMachinePage.tsx +++ b/Signum.React.Extensions/DiffLog/Templates/TimeMachinePage.tsx @@ -6,7 +6,7 @@ import * as Finder from '@framework/Finder' import EntityLink from '@framework/SearchControl/EntityLink' import { SearchControl, ColumnOption } from '@framework/Search' import { QueryDescription, ResultTable } from '@framework/FindOptions' -import { Entity, JavascriptMessage } from '@framework/Signum.Entities' +import { Entity, getToString, JavascriptMessage } from '@framework/Signum.Entities' import * as Navigator from '@framework/Navigator' import { TimeMachineMessage } from '../Signum.Entities.DiffLog' import { Lite } from '@framework/Signum.Entities' @@ -28,9 +28,9 @@ export default function TimeMachinePage(p: RouteComponentProps<{ type: string; i const lite = useAPI(() => { var lite = newLite(params.type, params.id!); - return Navigator.API.fillToStrings(lite) + return Navigator.API.fillLiteModels(lite) .then(() => lite) - .catch(() => { lite.toStr = TimeMachineMessage.EntityDeleted.niceToString(); return lite }); + .catch(() => { lite.model = TimeMachineMessage.EntityDeleted.niceToString(); lite.ModelType = undefined; return lite }); }, []) const searchControl = React.useRef(null); @@ -50,7 +50,7 @@ export default function TimeMachinePage(p: RouteComponentProps<{ type: string; i
{"{0} {1}".formatWith(getTypeInfo(lite.EntityType).niceName, lite.id)} -  {lite.toStr} +  {getToString(lite)} diff --git a/Signum.React.Extensions/Dynamic/DynamicViewClient.tsx b/Signum.React.Extensions/Dynamic/DynamicViewClient.tsx index b404512652..63eab12eb0 100644 --- a/Signum.React.Extensions/Dynamic/DynamicViewClient.tsx +++ b/Signum.React.Extensions/Dynamic/DynamicViewClient.tsx @@ -10,7 +10,7 @@ import { EntityOperationSettings } from '@framework/Operations' import * as Operations from '@framework/Operations' import { TypeContext } from '@framework/TypeContext' import { isTypeEntity, getTypeInfo, PropertyRoute } from '@framework/Reflection' -import { Entity, ModifiableEntity } from '@framework/Signum.Entities' +import { Entity, getToString, ModifiableEntity } from '@framework/Signum.Entities' import SelectorModal from '@framework/SelectorModal' import { ViewReplacer } from '@framework/Frames/ReactVisitor'; import * as Lines from '@framework/Lines' @@ -318,7 +318,7 @@ export function asSelectorFunction(dvs: DynamicViewSelectorEntity): (e: Entity) try { return evalWithScope(code, globalModules); } catch (e) { - throw new Error("Syntax in DynamicViewSelector for '" + dvs.entityType!.toStr + "':\r\n" + code + "\r\n" + (e as Error).message); + throw new Error("Syntax in DynamicViewSelector for '" + getToString(dvs.entityType) + "':\r\n" + code + "\r\n" + (e as Error).message); } } @@ -386,7 +386,7 @@ export function asOverrideFunction(dvo: DynamicViewOverrideEntity): (vr: ViewRep try { return eval(code); } catch (e) { - throw new Error("Syntax in DynamicViewOverride for '" + dvo.entityType!.toStr + "':\r\n" + code + "\r\n" + (e as Error).message); + throw new Error("Syntax in DynamicViewOverride for '" + getToString(dvo.entityType) + "':\r\n" + code + "\r\n" + (e as Error).message); } } diff --git a/Signum.React.Extensions/Dynamic/Type/DynamicMixinConnection.tsx b/Signum.React.Extensions/Dynamic/Type/DynamicMixinConnection.tsx index 66ef880347..d0afe2a61a 100644 --- a/Signum.React.Extensions/Dynamic/Type/DynamicMixinConnection.tsx +++ b/Signum.React.Extensions/Dynamic/Type/DynamicMixinConnection.tsx @@ -5,7 +5,7 @@ import * as Finder from '@framework/Finder' import * as Navigator from '@framework/Navigator' import { DynamicTypeEntity, DynamicTypeMessage, DynamicMixinConnectionEntity } from '../Signum.Entities.Dynamic' import { ValueLine, EntityLine, TypeContext, LiteAutocompleteConfig } from '@framework/Lines' -import { ModifiableEntity, Entity, Lite, JavascriptMessage } from '@framework/Signum.Entities' +import { ModifiableEntity, Entity, Lite, JavascriptMessage, getToString } from '@framework/Signum.Entities' import { getTypeInfo, Binding, PropertyRoute, symbolNiceName, getQueryNiceName } from '@framework/Reflection' import * as DynamicTypeClient from '../DynamicTypeClient' import { Typeahead } from '@framework/Components'; @@ -49,7 +49,7 @@ export function MixinCombo(p : MixinComboProps){ ], orderOptions: [], count: 5 - }).then(lites => lites?.map(a => a.toStr)); + }).then(lites => lites?.map(a => getToString(a))); } function handleOnChange(newValue: string) { diff --git a/Signum.React.Extensions/Dynamic/View/FindOptionsComponent.tsx b/Signum.React.Extensions/Dynamic/View/FindOptionsComponent.tsx index 7e033013c0..3fadb5bfc0 100644 --- a/Signum.React.Extensions/Dynamic/View/FindOptionsComponent.tsx +++ b/Signum.React.Extensions/Dynamic/View/FindOptionsComponent.tsx @@ -9,7 +9,7 @@ import * as Navigator from '@framework/Navigator' import { TypeContext, FormGroupStyle } from '@framework/TypeContext' import { Typeahead } from '@framework/Components' import QueryTokenBuilder from '@framework/SearchControl/QueryTokenBuilder' -import { ModifiableEntity, JavascriptMessage, EntityControlMessage } from '@framework/Signum.Entities' +import { ModifiableEntity, JavascriptMessage, EntityControlMessage, getToString } from '@framework/Signum.Entities' import { QueryEntity } from '@framework/Signum.Entities.Basics' import { FilterOperation, PaginationMode } from '@framework/Signum.Entities.DynamicQuery' import { ExpressionOrValueComponent, FieldComponent, DesignerModal } from './Designer' @@ -273,7 +273,7 @@ export function FindOptionsComponent(p : FindOptionsComponentProps){ export function QueryKeyLine(p : { queryKey: string | undefined, label: string; onChange: (queryKey: string | undefined) => void }){ function handleGetItems(query: string) { return Finder.API.findLiteLike({ types: QueryEntity.typeName, subString: query, count: 5 }) - .then(lites => lites.map(a => a.toStr)); + .then(lites => lites.map(a => getToString(a))); } diff --git a/Signum.React.Extensions/Excel/ExcelMenu.tsx b/Signum.React.Extensions/Excel/ExcelMenu.tsx index 8ef15512f4..3eab72ebe7 100644 --- a/Signum.React.Extensions/Excel/ExcelMenu.tsx +++ b/Signum.React.Extensions/Excel/ExcelMenu.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import * as Finder from '@framework/Finder' -import { Lite, PaginationMessage, SearchMessage, SelectorMessage } from '@framework/Signum.Entities' +import { getToString, Lite, PaginationMessage, SearchMessage, SelectorMessage } from '@framework/Signum.Entities' import * as Navigator from '@framework/Navigator' import SearchControlLoaded from '@framework/SearchControl/SearchControlLoaded' import { ExcelReportEntity, ExcelMessage, ExcelReportOperation } from './Signum.Entities.Excel' @@ -104,7 +104,7 @@ export default function ExcelMenu(p: ExcelMenuProps) { excelReports?.map((uq, i) => handleClick(uq)}> - {uq.toStr} + {getToString(uq)} ) } {(p.plainExcel || excelReports && excelReports.length > 0) && } diff --git a/Signum.React.Extensions/Files/FileDownloader.tsx b/Signum.React.Extensions/Files/FileDownloader.tsx index bce74a9db4..16b1d3dc48 100644 --- a/Signum.React.Extensions/Files/FileDownloader.tsx +++ b/Signum.React.Extensions/Files/FileDownloader.tsx @@ -111,17 +111,19 @@ export function registerConfiguration(type: export interface FileDownloaderConfiguration { fileUrl?: (file: T) => string; + fileLiteUrl?: (file: Lite) => string; downloadClick?: (event: React.MouseEvent, file: T) => void; viewClick?: (event: React.MouseEvent, file: T) => void; } registerConfiguration(FileEntity, { fileUrl: file => AppContext.toAbsoluteUrl("~/api/files/downloadFile/" + file.id), - viewClick: (event, file) => viewUrl(event, AppContext.toAbsoluteUrl("~/api/files/downloadFile/" + file.id)) + fileLiteUrl: file => AppContext.toAbsoluteUrl("~/api/files/downloadFile/" + file.id), }); registerConfiguration(FilePathEntity, { fileUrl: file => AppContext.toAbsoluteUrl("~/api/files/downloadFilePath/" + file.id), + fileLiteUrl: file => AppContext.toAbsoluteUrl("~/api/files/downloadFilePath/" + file.id), }); registerConfiguration(FileEmbedded, { diff --git a/Signum.React.Extensions/Files/FileImage.tsx b/Signum.React.Extensions/Files/FileImage.tsx index 44afa2bfae..85f8fd06a7 100644 --- a/Signum.React.Extensions/Files/FileImage.tsx +++ b/Signum.React.Extensions/Files/FileImage.tsx @@ -1,12 +1,13 @@ import * as React from 'react' import { IFile, IFilePath } from "./Signum.Entities.Files"; import { configurtions } from "./FileDownloader"; -import { ModifiableEntity } from '@framework/Signum.Entities'; +import { Entity, isLite, isModifiableEntity, Lite, ModifiableEntity } from '@framework/Signum.Entities'; import * as Services from '@framework/Services' import { PropertyRoute } from '@framework/Lines'; +import { useFetchInState } from '../../Signum.React/Scripts/Navigator'; interface FileImageProps extends React.ImgHTMLAttributes { - file?: IFile & ModifiableEntity | null; + file?: IFile & ModifiableEntity | Lite | null; } export function FileImage(p: FileImageProps) { @@ -15,19 +16,30 @@ export function FileImage(p: FileImageProps) { var { file, ...rest } = p; React.useEffect(() => { - if (file && !file.fullWebPath && !file.binaryFile) { - var url = configurtions[file.Type].fileUrl!(file); + if (file) { + + if (isModifiableEntity(file) && (file.fullWebPath || file.binaryFile)) + return; + + var url = + isLite(file) ? + configurtions[file.EntityType].fileLiteUrl!(file) : + configurtions[file.Type].fileUrl!(file); Services.ajaxGetRaw({ url: url }) .then(resp => resp.blob()) .then(blob => setObjectUrl(URL.createObjectURL(blob))) .done(); + } return () => { objectUrl && URL.revokeObjectURL(objectUrl) }; }, [p.file]); var src = file == null ? undefined : - (file as IFilePath).fullWebPath || (file.binaryFile != null ? "data:image/jpeg;base64," + file.binaryFile : objectUrl); + isModifiableEntity(file) && file.fullWebPath ? file.fullWebPath : + isModifiableEntity(file) && file.binaryFile ? "data:image/jpeg;base64," + file.binaryFile : + objectUrl; + return ( ); diff --git a/Signum.React.Extensions/Files/FileLine.tsx b/Signum.React.Extensions/Files/FileLine.tsx index 7714084f4b..dcd1bc909f 100644 --- a/Signum.React.Extensions/Files/FileLine.tsx +++ b/Signum.React.Extensions/Files/FileLine.tsx @@ -3,7 +3,7 @@ import { classes } from '@framework/Globals' import { TypeContext } from '@framework/TypeContext' import { getSymbol } from '@framework/Reflection' import { FormGroup } from '@framework/Lines/FormGroup' -import { ModifiableEntity, Lite, Entity, } from '@framework/Signum.Entities' +import { ModifiableEntity, Lite, Entity, getToString, } from '@framework/Signum.Entities' import { IFile, FileTypeSymbol } from './Signum.Entities.Files' import { EntityBaseProps, EntityBaseController } from '@framework/Lines/EntityBase' import { FileDownloader, FileDownloaderConfiguration, DownloadBehaviour } from './FileDownloader' @@ -94,7 +94,7 @@ export const FileLine = React.memo(React.forwardRef(function FileLine(props: Fil const val = ctx.value!; const content = p.download == "None" ? - {val.toStr} : + {getToString(val)} :
{p.getComponent ? p.getComponent(mlec) : - p.download == "None" ? {mlec.value.toStr} : + p.download == "None" ? {getToString(mlec.value)} : renderFile(p.getFile ? (mlec as TypeContext).subCtx(p.getFile) : mlec as TypeContext | undefined | null>)} {!p.ctx.readOnly && - {p.getFileFromElement ? p.getFileFromElement(mlec.value).toStr : mlec.value.toStr} + {getToString(p.getFileFromElement ? p.getFileFromElement(mlec.value) : mlec.value)} : | undefined) { +export namespace Options { + export let onIsolationChange: ((e: React.MouseEvent, isolation: Lite | undefined) => boolean) | null = null; +} + +export function changeOverridenIsolation(e: React.MouseEvent, isolation: Lite | undefined) { + + + if (Options.onIsolationChange && Options.onIsolationChange(e, isolation)) + return; - if (isolation) - sessionStorage.setItem('Curr_Isolation', JSON.stringify(isolation)); - else - sessionStorage.removeItem('Curr_Isolation'); + if (isolation) + sessionStorage.setItem('Curr_Isolation', JSON.stringify(isolation)); + else + sessionStorage.removeItem('Curr_Isolation'); - AppContext.resetUI(); + AppContext.resetUI(); } export function getOverridenIsolation(): Lite | undefined { diff --git a/Signum.React.Extensions/Isolation/IsolationDropdown.tsx b/Signum.React.Extensions/Isolation/IsolationDropdown.tsx index af9aa3794f..c14bfe03ab 100644 --- a/Signum.React.Extensions/Isolation/IsolationDropdown.tsx +++ b/Signum.React.Extensions/Isolation/IsolationDropdown.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import { Lite, is } from '@framework/Signum.Entities' +import { Lite, is, getToString } from '@framework/Signum.Entities' import { NavDropdown } from 'react-bootstrap' import { useAPI } from '@framework/Hooks'; import { IsolationEntity, IsolationMessage } from './Signum.Entities.Isolation'; @@ -10,8 +10,8 @@ import * as IsolationClient from './IsolationClient'; export default function IsolationDropdown(props: {}) { var isolations = useAPI(signal => IsolationClient.API.isolations(), []); - function handleSelect(c: Lite | undefined) { - IsolationClient.changeOverridenIsolation(c); + function handleSelect(e: React.MouseEvent, c: Lite | undefined) { + IsolationClient.changeOverridenIsolation(e, c); } if (!isolations) @@ -20,14 +20,14 @@ export default function IsolationDropdown(props: {}) { const current = IsolationClient.getOverridenIsolation(); return ( - - handleSelect(undefined)}> + + handleSelect(e, undefined)}> {IsolationMessage.GlobalMode.niceToString()} {isolations.map((iso, i) => - handleSelect(iso)}> - {iso.toStr} + handleSelect(e, iso)}> + {getToString(iso)} )} diff --git a/Signum.React.Extensions/Isolation/IsolationFilter.cs b/Signum.React.Extensions/Isolation/IsolationFilter.cs index 76b943e0df..698a613af1 100644 --- a/Signum.React.Extensions/Isolation/IsolationFilter.cs +++ b/Signum.React.Extensions/Isolation/IsolationFilter.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; using Signum.Entities.Authorization; using Signum.Entities.Basics; @@ -10,6 +11,8 @@ public class IsolationFilter : SignumDisposableResourceFilter { public const string Signum_Isolation_Key = "Signum_Isolation"; + public static Func?> GetIsolationFromHttpContext; + public override IDisposable? GetResource(ResourceExecutingContext context) { var user = UserHolder.Current; @@ -26,6 +29,12 @@ public class IsolationFilter : SignumDisposableResourceFilter isolation = Lite.Parse(isolationKey); } + if(isolation == null) + { + if (GetIsolationFromHttpContext != null) + isolation = GetIsolationFromHttpContext(context.HttpContext); + } + context.HttpContext.Items[Signum_Isolation_Key] = isolation; return IsolationEntity.Override(isolation); diff --git a/Signum.React.Extensions/Isolation/IsolationWidget.tsx b/Signum.React.Extensions/Isolation/IsolationWidget.tsx index 661493e5f0..f4a874c877 100644 --- a/Signum.React.Extensions/Isolation/IsolationWidget.tsx +++ b/Signum.React.Extensions/Isolation/IsolationWidget.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import { ModifiableEntity, tryGetMixin } from '@framework/Signum.Entities' +import { getToString, ModifiableEntity, tryGetMixin } from '@framework/Signum.Entities' import { IsolationMessage, IsolationMixin } from './Signum.Entities.Isolation'; import * as IsolationClient from './IsolationClient'; import { WidgetContext } from '@framework/Frames/Widgets'; @@ -18,10 +18,9 @@ export function IsolationWidget(p: IsolationWidgetProps) { if (mixin == null) return null; - const isolation = entity.isNew ? IsolationClient.getOverridenIsolation()?.toStr ?? IsolationMessage.GlobalEntity.niceToString() : - mixin.isolation?.toStr ?? IsolationMessage.GlobalEntity.niceToString(); + const isolation = entity.isNew ? IsolationClient.getOverridenIsolation() : mixin.isolation; return ( - {isolation} + {isolation == null ? IsolationMessage.GlobalEntity.niceToString() : getToString(isolation)} ); } diff --git a/Signum.React.Extensions/MachineLearning/Templates/PredictModal.tsx b/Signum.React.Extensions/MachineLearning/Templates/PredictModal.tsx index 9abec20d4a..feafe35884 100644 --- a/Signum.React.Extensions/MachineLearning/Templates/PredictModal.tsx +++ b/Signum.React.Extensions/MachineLearning/Templates/PredictModal.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import * as Navigator from "@framework/Navigator"; import { IModalProps, openModal } from "@framework/Modals"; import { API, PredictRequest, PredictOutputTuple, PredictSubQueryTable, AlternativePrediction } from "../PredictorClient"; -import { Lite, Entity, EntityControlMessage } from "@framework/Signum.Entities"; +import { Lite, Entity, EntityControlMessage, getToString } from "@framework/Signum.Entities"; import { StyleContext, FormGroup, TypeContext, EntityLine, EntityCombo, ValueLine } from "@framework/Lines"; import { QueryToken } from "@framework/FindOptions"; import { tryGetTypeInfos, ReadonlyBinding, getTypeInfo, getTypeInfos, toNumberFormatOptions, toNumberFormat } from "@framework/Reflection"; @@ -57,9 +57,9 @@ export function PredictModal(p: PredictModalProps) { return ( - {predict.predictor.toStr}
+ {getToString(predict.predictor)}
{PredictorEntity.niceName()} {predict.predictor.id} (
{EntityControlMessage.View.niceToString()}) - {e &&
{getTypeInfo(e.EntityType).niceName}: {e.toStr}
} + {e &&
{getTypeInfo(e.EntityType).niceName}: {getToString(e)}
}
@@ -179,7 +179,7 @@ export function PredictTable(p : PredictTableProps){ var sctx = new StyleContext(p.sctx, { formGroupStyle: "SrOnly" }); return (
-

{subQuery.toStr}

+

{getToString(subQuery)}

diff --git a/Signum.React.Extensions/Mailing/MailingClient.tsx b/Signum.React.Extensions/Mailing/MailingClient.tsx index f3c3685395..0238222d6f 100644 --- a/Signum.React.Extensions/Mailing/MailingClient.tsx +++ b/Signum.React.Extensions/Mailing/MailingClient.tsx @@ -5,7 +5,7 @@ import { EntitySettings } from '@framework/Navigator' import * as Navigator from '@framework/Navigator' import * as Constructor from '@framework/Constructor' import * as Finder from '@framework/Finder' -import { Lite, Entity, registerToString, JavascriptMessage } from '@framework/Signum.Entities' +import { Lite, Entity, registerToString, JavascriptMessage, getToString } from '@framework/Signum.Entities' import { EntityOperationSettings } from '@framework/Operations' import { PseudoType, Type, getTypeName, isTypeEntity } from '@framework/Reflection' import * as Operations from '@framework/Operations' @@ -158,10 +158,10 @@ export function getEmailTemplates(ctx: ContextualItemsContext): Promise< return { header: EmailTemplateEntity.nicePluralName(), - menuItems: wts.map(wt => - handleMenuClick(wt, ctx)}> + menuItems: wts.map(et => + handleMenuClick(et, ctx)}> - {wt.toStr} + {getToString(et)} ) } as MenuItemBlock; diff --git a/Signum.React.Extensions/Mailing/MailingMenu.tsx b/Signum.React.Extensions/Mailing/MailingMenu.tsx index 07f6956a35..91061f5fc6 100644 --- a/Signum.React.Extensions/Mailing/MailingMenu.tsx +++ b/Signum.React.Extensions/Mailing/MailingMenu.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { Lite } from '@framework/Signum.Entities' +import { getToString, Lite } from '@framework/Signum.Entities' import * as Navigator from '@framework/Navigator' import SearchControlLoaded from '@framework/SearchControl/SearchControlLoaded' import { EmailTemplateEntity, EmailMessageEntity } from './Signum.Entities.Mailing' @@ -43,7 +43,7 @@ export default function MailingMenu(p : MailingMenuProps){ emailTemplates.map((wt, i) => handleClick(wt)}> - {wt.toStr} + {getToString(wt)} ) } diff --git a/Signum.React.Extensions/Omnibox/EntityOmniboxProvider.tsx b/Signum.React.Extensions/Omnibox/EntityOmniboxProvider.tsx index 15e2cf1ffd..802fa183a0 100644 --- a/Signum.React.Extensions/Omnibox/EntityOmniboxProvider.tsx +++ b/Signum.React.Extensions/Omnibox/EntityOmniboxProvider.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Lite, Entity } from '@framework/Signum.Entities' +import { Lite, Entity, getToString } from '@framework/Signum.Entities' import { OmniboxMessage } from './Signum.Entities.Omnibox' import { OmniboxResult, OmniboxMatch, OmniboxProvider } from './OmniboxClient' import * as Navigator from '@framework/Navigator' @@ -34,7 +34,7 @@ export default class EntityOmniboxProvider extends OmniboxProvider renderStateButton(vsc, s.fileType)} findOptions={{ queryName: PrintLineEntity, diff --git a/Signum.React.Extensions/Toolbar/QueryToolbarConfig.tsx b/Signum.React.Extensions/Toolbar/QueryToolbarConfig.tsx index 957e2eca4e..15633c89de 100644 --- a/Signum.React.Extensions/Toolbar/QueryToolbarConfig.tsx +++ b/Signum.React.Extensions/Toolbar/QueryToolbarConfig.tsx @@ -1,6 +1,7 @@ import * as React from 'react' import { Location } from 'history' import { getQueryKey, getQueryNiceName } from '@framework/Reflection' +import { getToString } from '@framework/Signum.Entities' import * as Finder from '@framework/Finder' import * as AppContext from '@framework/AppContext' import { QueryEntity } from '@framework/Signum.Entities.Basics' @@ -20,7 +21,7 @@ export default class QueryToolbarConfig extends ToolbarConfig { getIcon(element: ToolbarResponse) { if (element.iconName == "count") - return ; + return ; return ToolbarConfig.coloredIcon(coalesceIcon(parseIcon(element.iconName) , ["far", "list-alt"]), element.iconColor || "dodgerblue"); } @@ -29,16 +30,16 @@ export default class QueryToolbarConfig extends ToolbarConfig { if (!res.openInPopup) super.handleNavigateClick(e, res); else { - Finder.explore({ queryName: res.content!.toStr! }).done() + Finder.explore({ queryName: getToString(res.content)! }).done() } } navigateTo(res: ToolbarResponse): Promise { - return Promise.resolve(Finder.findOptionsPath({ queryName: res.content!.toStr! })); + return Promise.resolve(Finder.findOptionsPath({ queryName: getToString(res.content)! })); } isCompatibleWithUrlPrio(res: ToolbarResponse, location: Location, query: any): number { - return location.pathname == AppContext.toAbsoluteUrl(Finder.findOptionsPath({ queryName: res.content!.toStr! })) ? 1 : 0; + return location.pathname == AppContext.toAbsoluteUrl(Finder.findOptionsPath({ queryName: getToString(res.content)! })) ? 1 : 0; } } diff --git a/Signum.React.Extensions/Toolbar/Renderers/ToolbarRenderer.tsx b/Signum.React.Extensions/Toolbar/Renderers/ToolbarRenderer.tsx index dc232e5635..dedd110eac 100644 --- a/Signum.React.Extensions/Toolbar/Renderers/ToolbarRenderer.tsx +++ b/Signum.React.Extensions/Toolbar/Renderers/ToolbarRenderer.tsx @@ -9,11 +9,10 @@ import { Nav } from 'react-bootstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { useAPI, useUpdatedRef, useHistoryListen } from '@framework/Hooks' import { QueryString } from '@framework/QueryString' +import { getToString } from '@framework/Signum.Entities' import { parseIcon } from '../../Basics/Templates/IconTypeahead' -import { SidebarMode } from '../SidebarContainer' -import { isActive } from '@framework/FindOptions'; -import { classes, Dic } from '@framework/Globals'; import { urlVariables } from '../../Dashboard/UrlVariables'; +import { Dic } from '@framework/Globals'; @@ -100,7 +99,7 @@ export function renderNavItem(res: ToolbarClient.ToolbarResponse, active: T case "Header": case "Item": if (res.elements && res.elements.length) { - var title = res.label || res.content!.toStr; + var title = res.label || getToString(res.content); var icon = ToolbarConfig.coloredIcon(parseIcon(res.iconName), res.iconColor); return ( diff --git a/Signum.React.Extensions/Toolbar/Templates/ToolbarElement.tsx b/Signum.React.Extensions/Toolbar/Templates/ToolbarElement.tsx index 20dd510750..fb9be17715 100644 --- a/Signum.React.Extensions/Toolbar/Templates/ToolbarElement.tsx +++ b/Signum.React.Extensions/Toolbar/Templates/ToolbarElement.tsx @@ -7,6 +7,7 @@ import { IconTypeaheadLine, parseIcon } from '../../Basics/Templates/IconTypeahe import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import * as Dashboard from '../../Dashboard/Admin/Dashboard' import { PermissionSymbol } from '../../Authorization/Signum.Entities.Authorization'; +import { getToString } from '@framework/Signum.Entities' import { useForceUpdate } from '@framework/Hooks' export default function ToolbarElement(p: { ctx: TypeContext }) { @@ -57,7 +58,7 @@ export default function ToolbarElement(p: { ctx: TypeContext}
- t.label)} valueHtmlAttributes={{ placeholder: content?.toStr || undefined }} /> + t.label)} valueHtmlAttributes={{ placeholder: getToString(content) || undefined }} /> {(ctx2.value.type == "Header" || ctx2.value.type == "Item") && (ctx2.value.content == null || PermissionSymbol.isLite(ctx2.value.content)) && t.url)} />} {content && (content.EntityType == "UserQuery" || content.EntityType == "Query") &&
diff --git a/Signum.React.Extensions/Translation/Code/TranslationCodeSync.tsx b/Signum.React.Extensions/Translation/Code/TranslationCodeSync.tsx index 8d0ef25b84..d7bf2962b0 100644 --- a/Signum.React.Extensions/Translation/Code/TranslationCodeSync.tsx +++ b/Signum.React.Extensions/Translation/Code/TranslationCodeSync.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { RouteComponentProps } from 'react-router' import { Dic } from '@framework/Globals' import { notifySuccess } from '@framework/Operations' -import { Lite } from '@framework/Signum.Entities' +import { getToString, Lite } from '@framework/Signum.Entities' import * as CultureClient from '../CultureClient' import { API, AssemblyResult } from '../TranslationClient' import { CultureInfoEntity } from '../../Basics/Signum.Entities.Basics' @@ -23,7 +23,7 @@ export default function TranslationCodeSync(p: RouteComponentProps<{ culture: st const [result, reloadResult] = useAPIWithReload(() => API.sync(assembly, culture, namespace), [assembly, culture, namespace]); var message = result?.totalTypes == 0 ? TranslationMessage._0AlreadySynchronized.niceToString(namespace ?? assembly) : - TranslationMessage.Synchronize0In1.niceToString(namespace ?? assembly, cultures ? cultures[culture].toStr : culture) + + TranslationMessage.Synchronize0In1.niceToString(namespace ?? assembly, cultures ? getToString(cultures[culture]) : culture) + (result ? ` [${Dic.getKeys(result.types).length}/${result.totalTypes}]` : " …"); useTitle(message); diff --git a/Signum.React.Extensions/Translation/Code/TranslationCodeView.tsx b/Signum.React.Extensions/Translation/Code/TranslationCodeView.tsx index c31d39d382..b4f893a7c5 100644 --- a/Signum.React.Extensions/Translation/Code/TranslationCodeView.tsx +++ b/Signum.React.Extensions/Translation/Code/TranslationCodeView.tsx @@ -3,7 +3,7 @@ import { Link, RouteComponentProps } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Dic } from '@framework/Globals' import { notifySuccess } from '@framework/Operations' -import { Lite } from '@framework/Signum.Entities' +import { getToString, Lite } from '@framework/Signum.Entities' import * as CultureClient from '../CultureClient' import { API, AssemblyResult } from '../TranslationClient' import { CultureInfoEntity } from '../../Basics/Signum.Entities.Basics' @@ -49,7 +49,7 @@ export default function TranslationCodeView(p: RouteComponentProps<{ culture: st const message = TranslationMessage.View0In1.niceToString(decodeDots(assembly), culture == undefined ? TranslationMessage.AllLanguages.niceToString() : - cultures ? cultures[culture].toStr : + cultures ? getToString(cultures[culture]) : culture); useTitle(message); diff --git a/Signum.React.Extensions/Translation/CultureDropdown.tsx b/Signum.React.Extensions/Translation/CultureDropdown.tsx index e94024c561..1abfd63929 100644 --- a/Signum.React.Extensions/Translation/CultureDropdown.tsx +++ b/Signum.React.Extensions/Translation/CultureDropdown.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { Dic } from '@framework/Globals'; -import { Lite, is } from '@framework/Signum.Entities' +import { Lite, is, getToString } from '@framework/Signum.Entities' import { CultureInfoEntity } from '../Basics/Signum.Entities.Basics' import * as CultureClient from './CultureClient' import { NavDropdown } from 'react-bootstrap'; @@ -24,7 +24,7 @@ export default function CultureDropdown(p: { fullName?: boolean }) { {Dic.map(cultures, (name, c, i) => handleSelect(c)}> - {p.fullName ? c.toStr : simplifyName(c.toStr!)} + {p.fullName ? getToString(c) : simplifyName(getToString(c)!)} )} @@ -59,7 +59,7 @@ export function CultureDropdownMenuItem(props: { fullName?: boolean }) {
{Dic.map(cultures, (name, c, i) => handleSelect(c)}> - {props.fullName ? c.toStr : simplifyName(c.toStr!)} + {props.fullName ? getToString(c) : simplifyName(getToString(c)!)} )}
diff --git a/Signum.React.Extensions/Translation/Instances/TranslatedInstanceSync.tsx b/Signum.React.Extensions/Translation/Instances/TranslatedInstanceSync.tsx index 499397fa5d..b2916787ed 100644 --- a/Signum.React.Extensions/Translation/Instances/TranslatedInstanceSync.tsx +++ b/Signum.React.Extensions/Translation/Instances/TranslatedInstanceSync.tsx @@ -17,7 +17,7 @@ import { getTypeInfo } from '@framework/Reflection' import { useTitle } from '@framework/AppContext' import { CultureInfoEntity } from '../../Basics/Signum.Entities.Basics' import { TranslationMember, initialElementIf } from '../Code/TranslationTypeTable' -import { Lite } from '@framework/Signum.Entities' +import { getToString, Lite } from '@framework/Signum.Entities' @@ -71,7 +71,7 @@ export default function TranslatedInstanceSync(p: RouteComponentProps<{ type: st } const message = result && result.totalInstances == 0 ? TranslationMessage._0AlreadySynchronized.niceToString(getTypeInfo(type).niceName) : - TranslationMessage.Synchronize0In1.niceToString(getTypeInfo(type).niceName, cultures ? cultures[culture].toStr : culture) + + TranslationMessage.Synchronize0In1.niceToString(getTypeInfo(type).niceName, cultures ? getToString(cultures[culture]) : culture) + (result && result.instances.length < result.totalInstances ? " [{0}/{1}]".formatWith(result.instances.length, result.totalInstances) : ""); useTitle(message); diff --git a/Signum.React.Extensions/Translation/Instances/TranslatedInstanceView.tsx b/Signum.React.Extensions/Translation/Instances/TranslatedInstanceView.tsx index 5ad734679e..06963f5260 100644 --- a/Signum.React.Extensions/Translation/Instances/TranslatedInstanceView.tsx +++ b/Signum.React.Extensions/Translation/Instances/TranslatedInstanceView.tsx @@ -15,6 +15,7 @@ import TextArea from '@framework/Components/TextArea' import { KeyCodes } from '@framework/Components' import { useTitle } from '@framework/AppContext' import { QueryString } from '@framework/QueryString' +import { getToString } from '@framework/Signum.Entities' export default function TranslationInstanceView(p: RouteComponentProps<{ type: string; culture?: string; }>) { @@ -75,7 +76,7 @@ export default function TranslationInstanceView(p: RouteComponentProps<{ type: s const message = TranslationMessage.View0In1.niceToString(type, culture == undefined ? TranslationMessage.AllLanguages.niceToString() : - cultures ? cultures[culture].toStr : + cultures ? getToString(cultures[culture]) : culture); useTitle(message); diff --git a/Signum.React.Extensions/Tree/TreeClient.tsx b/Signum.React.Extensions/Tree/TreeClient.tsx index bbb1a06086..4d44348da6 100644 --- a/Signum.React.Extensions/Tree/TreeClient.tsx +++ b/Signum.React.Extensions/Tree/TreeClient.tsx @@ -7,7 +7,7 @@ import * as Finder from '@framework/Finder' import { EntityOperationSettings } from '@framework/Operations' import * as Operations from '@framework/Operations' import { Type, tryGetTypeInfo } from '@framework/Reflection' -import { Lite } from '@framework/Signum.Entities' +import { getToString, Lite } from '@framework/Signum.Entities' import { TreeEntity, TreeOperation, MoveTreeModel, TreeMessage } from './Signum.Entities.Tree' import TreeModal from './TreeModal' import { FilterRequest, FilterOption } from "@framework/FindOptions"; @@ -72,7 +72,7 @@ function moveModal(lite: Lite) { return s.createMoveModel(lite, {}); else return Navigator.view(MoveTreeModel.New({ insertPlace: "LastNode" }), { - title: TreeMessage.Move0.niceToString(lite.toStr), + title: TreeMessage.Move0.niceToString(getToString(lite)), modalSize: "md", extraProps: { lite }, }) @@ -84,7 +84,7 @@ function copyModal(lite: Lite) { return s.createCopyModel(lite, {}); else return Navigator.view(MoveTreeModel.New({ insertPlace: "LastNode" }), { - title: TreeMessage.Copy0.niceToString(lite.toStr), + title: TreeMessage.Copy0.niceToString(getToString(lite)), modalSize: "md", extraProps: { lite }, }); diff --git a/Signum.React.Extensions/Tree/TreeViewer.tsx b/Signum.React.Extensions/Tree/TreeViewer.tsx index dfe90f558c..0964801d49 100644 --- a/Signum.React.Extensions/Tree/TreeViewer.tsx +++ b/Signum.React.Extensions/Tree/TreeViewer.tsx @@ -8,7 +8,7 @@ import * as Finder from '@framework/Finder' import ContextMenu from '@framework/SearchControl/ContextMenu' import { ContextMenuPosition } from '@framework/SearchControl/ContextMenu' import * as Operations from '@framework/Operations' -import { SearchMessage, JavascriptMessage, EntityControlMessage, toLite, liteKey } from '@framework/Signum.Entities' +import { SearchMessage, JavascriptMessage, EntityControlMessage, toLite, liteKey, getToString } from '@framework/Signum.Entities' import { TreeViewerMessage, TreeEntity, TreeOperation, MoveTreeModel } from './Signum.Entities.Tree' import * as TreeClient from './TreeClient' import { FilterOptionParsed, QueryDescription, SubTokensOptions, FilterOption } from "@framework/FindOptions"; @@ -427,7 +427,7 @@ export class TreeViewer extends React.Component - {`${JavascriptMessage.Selected.niceToString()} (${selected ? selected.lite.toStr : TreeViewerMessage.None.niceToString()})`} + {`${JavascriptMessage.Selected.niceToString()} (${selected ? getToString(selected.lite) : TreeViewerMessage.None.niceToString()})`} {menuItems == undefined ? {JavascriptMessage.loading.niceToString()} : diff --git a/Signum.React.Extensions/UserAssets/ImportAssetsPage.tsx b/Signum.React.Extensions/UserAssets/ImportAssetsPage.tsx index 49db35897d..1f3e4270ad 100644 --- a/Signum.React.Extensions/UserAssets/ImportAssetsPage.tsx +++ b/Signum.React.Extensions/UserAssets/ImportAssetsPage.tsx @@ -10,7 +10,7 @@ import { useForceUpdate } from '@framework/Hooks' import { useTitle } from '@framework/AppContext' import { ChangeEvent, EntityLine, EntityTable, PropertyRoute, ValueLine } from '@framework/Lines' import { EntityLink } from '@framework/Search' -import { is, liteKey, liteKeyLong, MList } from '@framework/Signum.Entities' +import { getToString, is, liteKey, liteKeyLong, MList } from '@framework/Signum.Entities' import SelectorModal from '../../Signum.React/Scripts/SelectorModal' import MessageModal from '../../Signum.React/Scripts/Modals/MessageModal' @@ -87,7 +87,7 @@ export default function ImportAssetsPage(p: ImportAssetsPageProps) { if (listChange.length > 1) { return MessageModal.show({ title: "", - message: UserAssetMessage.SameSelectionForAllConflictsOf0.niceToString(conflict.from.toStr), + message: UserAssetMessage.SameSelectionForAllConflictsOf0.niceToString(getToString(conflict.from)), buttons: "yes_no", }).then(result => { if (result == "yes") { @@ -147,7 +147,7 @@ export default function ImportAssetsPage(p: ImportAssetsPageProps) { ea.modified = true; } }).done(); - }}>{ea.customResolution.toStr}} + }}>{getToString(ea.customResolution)}} {ea.liteConflicts.length > 0 &&
diff --git a/Signum.React.Extensions/UserAssets/Templates/FilterBuilderEmbedded.tsx b/Signum.React.Extensions/UserAssets/Templates/FilterBuilderEmbedded.tsx index 351d5f37a3..1c2b513bd3 100644 --- a/Signum.React.Extensions/UserAssets/Templates/FilterBuilderEmbedded.tsx +++ b/Signum.React.Extensions/UserAssets/Templates/FilterBuilderEmbedded.tsx @@ -256,7 +256,7 @@ export function EntityLineOrExpression(p: EntityLineOrExpressionProps) { liteRef.current = lite; if (lite != null) { - Navigator.API.fillToStrings(lite) + Navigator.API.fillLiteModels(lite) .then(() => forceUpdate()) .done(); } diff --git a/Signum.React.Extensions/UserQueries/Templates/UserQuery.tsx b/Signum.React.Extensions/UserQueries/Templates/UserQuery.tsx index 6693fbe477..f9265e6b7f 100644 --- a/Signum.React.Extensions/UserQueries/Templates/UserQuery.tsx +++ b/Signum.React.Extensions/UserQueries/Templates/UserQuery.tsx @@ -9,7 +9,7 @@ import QueryTokenEmbeddedBuilder from '../../UserAssets/Templates/QueryTokenEmbe import FilterBuilderEmbedded from '../../UserAssets/Templates/FilterBuilderEmbedded'; import { useAPI, useForceUpdate } from '@framework/Hooks' import { QueryTokenEmbedded } from '../../UserAssets/Signum.Entities.UserAssets' -import { SearchMessage } from '@framework/Signum.Entities' +import { SearchMessage, getToString } from '@framework/Signum.Entities' const CurrentEntityKey = "[CurrentEntity]"; @@ -40,7 +40,7 @@ export default function UserQuery(p: { ctx: TypeContext }) { {query && (
e.entityType)} readOnly={ctx.value.appendFilters} onChange={() => forceUpdate()} - helpText={UserQueryMessage.MakesTheUserQueryAvailableAsAQuickLinkOf0.niceToString(ctx.value.entityType?.toStr ?? UserQueryMessage.TheSelected0.niceToString(ctx.niceName(a => a.entityType)))} /> + helpText={UserQueryMessage.MakesTheUserQueryAvailableAsAQuickLinkOf0.niceToString(getToString(ctx.value.entityType) ?? UserQueryMessage.TheSelected0.niceToString(ctx.niceName(a => a.entityType)))} /> { p.ctx.value.entityType &&
diff --git a/Signum.React.Extensions/UserQueries/Templates/UserQueryPage.tsx b/Signum.React.Extensions/UserQueries/Templates/UserQueryPage.tsx index 96a4a5a2a3..e4c379ad5f 100644 --- a/Signum.React.Extensions/UserQueries/Templates/UserQueryPage.tsx +++ b/Signum.React.Extensions/UserQueries/Templates/UserQueryPage.tsx @@ -30,7 +30,7 @@ export default function UserQueryPage(p: UserQueryPageProps) { .then(uq => { setCurrentUserQuery(uq); const lite = entity == undefined ? undefined : parseLite(entity); - return Navigator.API.fillToStrings(lite) + return Navigator.API.fillLiteModels(lite) .then(() => UserQueryClient.Converter.toFindOptions(uq, lite)) }) }, [userQueryId, entity]); diff --git a/Signum.React.Extensions/UserQueries/UserQueryClient.tsx b/Signum.React.Extensions/UserQueries/UserQueryClient.tsx index 98ccb08d2d..72ed198cb8 100644 --- a/Signum.React.Extensions/UserQueries/UserQueryClient.tsx +++ b/Signum.React.Extensions/UserQueries/UserQueryClient.tsx @@ -6,7 +6,7 @@ import { EntitySettings } from '@framework/Navigator' import * as AppContext from '@framework/AppContext' import * as Navigator from '@framework/Navigator' import * as Finder from '@framework/Finder' -import { Entity, Lite, liteKey } from '@framework/Signum.Entities' +import { Entity, getToString, Lite, liteKey } from '@framework/Signum.Entities' import * as Constructor from '@framework/Constructor' import * as QuickLinks from '@framework/QuickLinks' import { translated } from '../Translation/TranslatedInstanceTools' @@ -48,7 +48,7 @@ export function start(options: { routes: JSX.Element[] }) { API.forEntityType(ctx.lite.EntityType); return promise.then(uqs => - uqs.map(uq => new QuickLinks.QuickLinkAction(liteKey(uq), () => uq.toStr ?? "", e => { + uqs.map(uq => new QuickLinks.QuickLinkAction(liteKey(uq), () => getToString(uq) ?? "", e => { window.open(AppContext.toAbsoluteUrl(`~/userQuery/${uq.id}/${liteKey(ctx.lite)}`)); }, { icon: ["far", "list-alt"], iconColor: "dodgerblue" }))); }); @@ -110,7 +110,7 @@ function getGroupUserQueriesContextMenu(cic: ContextualItemsContext) { menuItems: uqs.map(uq => handleGroupMenuClick(uq, resFO, cic)}> - {uq.toStr} + {getToString(uq)} ) } as MenuItemBlock); diff --git a/Signum.React.Extensions/UserQueries/UserQueryMenu.tsx b/Signum.React.Extensions/UserQueries/UserQueryMenu.tsx index 87094ee816..74532c1123 100644 --- a/Signum.React.Extensions/UserQueries/UserQueryMenu.tsx +++ b/Signum.React.Extensions/UserQueries/UserQueryMenu.tsx @@ -3,7 +3,7 @@ import { DateTime } from 'luxon' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { classes, softCast } from '@framework/Globals' import * as Finder from '@framework/Finder' -import { parseLite, is, Lite, toLite, newMListElement, liteKey, SearchMessage, MList, MListElement } from '@framework/Signum.Entities' +import { parseLite, is, Lite, toLite, newMListElement, liteKey, SearchMessage, MList, MListElement, getToString } from '@framework/Signum.Entities' import * as AppContext from '@framework/AppContext' import * as Navigator from '@framework/Navigator' import SearchControlLoaded from '@framework/SearchControl/SearchControlLoaded' @@ -42,7 +42,7 @@ export default function UserQueryMenu(p: UserQueryMenuProps) { function setCurrentUserQuery(uq: Lite | undefined) { p.searchControl.extraUrlParams.userQuery = uq && liteKey(uq); - p.searchControl.pageSubTitle = uq?.toStr; + p.searchControl.pageSubTitle = getToString(uq); setCurrentUserQueryInternal(uq); p.searchControl.props.onPageTitleChanged?.(); } @@ -69,13 +69,13 @@ export default function UserQueryMenu(p: UserQueryMenuProps) { return UserQueryClient.API.forQuery(p.searchControl.props.findOptions.queryKey) .then(list => { setUserQueries(list); - if (currentUserQuery && currentUserQuery.toStr == null) { + if (currentUserQuery && currentUserQuery.model == null) { const similar = list.firstOrNull(l => is(l, currentUserQuery)); if (similar != null) { - currentUserQuery.toStr = similar.toStr; + currentUserQuery.model = similar.model; setCurrentUserQuery(currentUserQuery); } else { - Navigator.API.fillToStrings(currentUserQuery) + Navigator.API.fillLiteModels(currentUserQuery) .then(() => setCurrentUserQuery(currentUserQuery)) .done(); } @@ -232,7 +232,7 @@ export default function UserQueryMenu(p: UserQueryMenuProps) { }).done(); } - const currentUserQueryToStr = currentUserQuery ? currentUserQuery.toStr : undefined; + const currentUserQueryToStr = currentUserQuery ? getToString(currentUserQuery) : undefined; var canSave = Operations.tryGetOperationInfo(UserQueryOperation.Save, UserQueryEntity) != null; @@ -271,12 +271,12 @@ export default function UserQueryMenu(p: UserQueryMenuProps) {
}
{userQueries?.map((uq, i) => { - if (filter == undefined || uq.toStr?.search(new RegExp(RegExp.escape(filter), "i")) != -1) + if (filter == undefined || getToString(uq)?.search(new RegExp(RegExp.escape(filter), "i")) != -1) return ( handleOnClick(uq)}> - {uq.toStr} + {getToString(uq)} ); })} diff --git a/Signum.React.Extensions/Word/WordClient.tsx b/Signum.React.Extensions/Word/WordClient.tsx index 39a7fae708..00991f02d6 100644 --- a/Signum.React.Extensions/Word/WordClient.tsx +++ b/Signum.React.Extensions/Word/WordClient.tsx @@ -4,7 +4,7 @@ import { ajaxPost, ajaxPostRaw, saveFile } from '@framework/Services'; import { EntitySettings } from '@framework/Navigator' import * as Navigator from '@framework/Navigator' import * as Finder from '@framework/Finder' -import { Lite, Entity, EntityPack, toLite, ModifiableEntity, toMList } from '@framework/Signum.Entities' +import { Lite, Entity, EntityPack, toLite, ModifiableEntity, toMList, getToString } from '@framework/Signum.Entities' import { EntityOperationSettings } from '@framework/Operations' import { Type, isTypeEntity, QueryTokenString } from '@framework/Reflection' import * as Operations from '@framework/Operations' @@ -136,7 +136,7 @@ export function getWordTemplates(ctx: ContextualItemsContext): Promise handleMenuClick(wt, ctx)}> - {wt.toStr} + {getToString(wt)} ) } as MenuItemBlock; diff --git a/Signum.React.Extensions/Word/WordEntityMenu.tsx b/Signum.React.Extensions/Word/WordEntityMenu.tsx index 757b204b66..c900bd2a60 100644 --- a/Signum.React.Extensions/Word/WordEntityMenu.tsx +++ b/Signum.React.Extensions/Word/WordEntityMenu.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { Lite, toLite, Entity, EntityPack } from '@framework/Signum.Entities' +import { Lite, toLite, Entity, EntityPack, getToString } from '@framework/Signum.Entities' import * as Navigator from '@framework/Navigator' import { WordTemplateEntity, WordTemplateMessage } from './Signum.Entities.Word' import * as WordClient from './WordClient' @@ -42,7 +42,7 @@ export default function WordEntityMenu(p : WordEntityMenuProps){ p.entityPack.wordTemplates!.map((wt, i) => handleOnClick(wt)}> - {wt.toStr} + {getToString(wt)} ) } diff --git a/Signum.React.Extensions/Word/WordSearchMenu.tsx b/Signum.React.Extensions/Word/WordSearchMenu.tsx index 2b16a52522..2727a0bf31 100644 --- a/Signum.React.Extensions/Word/WordSearchMenu.tsx +++ b/Signum.React.Extensions/Word/WordSearchMenu.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { Lite } from '@framework/Signum.Entities' +import { getToString, Lite } from '@framework/Signum.Entities' import * as Navigator from '@framework/Navigator' import SearchControlLoaded from '@framework/SearchControl/SearchControlLoaded' import { WordTemplateEntity, WordTemplateMessage } from './Signum.Entities.Word' @@ -44,7 +44,7 @@ export default function WordSearchMenu(p : WordSearchMenuProps){ wordReports.map((wt, i) => handleOnClick(wt)}> - {wt.toStr} + {getToString(wt)} ) } diff --git a/Signum.React.Extensions/Workflow/ActivityMonitor/WorkflowActivityMonitorPage.tsx b/Signum.React.Extensions/Workflow/ActivityMonitor/WorkflowActivityMonitorPage.tsx index 560de294cc..04dc061f0c 100644 --- a/Signum.React.Extensions/Workflow/ActivityMonitor/WorkflowActivityMonitorPage.tsx +++ b/Signum.React.Extensions/Workflow/ActivityMonitor/WorkflowActivityMonitorPage.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import * as Finder from '@framework/Finder' -import { JavascriptMessage, Lite } from '@framework/Signum.Entities' +import { getToString, JavascriptMessage, Lite } from '@framework/Signum.Entities' import { WorkflowEntity, WorkflowModel, WorkflowActivityMonitorMessage, CaseActivityEntity } from '../Signum.Entities.Workflow' import * as Navigator from '@framework/Navigator' import { API, WorkflowActivityMonitor, WorkflowActivityMonitorRequest } from '../WorkflowClient' @@ -29,7 +29,7 @@ export default function WorkflowActivityMonitorPage(p: RouteComponentProps<{ wor var workflow = useAPI(() => { const lite = newLite(WorkflowEntity, p.match.params.workflowId); - return Navigator.API.fillToStrings(lite).then(() => lite); + return Navigator.API.fillLiteModels(lite).then(() => lite); }, [p.match.params.workflowId]); const config = React.useMemo(() => workflow == null ? undefined : ({ @@ -55,7 +55,7 @@ export default function WorkflowActivityMonitorPage(p: RouteComponentProps<{ wor return (

- {!config ? JavascriptMessage.loading.niceToString() : config.workflow.toStr} + {!config ? JavascriptMessage.loading.niceToString() : getToString(config.workflow)} {config && Navigator.isViewable(WorkflowEntity) &&  }
diff --git a/Signum.React.Extensions/Workflow/ActivityMonitor/WorkflowActivityStatsModal.tsx b/Signum.React.Extensions/Workflow/ActivityMonitor/WorkflowActivityStatsModal.tsx index 7e541aefcc..3d0eaf09bf 100644 --- a/Signum.React.Extensions/Workflow/ActivityMonitor/WorkflowActivityStatsModal.tsx +++ b/Signum.React.Extensions/Workflow/ActivityMonitor/WorkflowActivityStatsModal.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { openModal, IModalProps } from '@framework/Modals'; import * as Navigator from '@framework/Navigator'; -import { JavascriptMessage, toLite } from '@framework/Signum.Entities' +import { getToString, JavascriptMessage, toLite } from '@framework/Signum.Entities' import { WorkflowActivityStats } from "../WorkflowClient"; import { FormGroup, StyleContext, FormControlReadonly } from "@framework/Lines"; import { WorkflowActivityEntity, WorkflowActivityModel, WorkflowActivityMonitorMessage, CaseActivityEntity } from "../Signum.Entities.Workflow"; @@ -82,7 +82,7 @@ const [show, setShow] = React.useState(true); var stats = p.stats; return - {stats.workflowActivity.toStr} + {getToString(stats.workflowActivity)}
{ diff --git a/Signum.React.Extensions/Workflow/Bpmn/CaseFlowRenderer.ts b/Signum.React.Extensions/Workflow/Bpmn/CaseFlowRenderer.ts index c2cf5b948b..b2d8938137 100644 --- a/Signum.React.Extensions/Workflow/Bpmn/CaseFlowRenderer.ts +++ b/Signum.React.Extensions/Workflow/Bpmn/CaseFlowRenderer.ts @@ -7,6 +7,7 @@ import { Color, Gradient } from '../../Basics/Color' import { CaseFlow, CaseActivityStats, formatDuration } from '../WorkflowClient' import * as BpmnUtils from './BpmnUtils' import { calculatePoint, Rectangle } from "../../Map/Utils" +import { getToString } from "@framework/Signum.Entities" export class CaseFlowRenderer extends CustomRenderer { static $inject = ['config.bpmnRenderer', 'eventBus', 'styles', 'pathMap', 'canvas', 'textRenderer']; @@ -30,7 +31,7 @@ export class CaseFlowRenderer extends CustomRenderer { else { const pathGroup = (path.parentNode as SVGGElement).parentNode as SVGGElement; const title = (Array.toArray(pathGroup.childNodes) as SVGElement[]).filter(a => a.nodeName == "title").firstOrNull() || pathGroup.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "title")); - title.textContent = stats.filter(con => con.doneDate != null).map(con => `${DoneType.niceToString(con.doneType)} (${con.doneBy.toStr} ${DateTime.fromISO(con.doneDate).toRelative()})`).join("\n"); + title.textContent = stats.filter(con => con.doneDate != null).map(con => `${DoneType.niceToString(con.doneType)} (${getToString(con.doneBy)} ${DateTime.fromISO(con.doneDate).toRelative()})`).join("\n"); } return path; @@ -148,7 +149,7 @@ export class CaseFlowRenderer extends CustomRenderer { const title = (Array.toArray(pathGroup.childNodes) as SVGElement[]).filter(a => a.nodeName == "title").firstOrNull() || pathGroup.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "title")); - title.textContent = `${DoneType.niceToString(jump.doneType)} (${jump.doneBy.toStr} ${DateTime.fromISO(jump.doneDate).toRelative()})`; + title.textContent = `${DoneType.niceToString(jump.doneType)} (${getToString(jump.doneBy)} ${DateTime.fromISO(jump.doneDate).toRelative()})`; }); } } @@ -170,13 +171,13 @@ function getDoneColor(doneType: DoneType) { } function getTitle(stats: CaseActivityStats) { - let result = `${stats.workflowActivity.toStr} (${CaseNotificationEntity.nicePluralName()} ${stats.notifications}) + let result = `${getToString(stats.workflowActivity)} (${CaseNotificationEntity.nicePluralName()} ${stats.notifications}) ${CaseActivityEntity.nicePropertyName(a => a.startDate)}: ${DateTime.fromISO(stats.startDate).toFormat("FFF")} (${DateTime.fromISO(stats.startDate).toRelative()})`; if (stats.doneDate != null) result += ` ${CaseActivityEntity.nicePropertyName(a => a.doneDate)}: ${DateTime.fromISO(stats.doneDate).toFormat("FFF")} (${DateTime.fromISO(stats.doneDate).toRelative()}) -${CaseActivityEntity.nicePropertyName(a => a.doneBy)}: ${stats.doneBy && stats.doneBy.toStr} (${DoneType.niceToString(stats.doneType!)}) +${CaseActivityEntity.nicePropertyName(a => a.doneBy)}: ${stats.doneBy && getToString(stats.doneBy)} (${DoneType.niceToString(stats.doneType!)}) ${CaseActivityEntity.nicePropertyName(a => a.duration)}: ${formatMinutes(stats.duration)}`; result += ` diff --git a/Signum.React.Extensions/Workflow/Bpmn/ConnectionIcons.ts b/Signum.React.Extensions/Workflow/Bpmn/ConnectionIcons.ts index dc4333694c..33757cf694 100644 --- a/Signum.React.Extensions/Workflow/Bpmn/ConnectionIcons.ts +++ b/Signum.React.Extensions/Workflow/Bpmn/ConnectionIcons.ts @@ -1,6 +1,6 @@ -/// +/// import { WorkflowConditionEntity, WorkflowActionEntity } from '../Signum.Entities.Workflow' -import { Lite, liteKey } from '@framework/Signum.Entities' +import { getToString, Lite, liteKey } from '@framework/Signum.Entities' export function getOrientation(rect: BPMN.DiElement, reference: BPMN.DiElement, padding: number) { padding = padding || 0; @@ -124,7 +124,7 @@ export class ConnectionIcons { this._overlays.add(shape, 'transaction-boundaries', { position: position, - html: `
` + html: `
` }); }; diff --git a/Signum.React.Extensions/Workflow/Bpmn/WorkflowActivityMonitorRenderer.ts b/Signum.React.Extensions/Workflow/Bpmn/WorkflowActivityMonitorRenderer.ts index d918e95095..971aa1ef37 100644 --- a/Signum.React.Extensions/Workflow/Bpmn/WorkflowActivityMonitorRenderer.ts +++ b/Signum.React.Extensions/Workflow/Bpmn/WorkflowActivityMonitorRenderer.ts @@ -8,7 +8,7 @@ import * as BpmnUtils from './BpmnUtils' import NavigatedViewer from "bpmn-js/lib/NavigatedViewer" import { WorkflowActivityMonitorConfig } from "../ActivityMonitor/WorkflowActivityMonitorPage"; import { QueryToken } from "@framework/FindOptions"; -import { is } from "@framework/Signum.Entities"; +import { getToString, is } from "@framework/Signum.Entities"; export class WorkflowActivityMonitorRenderer extends CustomRenderer { workflowActivityMonitor!: WorkflowActivityMonitor; @@ -60,7 +60,7 @@ export class WorkflowActivityMonitorRenderer extends CustomRenderer { } function getTitle(stats: WorkflowActivityStats, config: WorkflowActivityMonitorConfig) { - let result = `${stats.workflowActivity.toStr} (${stats.caseActivityCount})`; + let result = `${getToString(stats.workflowActivity)} (${stats.caseActivityCount})`; if (config.columns.length) { result += "\n" + config.columns.map((col, i) => diff --git a/Signum.React.Extensions/Workflow/Case/ActivityWithRemarks.tsx b/Signum.React.Extensions/Workflow/Case/ActivityWithRemarks.tsx index 5170892da1..c73a7add86 100644 --- a/Signum.React.Extensions/Workflow/Case/ActivityWithRemarks.tsx +++ b/Signum.React.Extensions/Workflow/Case/ActivityWithRemarks.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { classes } from '@framework/Globals' -import { Lite } from '@framework/Signum.Entities' +import { getToString, Lite } from '@framework/Signum.Entities' import { CaseActivityMessage, CaseNotificationEntity, CaseNotificationOperation, CaseActivityEntity, WorkflowActivityEntity, CaseTagTypeEntity, CaseEntity } from '../Signum.Entities.Workflow' import { FindOptions } from '@framework/Search' import * as Finder from '@framework/Finder' @@ -86,7 +86,7 @@ export default function ActivityWithRemarksComponent(p: ActivityWithRemarksProps } return ( - {p.data.workflowActivity.toStr} + {getToString(p.data.workflowActivity)}  
-
{caseActivityStats.first().workflowActivity.toStr} ({caseActivityStats.length} {caseActivityStats.length == 1 ? CaseActivityEntity.niceName() : CaseActivityEntity.nicePluralName()})
+
{getToString(caseActivityStats.first().workflowActivity)} ({caseActivityStats.length} {caseActivityStats.length == 1 ? CaseActivityEntity.niceName() : CaseActivityEntity.nicePluralName()})
@@ -45,7 +45,7 @@ export default function CaseActivityStatsModal(p: CaseActivityStatsModalProps) { { caseActivityStats.map(a => {a.doneBy.toStr} {DoneType.niceToString(a.doneType!)} ({DateTime.fromISO(a.doneDate).toRelative()}) as any}> + title={a.doneDate == null ? CaseActivityMessage.Pending.niceToString() : {getToString(a.doneBy)} {DoneType.niceToString(a.doneType!)} ({DateTime.fromISO(a.doneDate).toRelative()}) as any}> ) } @@ -121,7 +121,7 @@ export function CaseActivityStatsComponent(p : CaseActivityStatsComponentProps){ return (
- {stats.caseActivity.toStr} + {getToString(stats.caseActivity)} a.doneBy)}>{stats.doneBy && } a.startDate)}>{formatDate(stats.startDate)} a.doneDate)}>{formatDate(stats.doneDate)} diff --git a/Signum.React.Extensions/Workflow/Case/CaseButtonBar.tsx b/Signum.React.Extensions/Workflow/Case/CaseButtonBar.tsx index 2a5b0817bb..8eaf8b3500 100644 --- a/Signum.React.Extensions/Workflow/Case/CaseButtonBar.tsx +++ b/Signum.React.Extensions/Workflow/Case/CaseButtonBar.tsx @@ -4,7 +4,7 @@ import { DateTime } from 'luxon' import { TypeContext, EntityFrame } from '@framework/TypeContext' import { PropertyRoute, ReadonlyBinding } from '@framework/Reflection' import { ValueLine } from '@framework/Lines' -import { EntityPack } from '@framework/Signum.Entities' +import { EntityPack, getToString } from '@framework/Signum.Entities' import { ButtonBar } from '@framework/Frames/ButtonBar' import { CaseActivityEntity, CaseActivityMessage, WorkflowActivityEntity } from '../Signum.Entities.Workflow' import { DynamicViewMessage } from '../../Dynamic/Signum.Entities.Dynamic' @@ -21,7 +21,7 @@ export default function CaseButtonBar(p : CaseButtonBarProps){ return (
{CaseActivityMessage.DoneBy0On1.niceToString().formatHtml( - {ca.doneBy && ca.doneBy.toStr}, + {ca.doneBy && getToString(ca.doneBy)}, ca.doneDate && {DateTime.fromISO(ca.doneDate).toFormat("FFF")} ({DateTime.fromISO(ca.doneDate).toRelative()})) }
diff --git a/Signum.React.Extensions/Workflow/Case/CaseFramePage.tsx b/Signum.React.Extensions/Workflow/Case/CaseFramePage.tsx index 677b2e1114..324b180c0f 100644 --- a/Signum.React.Extensions/Workflow/Case/CaseFramePage.tsx +++ b/Signum.React.Extensions/Workflow/Case/CaseFramePage.tsx @@ -78,7 +78,7 @@ export default class CaseFramePage extends React.Component {prev == null ? JavascriptMessage.loading.niceToString() : CaseActivityMessage.From0On1.niceToString().formatHtml( - {prev.doneBy!.toStr}, + {getToString(prev.doneBy)}, {DateTime.fromISO(prev.doneDate!).toFormat("FFF")} ({DateTime.fromISO(prev.doneDate!).toRelative()})) }
diff --git a/Signum.React.Extensions/Workflow/Case/InlineCaseTags.tsx b/Signum.React.Extensions/Workflow/Case/InlineCaseTags.tsx index 161c8cc739..d5a01b1628 100644 --- a/Signum.React.Extensions/Workflow/Case/InlineCaseTags.tsx +++ b/Signum.React.Extensions/Workflow/Case/InlineCaseTags.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Dic, classes } from '@framework/Globals' import * as Navigator from '@framework/Navigator' import * as Operations from '@framework/Operations' -import { Lite, newMListElement } from '@framework/Signum.Entities' +import { getToString, Lite, newMListElement } from '@framework/Signum.Entities' import { CaseTagTypeEntity, CaseEntity, CaseTagsModel, CaseOperation } from '../Signum.Entities.Workflow' import Tag from './Tag' import * as WorkflowClient from '../WorkflowClient' @@ -31,7 +31,7 @@ export default function InlineCaseTags(p: InlineCaseTagsProps) { } }, [p.case, ...p.defaultTags ?? []]); - + function handleTagsClick(e: React.MouseEvent) { e.preventDefault(); @@ -41,7 +41,7 @@ export default function InlineCaseTags(p: InlineCaseTagsProps) { }); Navigator.view(model, - { title: p.case.toStr ?? "" }) + { title: getToString(p.case) ?? "" }) .then(cm => { if (!cm) return; diff --git a/Signum.React.Extensions/Workflow/Workflow/WorkflowDropdown.tsx b/Signum.React.Extensions/Workflow/Workflow/WorkflowDropdown.tsx index ef46a45bc6..20b5705117 100644 --- a/Signum.React.Extensions/Workflow/Workflow/WorkflowDropdown.tsx +++ b/Signum.React.Extensions/Workflow/Workflow/WorkflowDropdown.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { getTypeInfo } from '@framework/Reflection' -import { JavascriptMessage } from '@framework/Signum.Entities' +import { getToString, JavascriptMessage } from '@framework/Signum.Entities' import { WorkflowEntity, CaseActivityQuery, WorkflowMainEntityStrategy } from '../Signum.Entities.Workflow' import * as WorkflowClient from '../WorkflowClient' import { NavDropdown, Dropdown } from 'react-bootstrap' @@ -42,7 +42,7 @@ function WorkflowDropdownImp(props: {}) { (kvp.elements.length > 1 && {kvp.elements[0].typeInfo.niceName}), ...kvp.elements.map((val, j) => - {val.workflow.toStr}{val.mainEntityStrategy == "CreateNew" ? "" : `(${WorkflowMainEntityStrategy.niceToString(val.mainEntityStrategy)})`} + {getToString(val.workflow)}{val.mainEntityStrategy == "CreateNew" ? "" : `(${WorkflowMainEntityStrategy.niceToString(val.mainEntityStrategy)})`} ) ])} diff --git a/Signum.React.Extensions/Workflow/Workflow/WorkflowHelpComponent.tsx b/Signum.React.Extensions/Workflow/Workflow/WorkflowHelpComponent.tsx index 48023a92e9..306739e2b7 100644 --- a/Signum.React.Extensions/Workflow/Workflow/WorkflowHelpComponent.tsx +++ b/Signum.React.Extensions/Workflow/Workflow/WorkflowHelpComponent.tsx @@ -3,6 +3,7 @@ import { WorkflowEntity, WorkflowActivityEntity, WorkflowActivityMessage } from import * as Finder from '@framework/Finder' import { TypeHelpMode } from '../../TypeHelp/TypeHelpClient' import ValueLineModal from '@framework/ValueLineModal'; +import { getToString } from '@framework/Signum.Entities'; interface WorkflowHelpComponentProps { typeName: string; @@ -28,8 +29,8 @@ export default function WorkflowHelpComponent(p : WorkflowHelpComponentProps){ return; var text = acts.map(a => p.mode == "CSharp" ? - `WorkflowActivityInfo.Current.Is("${w.toStr}", "${a.toStr}")` : - `modules.WorkflowClient.inWorkflow(ctx, "${w.toStr}", "${a.toStr}")` + `WorkflowActivityInfo.Current.Is("${getToString(w)}", "${getToString(a)}")` : + `modules.WorkflowClient.inWorkflow(ctx, "${getToString(w)}", "${getToString(a)}")` ).join(" ||\r\n"); ValueLineModal.show({ diff --git a/Signum.React.Extensions/Workflow/Workflow/WorkflowReplacementComponent.tsx b/Signum.React.Extensions/Workflow/Workflow/WorkflowReplacementComponent.tsx index 5cd5c72feb..e7d57e317b 100644 --- a/Signum.React.Extensions/Workflow/Workflow/WorkflowReplacementComponent.tsx +++ b/Signum.React.Extensions/Workflow/Workflow/WorkflowReplacementComponent.tsx @@ -4,7 +4,7 @@ import { TypeContext } from '@framework/Lines' import { SearchValueLine } from '@framework/Search' import { symbolNiceName } from '@framework/Reflection' import { PreviewTask } from '../WorkflowClient' -import { is } from "@framework/Signum.Entities"; +import { getToString, is } from "@framework/Signum.Entities"; import { useForceUpdate } from '@framework/Hooks' export default function WorkflowReplacementComponent(p: { ctx: TypeContext }) { @@ -25,7 +25,7 @@ export default function WorkflowReplacementComponent(p: { ctx: TypeContext

), - "MainEntity": new Finder.CellFormatter(cell => {cell.toStr}), - "Actor": new Finder.CellFormatter(cell => {cell.toStr}), - "Sender": new Finder.CellFormatter(cell => cell && {cell.toStr}), - "Workflow": new Finder.CellFormatter(cell => {cell.toStr}), + "MainEntity": new Finder.CellFormatter(cell => {getToString(cell)}), + "Actor": new Finder.CellFormatter(cell => {getToString(cell)}), + "Sender": new Finder.CellFormatter(cell => cell && {getToString(cell)}), + "Workflow": new Finder.CellFormatter(cell => {getToString(cell)}), }, defaultOrders: [{ token: "StartDate", @@ -223,7 +223,7 @@ export function start(options: { routes: JSX.Element[], overrideCaseActivityMixi function askDeleteMainEntity(mainEntity?: ICaseMainEntity): Promise { return MessageModal.show({ title: CaseMessage.DeleteMainEntity.niceToString(), - message: mainEntity == null ? CaseMessage.DoYouWAntToAlsoDeleteTheMainEntities.niceToString() : CaseMessage.DoYouWAntToAlsoDeleteTheMainEntity0.niceToString(mainEntity.toStr), + message: mainEntity == null ? CaseMessage.DoYouWAntToAlsoDeleteTheMainEntities.niceToString() : CaseMessage.DoYouWAntToAlsoDeleteTheMainEntity0.niceToString(getToString(mainEntity)), buttons: "yes_no_cancel", style: "warning" }).then(u => u == "cancel" ? undefined : u == "yes") @@ -452,7 +452,7 @@ function chooseWorkflowExpirationDate(workflows: Lite[]): Promis message:
{WorkflowMessage.PleaseChooseExpirationDate.niceToString()} -
    {workflows.map((w, i) =>
  • {w.toStr}
  • )}
+
    {workflows.map((w, i) =>
  • {getToString(w)}
  • )}
}); } @@ -631,7 +631,7 @@ function getWorkflowJumpSelector(activity: Lite): Promis .then(jumps => SelectorModal.chooseElement(jumps, { title: WorkflowActivityMessage.ChooseADestinationForWorkflowJumping.niceToString(), - buttonDisplay: a => a.toStr ?? "", + buttonDisplay: a => getToString(a) ?? "", forceShow: true })); } diff --git a/Signum.React.Extensions/Workflow/WorkflowToolbarMenuConfig.tsx b/Signum.React.Extensions/Workflow/WorkflowToolbarMenuConfig.tsx index 0d1df4baa3..6cd7a4021a 100644 --- a/Signum.React.Extensions/Workflow/WorkflowToolbarMenuConfig.tsx +++ b/Signum.React.Extensions/Workflow/WorkflowToolbarMenuConfig.tsx @@ -3,7 +3,7 @@ import * as AppContext from '@framework/AppContext' import { useAPI } from '@framework/Hooks' import * as Navigator from '@framework/Navigator' import { getTypeInfo } from '@framework/Reflection' -import { is } from '@framework/Signum.Entities' +import { getToString, is } from '@framework/Signum.Entities' import * as React from 'react' import { Nav } from 'react-bootstrap' import { PermissionSymbol } from '../Authorization/Signum.Entities.Authorization' @@ -85,7 +85,7 @@ function WorkflowDropdownImp() { icon={ToolbarConfig.coloredIcon("inbox", "steelblue")} /> {getStarts(starts).flatMap((kvp, i) => kvp.elements.map((val, j) => - ) => { AppContext.pushOrOpenInTab(`~/workflow/new/${val.workflow.id}/${val.mainEntityStrategy}`, e); }} active={false} icon={ToolbarConfig.coloredIcon("plus-square", "seagreen")} diff --git a/Signum.React/ApiControllers/EntityController.cs b/Signum.React/ApiControllers/EntityController.cs index 41bb9b4af3..dfae55d2c8 100644 --- a/Signum.React/ApiControllers/EntityController.cs +++ b/Signum.React/ApiControllers/EntityController.cs @@ -41,13 +41,13 @@ public EntityPackTS GetEntityPackEntity([Required, FromBody]Entity entity) return SignumServer.GetEntityPack(entity); } - [HttpPost("api/entityToStrings")] - public string[] EntityToStrings([Required, FromBody]Lite[] lites) + [HttpPost("api/liteModels")] + public object[] LiteModels([Required, FromBody]Lite[] lites) { if (lites == null || lites.Length == 0) throw new ArgumentNullException(nameof(lites)); - return lites.Select(a => Database.GetToStr(a.EntityType, a.Id)).ToArray(); + return lites.Select(a => Database.GetLiteModel(a.EntityType, a.Id, a.ModelType)).ToArray(); } [HttpGet("api/fetchAll/{typeName}"), ProfilerActionSplitter("typeName")] diff --git a/Signum.React/Facades/LambdaToJavascriptConverter.cs b/Signum.React/Facades/LambdaToJavascriptConverter.cs index 9fae558f07..0704aaf328 100644 --- a/Signum.React/Facades/LambdaToJavascriptConverter.cs +++ b/Signum.React/Facades/LambdaToJavascriptConverter.cs @@ -1,3 +1,5 @@ +using Signum.Engine.Basics; +using Signum.Entities.Basics; using Signum.Entities.Reflection; using Signum.Utilities.Reflection; @@ -17,7 +19,7 @@ internal class LambdaToJavascriptConverter if (body == null) return null; - return "function(e){ return " + body + "; }"; + return "return " + body; } static Dictionary replacements = new Dictionary @@ -54,24 +56,34 @@ internal class LambdaToJavascriptConverter { var a = ToJavascript(param, me.Expression!); - if (a == null) - return null; - - if (me.Expression!.Type.IsNullable()) + if (a != null) { - if (me.Member.Name == "HasValue") - return a + " != null"; - else if (me.Member.Name == "Value") - return a; - } + if (me.Expression!.Type.IsNullable()) + { + if (me.Member.Name == "HasValue") + return a + " != null"; + else if (me.Member.Name == "Value") + return a; + } - if (me.Expression.Type.IsEntity()) - { - if (me.Member.Name == "IdOrNull") - return a + "." + "id"; + if (me.Expression.Type.IsEntity()) + { + if (me.Member.Name == "IdOrNull") + return a + "." + "id"; + } + + return a + "." + me.Member.Name.FirstLower(); } + } + + if(expr is ConditionalExpression cond) + { + var test = ToJavascript(param, cond.Test); + var ifTrue = ToJavascript(param, cond.IfTrue); + var ifFalse = ToJavascript(param, cond.IfFalse); - return a + "." + me.Member.Name.FirstLower(); + if (test != null && ifTrue != null && ifFalse != null) + return "(" + test + " ? " + ifTrue + " : " + ifFalse + ")"; } if (expr is BinaryExpression be) @@ -84,8 +96,15 @@ internal class LambdaToJavascriptConverter if (a != null && b != null) return "(" + a + " + " + b + ")"; + } + else if (be.NodeType == ExpressionType.Add) + { + + var a = ToJavascript(param, be.Left); + var b = ToJavascript(param, be.Right); - return null; + if (a != null && b != null) + return "(" + a + " + " + b + ")"; } else { @@ -95,11 +114,7 @@ internal class LambdaToJavascriptConverter var op = ToJsOperator(be.NodeType); if (a != null && op != null && b != null) - { return a + op + b; - } - - return null; } } @@ -111,29 +126,52 @@ internal class LambdaToJavascriptConverter if (mc.Method.Name == "GetType" && mc.Object != null && (mc.Object.Type.IsIEntity() || mc.Object.Type.IsModelEntity())) { var obj = ToJavascript(param, mc.Object!); - if (obj == null) - return null; + if (obj != null) + return "fd.getTypeInfo(" + obj + ")"; + } + + if (mc.Method.Name == nameof(string.Format) && mc.Method.DeclaringType == typeof(string) || + mc.Method.Name == nameof(StringExtensions.FormatWith) && mc.Method.DeclaringType == typeof(StringExtensions)) + { + var format = (mc.Object ?? mc.GetArgument("format")); + + var args = mc.TryGetArgument("args")?.Let(a => ((NewArrayExpression)a).Expressions) ?? + new[] { mc.TryGetArgument("arg0"), mc.TryGetArgument("arg1"), mc.TryGetArgument("arg2"), mc.TryGetArgument("arg3") }.NotNull().ToReadOnly(); - return "getTypeInfo(" + obj + ")"; + var strFormat = ToJavascriptToString(param, format); + var arguments = args.Select(a => ToJavascriptToString(param, a)).ToList(); + + if (strFormat != null && !arguments.Contains(null)) + return $"{strFormat}.formatWith(" + arguments.ToString(", ") + ")"; } if (mc.Method.IsExtensionMethod() && mc.Arguments.Only()?.Type == typeof(Type)) { var obj = ToJavascript(param, mc.Arguments.SingleEx()); - if (obj == null) - return null; + if (obj != null) + { + if (mc.Method.Name == nameof(DescriptionManager.NiceName)) + return obj + ".niceName"; - 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(DescriptionManager.NicePluralName)) - return obj + ".nicePluralName"; + if (mc.Method.Name == nameof(Reflector.NewNiceName)) + return "fd.newNiceName(" + obj + ")"; - if (mc.Method.Name == nameof(Reflector.NewNiceName)) - return "newNiceName(" + obj + ")"; - - return null; + + } + } + + if (mc.Method.Name == nameof(Symbol.NiceToString)) + { + if(mc.Object != null && (typeof(Symbol).IsAssignableFrom(mc.Object.Type) || typeof(SemiSymbol).IsAssignableFrom(mc.Object.Type))) + { + var obj = ToJavascript(param, mc.Object!); + if (obj != null) + return "fd.getToString(" + obj + ")"; + } } @@ -185,8 +223,20 @@ internal class LambdaToJavascriptConverter if (t != null && a != null && b != null) return "(" + t + " ? " + a + " : " + b + ")"; + } - return null; + if(expr is MemberInitExpression mie && + mie.NewExpression.Arguments.Count == 0 && + mie.Bindings.All(b => b is MemberAssignment)) + { + var fields = mie.Bindings.Cast() + .ToString(ma => ma.Member.Name.FirstLower() + ": " + ToJavascript(param, ma.Expression) + ",", "\n"); + + var t = mie.Type; + + var typeName = TypeLogic.TryGetCleanName(t) ?? t.Name; + + return $"fd.New(\"{typeName}\", {{\n{fields}\n}})"; } return null; @@ -220,28 +270,28 @@ internal class LambdaToJavascriptConverter return r; if (expr.Type.IsModifiableEntity() || expr.Type.IsLite() || expr.Type.IsIEntity()) - return "getToString(" + r + ")"; + return "fd.getToString(" + r + ")"; string? formatFull = format == null ? null : (", '" + format + "'"); if (expr.Type.UnNullify() == typeof(DateTime)) - return "dateToString(" + r + ", 'DateTime'" + formatFull + ")"; + return "fd.dateToString(" + r + ", 'DateTime'" + formatFull + ")"; if (expr.Type.UnNullify() == typeof(DateOnly)) - return "dateToString(" + r + ", 'Date'" + formatFull + ")"; + return "fd.dateToString(" + r + ", 'Date'" + formatFull + ")"; if (expr.Type.UnNullify() == typeof(TimeSpan)) /*deprecate?*/ - return "timeToString(" + r + formatFull + ")"; + return "fd.timeToString(" + r + formatFull + ")"; if (expr.Type.UnNullify() == typeof(TimeOnly)) - return "timeToString(" + r + formatFull + ")"; + return "fd.timeToString(" + r + formatFull + ")"; if (ReflectionTools.IsIntegerNumber(expr.Type.UnNullify())) - return "numberToString(" + r + (formatFull ?? ", 'D'") + ")"; + return "fd.numberToString(" + r + (formatFull ?? ", 'D'") + ")"; if (ReflectionTools.IsDecimalNumber(expr.Type.UnNullify())) - return "numberToString(" + r + (formatFull ?? ", 'N'") + ")"; + return "fd.numberToString(" + r + (formatFull ?? ", 'N'") + ")"; - return "valToString(" + r + ")"; + return "fd.valToString(" + r + ")"; } } diff --git a/Signum.React/Facades/ReflectionServer.cs b/Signum.React/Facades/ReflectionServer.cs index db194b2723..5129fb67f9 100644 --- a/Signum.React/Facades/ReflectionServer.cs +++ b/Signum.React/Facades/ReflectionServer.cs @@ -179,7 +179,7 @@ where typeof(ModelEntity).IsAssignableFrom(type) && !type.IsAbstract var settings = Schema.Current.Settings; var result = (from type in TypeLogic.TypeToEntity.Keys.Concat(models) - where !type.IsEnumEntity() && !type.IsGenericType &&!ReflectionServer.ExcludeTypes.Contains(type) + where !type.IsEnumEntity() && !type.IsGenericType && !ReflectionServer.ExcludeTypes.Contains(type) let descOptions = LocalizedAssembly.GetDescriptionOptions(type) let allOperations = !type.IsEntity() ? null : OperationLogic.GetAllOperationInfos(type) select KeyValuePair.Create(GetTypeName(type), OnTypeExtension(new TypeInfoTS @@ -221,6 +221,13 @@ select KeyValuePair.Create(GetTypeName(type), OnTypeExtension(new TypeInfoTS .Where(kvp => kvp.Value != null) .ToDictionaryEx("properties"), + CustomLiteModels = !type.IsEntity() ? null : Lite.LiteModelConstructors.TryGetC(type)?.Values + .ToDictionary(lmc => lmc.ModelType.TypeName(), lmc => new CustomLiteModelTS + { + IsDefault = lmc.IsDefault, + ConstructorFunctionString = LambdaToJavascriptConverter.ToJavascript(lmc.GetConstructorExpression())! + }), + HasConstructorOperation = allOperations != null && allOperations.Any(oi => oi.OperationType == OperationType.Constructor), Operations = allOperations == null ? null : allOperations.Select(oi => KeyValuePair.Create(oi.OperationSymbol.Key, OnOperationExtension(new OperationInfoTS(oi), oi, type)!)).Where(kvp => kvp.Value != null).ToDictionaryEx("operations"), @@ -357,7 +364,9 @@ public class TypeInfoTS [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool IsSystemVersioned { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? ToStringFunction { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool QueryDefined { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Dictionary Members { get; set; } = null!; + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Dictionary? CustomLiteModels { get; set; } = null!; [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool HasConstructorOperation { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Dictionary? Operations { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] public bool RequiresEntityPack { get; set; } @@ -369,6 +378,12 @@ public class TypeInfoTS public override string ToString() => $"{Kind} {NiceName} {EntityKind} {EntityData}"; } +public class CustomLiteModelTS +{ + public string? ConstructorFunctionString = null!; + public bool IsDefault; +} + public class MemberInfoTS { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public TypeReferenceTS? Type { get; set; } diff --git a/Signum.React/Scripts/Components/Typeahead.tsx b/Signum.React/Scripts/Components/Typeahead.tsx index 8444c9e1fb..ddc56e4e57 100644 --- a/Signum.React/Scripts/Components/Typeahead.tsx +++ b/Signum.React/Scripts/Components/Typeahead.tsx @@ -301,7 +301,7 @@ Typeahead.defaultProps = { export namespace TypeaheadOptions { - export function highlightedText(val: string, query?: string): React.ReactNode { + export function highlightedText(val: string, query?: string): React.ReactChild { if (query == undefined) return val; @@ -319,13 +319,13 @@ export namespace TypeaheadOptions { ); } - export function highlightedTextAll(val: string, query: string | undefined): React.ReactNode { + export function highlightedTextAll(val: string, query: string | undefined): React.ReactChild { if (query == undefined) return val; const parts = query.toLocaleLowerCase().split(" ").filter(a => a.length > 0).orderByDescending(a => a.length); - function splitText(str: string, partIndex: number): React.ReactNode { + function splitText(str: string, partIndex: number): React.ReactChild { if (str.length == 0) return str; diff --git a/Signum.React/Scripts/Finder.tsx b/Signum.React/Scripts/Finder.tsx index f57829fe3d..0577f7377c 100644 --- a/Signum.React/Scripts/Finder.tsx +++ b/Signum.React/Scripts/Finder.tsx @@ -15,7 +15,7 @@ import { import { PaginationMode, OrderType, FilterOperation, FilterType, UniqueType, QueryTokenMessage, FilterGroupOperation, PinnedFilterActive } from './Signum.Entities.DynamicQuery'; -import { Entity, Lite, toLite, liteKey, parseLite, EntityControlMessage, isLite, isEntityPack, isEntity, External, SearchMessage, ModifiableEntity, is, JavascriptMessage, isMListElement, MListElement } from './Signum.Entities'; +import { Entity, Lite, toLite, liteKey, parseLite, EntityControlMessage, isLite, isEntityPack, isEntity, External, SearchMessage, ModifiableEntity, is, JavascriptMessage, isMListElement, MListElement, getToString } from './Signum.Entities'; import { TypeEntity, QueryEntity, ExceptionEntity } from './Signum.Entities.Basics'; import { @@ -1219,7 +1219,7 @@ export class TokenCompleter { export function parseFilterValues(filterOptions: FilterOptionParsed[]): Promise { - const needToStr: Lite[] = []; + const needsModel: Lite[] = []; function parseFilterValue(fo: FilterOptionParsed) { if (isFilterGroupOptionParsed(fo)) @@ -1229,27 +1229,27 @@ export function parseFilterValues(filterOptions: FilterOptionParsed[]): Promise< if (!Array.isArray(fo.value)) fo.value = [fo.value]; - fo.value = (fo.value as any[]).map(v => parseValue(fo.token!, v, needToStr)); + fo.value = (fo.value as any[]).map(v => parseValue(fo.token!, v, needsModel)); } else { if (Array.isArray(fo.value)) throw new Error("Unespected array for operation " + fo.operation); - fo.value = parseValue(fo.token!, fo.value, needToStr); + fo.value = parseValue(fo.token!, fo.value, needsModel); } } } filterOptions.forEach(fo => parseFilterValue(fo)); - if (needToStr.length == 0) + if (needsModel.length == 0) return Promise.resolve(undefined); - return Navigator.API.fillToStringsArray(needToStr) + return Navigator.API.fillLiteModelsArray(needsModel) } -function parseValue(token: QueryToken, val: any, needToStr: Array): any { +function parseValue(token: QueryToken, val: any, needModel: Array): any { switch (token.filterType) { case "Boolean": return parseBoolean(val); case "Integer": return nanToNull(parseInt(val)); @@ -1291,8 +1291,8 @@ function parseValue(token: QueryToken, val: any, needToStr: Array): any { { const lite = convertToLite(val); - if (lite && !lite.toStr) - needToStr.push(lite); + if (lite && !lite.model) + needModel.push(lite); return lite; } @@ -1876,7 +1876,12 @@ function initFormatRules(): FormatRule[] { { name: "Object", isApplicable: qt => true, - formatter: qt => new CellFormatter(cell => cell ? {cell.toStr ?? cell.toString()} : undefined) + formatter: qt => new CellFormatter(cell => cell ? {cell?.toString()} : undefined) + }, + { + name: "Object", + isApplicable: qt => qt.filterType == "Embedded" || qt.filterType == "Lite", + formatter: qt => new CellFormatter(cell => cell ? {getToString(cell)} : undefined) }, { name: "MultiLine", @@ -1889,7 +1894,7 @@ function initFormatRules(): FormatRule[] { return false; }, - formatter: qt => new CellFormatter(cell => cell ? {cell.toStr ?? cell.toString()} : undefined) + formatter: qt => new CellFormatter(cell => cell ? {isLite(cell) ? getToString(cell) : cell?.toString()} : undefined) }, { name: "Password", diff --git a/Signum.React/Scripts/Frames/FramePage.tsx b/Signum.React/Scripts/Frames/FramePage.tsx index c1511b6a8b..104cb8d514 100644 --- a/Signum.React/Scripts/Frames/FramePage.tsx +++ b/Signum.React/Scripts/Frames/FramePage.tsx @@ -49,7 +49,7 @@ export default function FramePage(p: FramePageProps) { const type = ti.name; const id = p.match.params.id; - useTitle(state?.pack.entity.toStr ?? "", [state?.pack.entity]); + useTitle(getToString(state?.pack.entity) ?? "", [state?.pack.entity]); React.useEffect(() => { diff --git a/Signum.React/Scripts/Lines/AutoCompleteConfig.tsx b/Signum.React/Scripts/Lines/AutoCompleteConfig.tsx index 73547ff516..d7d7ceb34c 100644 --- a/Signum.React/Scripts/Lines/AutoCompleteConfig.tsx +++ b/Signum.React/Scripts/Lines/AutoCompleteConfig.tsx @@ -6,7 +6,8 @@ import { getTypeInfo, getQueryKey, QueryTokenString, getTypeName, getTypeInfos, import { ModifiableEntity, Lite, Entity, toLite, is, isLite, isEntity, getToString, liteKey, SearchMessage, parseLiteList } from '../Signum.Entities' import { toFilterRequests } from '../Finder'; import { TypeaheadController, TypeaheadOptions } from '../Components/Typeahead' -import { AutocompleteConstructor, getAutocompleteConstructors } from '../Navigator'; +import { AutocompleteConstructor } from '../Navigator'; +import * as Navigator from '../Navigator'; import { Dic } from '../Globals' export interface AutocompleteConfig { @@ -83,11 +84,11 @@ export class LiteAutocompleteConfig implements AutocompleteCon } var toStr = getToString(item); - var text = TypeaheadOptions.highlightedTextAll(toStr, subStr); + var html = Navigator.renderLite(item, subStr); if (this.showType) - return {text}; + return {html}; else - return text; + return html; } getEntityFromItem(item: Lite | AutocompleteConstructor): Promise | ModifiableEntity | undefined> { @@ -107,7 +108,6 @@ export class LiteAutocompleteConfig implements AutocompleteCon } getItemFromEntity(entity: Lite | ModifiableEntity): Promise> { - var lite = this.convertToLite(entity);; if (!this.requiresInitialLoad) @@ -145,7 +145,7 @@ export class LiteAutocompleteConfig implements AutocompleteCon } getSortByString(item: Lite | AutocompleteConstructor): string { - return isLite(item) ? item.toStr! : + return isLite(item) ? getToString(item)! : isAutocompleteConstructor(item) ? getTypeName(item.type) : ""; } @@ -303,11 +303,11 @@ export class FindOptionsAutocompleteConfig implements AutocompleteConfig{text}; + return {html}; else - return text; + return html; } getEntityFromItem(item: ResultRow | AutocompleteConstructor): Promise | ModifiableEntity | undefined> { @@ -373,7 +373,7 @@ export class FindOptionsAutocompleteConfig implements AutocompleteConfig): string { - return isResultRow(item) ? item.entity?.toStr! : + return isResultRow(item) ? getToString(item.entity)! : isAutocompleteConstructor(item) ? getTypeName(item.type) : ""; } diff --git a/Signum.React/Scripts/Lines/EntityBase.tsx b/Signum.React/Scripts/Lines/EntityBase.tsx index a71a4ac3f0..01a53b45d5 100644 --- a/Signum.React/Scripts/Lines/EntityBase.tsx +++ b/Signum.React/Scripts/Lines/EntityBase.tsx @@ -6,7 +6,7 @@ import * as Finder from '../Finder' import { FindOptions } from '../FindOptions' import { TypeContext } from '../TypeContext' import { PropertyRoute, tryGetTypeInfos, TypeInfo, IsByAll, TypeReference, getTypeInfo, getTypeInfos, Type } from '../Reflection' -import { ModifiableEntity, Lite, Entity, EntityControlMessage, toLiteFat, is, entityInfo, SelectorMessage, toLite, parseLiteList } from '../Signum.Entities' +import { ModifiableEntity, Lite, Entity, EntityControlMessage, toLiteFat, is, entityInfo, SelectorMessage, toLite, parseLiteList, getToString } from '../Signum.Entities' import { LineBaseController, LineBaseProps } from './LineBase' import SelectorModal from '../SelectorModal' import { TypeEntity } from "../Signum.Entities.Basics"; @@ -208,7 +208,7 @@ export class EntityBaseController

extends LineBaseCon return Promise.resolve(t.name); if (t.name == IsByAll) - return Finder.find(TypeEntity, { title: SelectorMessage.PleaseSelectAType.niceToString() }).then(t => t?.toStr /*CleanName*/); + return Finder.find(TypeEntity, { title: SelectorMessage.PleaseSelectAType.niceToString() }).then(t => getToString(t) /*CleanName*/); const tis = tryGetTypeInfos(t).notNull().filter(ti => predicate(ti)); @@ -284,7 +284,7 @@ export class EntityBaseController

extends LineBaseCon return; lites = lites.filter(lite => lite.EntityType == ti.name); - return Navigator.API.fillToStrings(...lites) + return Navigator.API.fillLiteModels(...lites) .then(() => SelectorModal.chooseLite(ti.name, lites)); }) .then(lite => { diff --git a/Signum.React/Scripts/Lines/EntityCheckboxList.tsx b/Signum.React/Scripts/Lines/EntityCheckboxList.tsx index 1581becce1..30ab65dec9 100644 --- a/Signum.React/Scripts/Lines/EntityCheckboxList.tsx +++ b/Signum.React/Scripts/Lines/EntityCheckboxList.tsx @@ -5,7 +5,7 @@ import * as Finder from '../Finder' import { FindOptions, ResultRow } from '../FindOptions' import { TypeContext } from '../TypeContext' import { TypeReference } from '../Reflection' -import { ModifiableEntity, Lite, Entity, MList, toLite, is, liteKey } from '../Signum.Entities' +import { ModifiableEntity, Lite, Entity, MList, toLite, is, liteKey, getToString } from '../Signum.Entities' import { EntityListBaseController, EntityListBaseProps } from './EntityListBase' import { useController } from './LineBase' import { normalizeEmptyArray } from './EntityCombo' @@ -125,7 +125,7 @@ export function EntityCheckboxListSelect(props: EntityCheckboxListSelectProps) { } else Finder.API.fetchAllLites({ types: p.type!.name }) - .then(data => setData(data.orderBy(a => a.toStr))) + .then(data => setData(data.orderBy(a => getToString(a)))) .done(); } }, [normalizeEmptyArray(p.data), p.type!.name, p.deps, p.findOptions && Finder.findOptionsPath(p.findOptions)]); @@ -196,7 +196,7 @@ export function EntityCheckboxListSelect(props: EntityCheckboxListSelectProps) { name={liteKey(row.entity!)} onChange={e => c.handleOnChange(row.entity!)} />   - {p.onRenderItem ? p.onRenderItem(row, i, checked, c, resultTable) : {row.entity!.toStr}} + {p.onRenderItem ? p.onRenderItem(row, i, checked, c, resultTable) : {getToString(row.entity)}} ); } diff --git a/Signum.React/Scripts/Lines/EntityCombo.tsx b/Signum.React/Scripts/Lines/EntityCombo.tsx index 52a2cb7f90..9107904448 100644 --- a/Signum.React/Scripts/Lines/EntityCombo.tsx +++ b/Signum.React/Scripts/Lines/EntityCombo.tsx @@ -3,9 +3,10 @@ import { ModifiableEntity, Lite, Entity, toLite, is, liteKey, getToString, isEnt import * as Finder from '../Finder' import { FindOptions, ResultRow } from '../FindOptions' import { TypeContext } from '../TypeContext' -import { TypeReference } from '../Reflection' +import { getTypeInfos, tryGetTypeInfos, TypeReference } from '../Reflection' import { EntityBaseController, EntityBaseProps } from './EntityBase' import { FormGroup } from './FormGroup' +import * as Navigator from '../Navigator' import { FormControlReadonly } from './FormControlReadonly' import { classes } from '../Globals'; import { useController } from './LineBase' @@ -37,6 +38,12 @@ export class EntityComboController extends EntityBaseController a && Navigator.getSettings(a)?.renderLite)) { + p.onRenderItem = row => (row?.entity && Navigator.renderLite(row.entity)) ?? ""; + } + } doView(entity: ModifiableEntity | Lite) { var promise = super.doView(entity) ?? Promise.resolve(undefined); @@ -221,8 +228,15 @@ export const EntityComboSelect = React.forwardRef(function EntityComboSelect(p: if (p.onRenderItem) { return ( - p.onChange(row?.entity ?? null)} value={getResultRow(lite)} - title={lite?.toStr} + p.onChange(row?.entity ?? null)} + value={getResultRow(lite)} + title={getToString(lite)} + filter={(e, query) => { + var toStr = getToString((e as ResultRow).entity).toLowerCase(); + return query.toLowerCase().split(' ').every(part => toStr.contains(part)); + }} renderValue={a => p.onRenderItem!(a.item?.entity == null ? undefined : a.item, "Value")} renderListItem={a => p.onRenderItem!(a.item?.entity == null ? undefined : a.item, "ListItem")} /> @@ -230,7 +244,7 @@ export const EntityComboSelect = React.forwardRef(function EntityComboSelect(p: } else { return ( case "Enum": return getEnumInfo(token!.type.name, value).niceName; diff --git a/Signum.React/Scripts/Signum.Entities.t4s b/Signum.React/Scripts/Signum.Entities.t4s index 6029f0fc31..e3632c7a7f 100644 --- a/Signum.React/Scripts/Signum.Entities.t4s +++ b/Signum.React/Scripts/Signum.Entities.t4s @@ -9,7 +9,7 @@ } export function liteKeyLong(lite: Lite) { - return lite.EntityType + ";" + (lite.id == undefined ? "" : lite.id) + ";" + lite.toStr; + return lite.EntityType + ";" + (lite.id == undefined ? "" : lite.id) + ";" + getToString(lite); } export interface Entity extends ModifiableEntity { @@ -57,10 +57,12 @@ export function toMList(array: T[]): MList { } export interface Lite { - entity?: T; EntityType: string; id?: number | string; - toStr?: string; + model?: unknown; + + ModelType?: string; + entity?: T; } export interface ModelState { @@ -86,12 +88,62 @@ export function registerToString(type: Type, toSt toStringDictionary[type.typeName] = toStringFunc as ((e: ModifiableEntity) => string) | null; } + +export function registerCustomModelConsturctor(type: Type, modelType: Type, constructLiteModel: ((e: T) => M)) { + var ti = Reflection.tryGetTypeInfo(type.typeName); + + if (ti) { + var clm = ti.customLiteModels?.[modelType.typeName]; + + if (clm == null) + throw new Error(`Type ${type.typeName} has no registered Lite Model '${modelType}'`); + + clm.constructorFunction = constructLiteModel as any as (e: Entity) => ModelEntity; + } +} + import * as Reflection from './Reflection' +import { object } from 'prop-types'; export function newNiceName(ti: Reflection.TypeInfo) { return FrameMessage.New0_G.niceToString().forGenderAndNumber(ti.gender).formatWith(ti.niceName); } +function createLiteModel(e: Entity, modelType?: string): ModelEntity | string { + + var ti = Reflection.tryGetTypeInfo(e.Type); + + if (ti == null) + return getToString(e); + + modelType ??= getDefaultLiteModelType(ti); + + if (modelType == "string") + return getToString(e); + + var clm = ti.customLiteModels?.[modelType]; + + if (clm == null) + throw new Error(`Type ${e.Type} has no registered Lite Model '${modelType}'`); + + if (clm.constructorFunction) + return clm.constructorFunction(e); + + if (clm.constructorFunctionString == null) + throw new Error(`No constructor function for '${modelType}' provided`); + + clm.constructorFunction = compileFunction(clm.constructorFunctionString); + + return clm.constructorFunction!(e); +} + +function getDefaultLiteModelType(ti: Reflection.TypeInfo) { + if (!ti.customLiteModels) + return "string"; + + return Object.keys(ti.customLiteModels).singleOrNull(modelType => ti.customLiteModels![modelType].isDefault) ?? "string" +} + function getOrCreateToStringFunction(type: string) { let f = toStringDictionary[type]; if (f || f === null) @@ -99,28 +151,30 @@ function getOrCreateToStringFunction(type: string) { const ti = Reflection.tryGetTypeInfo(type); - const getToString2 = getToString; - const newNiceName2 = newNiceName; - - try { - const getToString = getToString2; - const valToString = Reflection.valToString; - const numberToString = Reflection.numberToString; - const dateToString = Reflection.dateToString; - const timeToString = Reflection.timeToString; - const getTypeInfo = Reflection.getTypeInfo; - const newNiceName = newNiceName2; - - f = ti && ti.toStringFunction ? eval("(" + ti.toStringFunction + ")") : null; - } catch (e) { - f = null; - } - - toStringDictionary[type] = f; + toStringDictionary[type] = ti?.toStringFunction ? compileFunction(ti.toStringFunction) : null; return f; } +function compileFunction(functionString: string): (e: any) => any { + + var func = new Function("e", "fd", functionString); + + var funcDeps = { + getToString: getToString, + valToString: Reflection.valToString, + numberToString: Reflection.numberToString, + dateToString: Reflection.dateToString, + timeToString: Reflection.timeToString, + getTypeInfo: Reflection.getTypeInfo, + newNiceName: newNiceName, + New : Reflection.New, + }; + + return e => func(e, funcDeps); +} + + export function getToString(entityOrLite: ModifiableEntity | Lite | undefined | null, toStringLite?: (e : Entity) => string): string { if (entityOrLite == null) return ""; @@ -133,7 +187,13 @@ export function getToString(entityOrLite: ModifiableEntity | Lite | unde if (Reflection.isLowPopulationSymbol(lite.EntityType)) return Reflection.symbolNiceName(lite as Lite); - return lite.toStr || lite.EntityType; + if (typeof lite.model == "string") + return lite.model; + + if (isModifiableEntity(lite.model)) + return getToString(lite.model); + + return lite.EntityType; } const entity = entityOrLite as ModifiableEntity; @@ -147,14 +207,14 @@ export function getToString(entityOrLite: ModifiableEntity | Lite | unde return entity.toStr || entity.Type; } -export function toLite(entity: T, fat?: boolean, toStr?: string): Lite; -export function toLite(entity: T | null | undefined, fat?: boolean, toStr?: string): Lite | null; -export function toLite(entity: T | null | undefined, fat?: boolean, toStr?: string): Lite | null { +export function toLite(entity: T, fat?: boolean, model?: unknown): Lite; +export function toLite(entity: T | null | undefined, fat?: boolean, model?: unknown): Lite | null; +export function toLite(entity: T | null | undefined, fat?: boolean, model?: unknown): Lite | null { if (!entity) return null; if (fat) - return toLiteFat(entity, toStr); + return toLiteFat(entity, model); if (entity.id == undefined) throw new Error(`The ${entity.Type} has no Id`); @@ -162,17 +222,17 @@ export function toLite(entity: T | null | undefined, fat?: boo return { EntityType: entity.Type, id: entity.id, - toStr: toStr || getToString(entity), + model: model || createLiteModel(entity), } } -export function toLiteFat(entity: T, toStr?: string): Lite { +export function toLiteFat(entity: T, model?: unknown): Lite { return { entity: entity, EntityType: entity.Type, id: entity.id, - toStr: toStr || getToString(entity), + model: model || createLiteModel(entity), } } diff --git a/Signum.React/Scripts/Signum.Entities.ts b/Signum.React/Scripts/Signum.Entities.ts index 6fa7514870..718a07c405 100644 --- a/Signum.React/Scripts/Signum.Entities.ts +++ b/Signum.React/Scripts/Signum.Entities.ts @@ -16,7 +16,7 @@ export interface ModifiableEntity { } export function liteKeyLong(lite: Lite) { - return lite.EntityType + ";" + (lite.id == undefined ? "" : lite.id) + ";" + lite.toStr; + return lite.EntityType + ";" + (lite.id == undefined ? "" : lite.id) + ";" + getToString(lite); } export interface Entity extends ModifiableEntity { @@ -64,10 +64,12 @@ export function toMList(array: T[]): MList { } export interface Lite { - entity?: T; EntityType: string; id?: number | string; - toStr?: string; + model?: unknown; + + ModelType?: string; + entity?: T; } export interface ModelState { @@ -93,12 +95,62 @@ export function registerToString(type: Type, toSt toStringDictionary[type.typeName] = toStringFunc as ((e: ModifiableEntity) => string) | null; } + +export function registerCustomModelConsturctor(type: Type, modelType: Type, constructLiteModel: ((e: T) => M)) { + var ti = Reflection.tryGetTypeInfo(type.typeName); + + if (ti) { + var clm = ti.customLiteModels?.[modelType.typeName]; + + if (clm == null) + throw new Error(`Type ${type.typeName} has no registered Lite Model '${modelType}'`); + + clm.constructorFunction = constructLiteModel as any as (e: Entity) => ModelEntity; + } +} + import * as Reflection from './Reflection' +import { object } from 'prop-types'; export function newNiceName(ti: Reflection.TypeInfo) { return FrameMessage.New0_G.niceToString().forGenderAndNumber(ti.gender).formatWith(ti.niceName); } +function createLiteModel(e: Entity, modelType?: string): ModelEntity | string { + + var ti = Reflection.tryGetTypeInfo(e.Type); + + if (ti == null) + return getToString(e); + + modelType ??= getDefaultLiteModelType(ti); + + if (modelType == "string") + return getToString(e); + + var clm = ti.customLiteModels?.[modelType]; + + if (clm == null) + throw new Error(`Type ${e.Type} has no registered Lite Model '${modelType}'`); + + if (clm.constructorFunction) + return clm.constructorFunction(e); + + if (clm.constructorFunctionString == null) + throw new Error(`No constructor function for '${modelType}' provided`); + + clm.constructorFunction = compileFunction(clm.constructorFunctionString); + + return clm.constructorFunction!(e); +} + +function getDefaultLiteModelType(ti: Reflection.TypeInfo) { + if (!ti.customLiteModels) + return "string"; + + return Object.keys(ti.customLiteModels).singleOrNull(modelType => ti.customLiteModels![modelType].isDefault) ?? "string" +} + function getOrCreateToStringFunction(type: string) { let f = toStringDictionary[type]; if (f || f === null) @@ -106,28 +158,30 @@ function getOrCreateToStringFunction(type: string) { const ti = Reflection.tryGetTypeInfo(type); - const getToString2 = getToString; - const newNiceName2 = newNiceName; - - try { - const getToString = getToString2; - const valToString = Reflection.valToString; - const numberToString = Reflection.numberToString; - const dateToString = Reflection.dateToString; - const timeToString = Reflection.timeToString; - const getTypeInfo = Reflection.getTypeInfo; - const newNiceName = newNiceName2; - - f = ti && ti.toStringFunction ? eval("(" + ti.toStringFunction + ")") : null; - } catch (e) { - f = null; - } - - toStringDictionary[type] = f; + toStringDictionary[type] = ti?.toStringFunction ? compileFunction(ti.toStringFunction) : null; return f; } +function compileFunction(functionString: string): (e: any) => any { + + var func = new Function("e", "fd", functionString); + + var funcDeps = { + getToString: getToString, + valToString: Reflection.valToString, + numberToString: Reflection.numberToString, + dateToString: Reflection.dateToString, + timeToString: Reflection.timeToString, + getTypeInfo: Reflection.getTypeInfo, + newNiceName: newNiceName, + New : Reflection.New, + }; + + return e => func(e, funcDeps); +} + + export function getToString(entityOrLite: ModifiableEntity | Lite | undefined | null, toStringLite?: (e : Entity) => string): string { if (entityOrLite == null) return ""; @@ -140,7 +194,13 @@ export function getToString(entityOrLite: ModifiableEntity | Lite | unde if (Reflection.isLowPopulationSymbol(lite.EntityType)) return Reflection.symbolNiceName(lite as Lite); - return lite.toStr || lite.EntityType; + if (typeof lite.model == "string") + return lite.model; + + if (isModifiableEntity(lite.model)) + return getToString(lite.model); + + return lite.EntityType; } const entity = entityOrLite as ModifiableEntity; @@ -154,14 +214,14 @@ export function getToString(entityOrLite: ModifiableEntity | Lite | unde return entity.toStr || entity.Type; } -export function toLite(entity: T, fat?: boolean, toStr?: string): Lite; -export function toLite(entity: T | null | undefined, fat?: boolean, toStr?: string): Lite | null; -export function toLite(entity: T | null | undefined, fat?: boolean, toStr?: string): Lite | null { +export function toLite(entity: T, fat?: boolean, model?: unknown): Lite; +export function toLite(entity: T | null | undefined, fat?: boolean, model?: unknown): Lite | null; +export function toLite(entity: T | null | undefined, fat?: boolean, model?: unknown): Lite | null { if (!entity) return null; if (fat) - return toLiteFat(entity, toStr); + return toLiteFat(entity, model); if (entity.id == undefined) throw new Error(`The ${entity.Type} has no Id`); @@ -169,17 +229,17 @@ export function toLite(entity: T | null | undefined, fat?: boo return { EntityType: entity.Type, id: entity.id, - toStr: toStr || getToString(entity), + model: model || createLiteModel(entity), } } -export function toLiteFat(entity: T, toStr?: string): Lite { +export function toLiteFat(entity: T, model?: unknown): Lite { return { entity: entity, EntityType: entity.Type, id: entity.id, - toStr: toStr || getToString(entity), + model: model || createLiteModel(entity), } } diff --git a/Signum.Test/Environment/Entities.cs b/Signum.Test/Environment/Entities.cs index b4f413ef71..06189879fe 100644 --- a/Signum.Test/Environment/Entities.cs +++ b/Signum.Test/Environment/Entities.cs @@ -176,6 +176,22 @@ public enum AwardResult Nominated } +public class AwardLiteModel : ModelEntity +{ + [StringLengthValidator(Max = 100)] + public string Type { get; set; } + + [StringLengthValidator(Max = 100)] + public string Category { get; set; } + + public int Year { get; set; } + + public override string ToString() + { + return $"{Category} {Year}"; + } +} + public class GrammyAwardEntity : AwardEntity { } @@ -312,7 +328,9 @@ public class AwardNominationEntity : Entity, ICanBeOrdered public Lite Author { get; set; } [ForceNullable] - [ImplementedBy(typeof(GrammyAwardEntity), typeof(PersonalAwardEntity), typeof(AmericanMusicAwardEntity)), NotNullValidator(Disabled = true)] + [LiteModel(typeof(AwardLiteModel), ForEntityType = typeof(GrammyAwardEntity))] + [ImplementedBy(typeof(GrammyAwardEntity), typeof(PersonalAwardEntity), typeof(AmericanMusicAwardEntity))] + [NotNullValidator(Disabled = true)] public Lite Award { get; set; } public int Year { get; set; } diff --git a/Signum.Test/Environment/MusicStarter.cs b/Signum.Test/Environment/MusicStarter.cs index 99e33c4c5a..75abc5d602 100644 --- a/Signum.Test/Environment/MusicStarter.cs +++ b/Signum.Test/Environment/MusicStarter.cs @@ -60,6 +60,9 @@ public static void Start(string connectionString) sb.Schema.Settings.FieldAttributes((OperationLogEntity ol) => ol.User).Add(new ImplementedByAttribute()); sb.Schema.Settings.FieldAttributes((ExceptionEntity e) => e.User).Add(new ImplementedByAttribute()); + Lite.RegisterLiteModelConstructor((AmericanMusicAwardEntity a) => new AwardLiteModel { Category = a.Category, Year = a.Year, Type = "AMA" }); + Lite.RegisterLiteModelConstructor((GrammyAwardEntity a) => new AwardLiteModel { Category = a.Category, Year = a.Year, Type = "Grammy" }, isDefault: false); + if (Connector.Current.SupportsTemporalTables) { sb.Schema.Settings.TypeAttributes().Add(new SystemVersionedAttribute()); diff --git a/Signum.Test/LinqProvider/ExpandTest.cs b/Signum.Test/LinqProvider/ExpandTest.cs index 207462b51b..f12dd3b5f3 100644 --- a/Signum.Test/LinqProvider/ExpandTest.cs +++ b/Signum.Test/LinqProvider/ExpandTest.cs @@ -15,19 +15,19 @@ public ExpandTest() [Fact] public void ExpandToStringNull() { - Database.Query().Select(a => a.ToLite()).ExpandLite(a => a, ExpandLite.ToStringNull).ToList(); + Database.Query().Select(a => a.ToLite()).ExpandLite(a => a, ExpandLite.ModelNull).ToList(); } [Fact] public void ExpandToStringLazy() { - Database.Query().Select(a => a.ToLite()).ExpandLite(a => a, ExpandLite.ToStringLazy).ToList(); + Database.Query().Select(a => a.ToLite()).ExpandLite(a => a, ExpandLite.ModelLazy).ToList(); } [Fact] public void ExpandToStringEager() { - Database.Query().Select(a => a.ToLite()).ExpandLite(a => a, ExpandLite.ToStringEager).ToList(); + Database.Query().Select(a => a.ToLite()).ExpandLite(a => a, ExpandLite.ModelEager).ToList(); } [Fact] diff --git a/Signum.Test/LinqProvider/SelectLiteModel.cs b/Signum.Test/LinqProvider/SelectLiteModel.cs new file mode 100644 index 0000000000..6ebc4d83d0 --- /dev/null +++ b/Signum.Test/LinqProvider/SelectLiteModel.cs @@ -0,0 +1,31 @@ + +namespace Signum.Test.LinqProvider; + +///

+/// Summary description for LinqProvider +/// +public class SelectLiteModel +{ + public SelectLiteModel() + { + MusicStarter.StartAndLoad(); + Connector.CurrentLogger = new DebugTextWriter(); + } + + [Fact] + public void SelectAwardLiteModel() + { + var awards = Database.Query().Where(a => a.Award != null).Select(a => a.Award).ToList(); + + foreach (var a in awards) + { + if (a is Lite) + Assert.True(a.Model is AwardLiteModel); //Override for type + else if (a is Lite) + Assert.True(a.Model is AwardLiteModel); //Property Attribute + else + Assert.True(a.Model is string); //Globally + + } + } +} diff --git a/Signum.Upgrade/ApplicationRenamer.cs b/Signum.Upgrade/ApplicationRenamer.cs new file mode 100644 index 0000000000..aaa3843d6d --- /dev/null +++ b/Signum.Upgrade/ApplicationRenamer.cs @@ -0,0 +1,104 @@ +using LibGit2Sharp; +using Signum.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Signum.Upgrade; + +internal class ApplicationRenamer +{ + public static void RenameApplication(UpgradeContext uctx) + { + Console.WriteLine($"This class will help you rename all the files names and file content from '{uctx.ApplicationName}' to a new name"); + + while (CodeUpgradeRunner.IsDirtyExceptSubmodules(uctx.RootFolder)) + { + Console.WriteLine(); + Console.WriteLine("There are changes in the git repo. Commit or reset the changes and press [Enter]"); + Console.ReadLine(); + } + + var newName = SafeConsole.AskString("New name?", s => Regex.IsMatch(s, @"[A-Z][a-zA-Z0-9]+") ? null : "New name should start by Upercase and then continue with numbers or letters"); + InternalRenameDirectoryTree(new DirectoryInfo(uctx.RootFolder), UpgradeContext.DefaultIgnoreDirectories, n => + { + return n + .Replace(uctx.ApplicationName.ToLower(), newName.ToLower()) + .Replace(uctx.ApplicationName.ToUpper(), newName.ToUpper()) + .Replace(uctx.ApplicationName, newName); + }); + Commit(uctx, $"Rename file names from {uctx.ApplicationName} to {newName}"); + + RenameContent(uctx, newName); + Commit(uctx, $"Rename content from {uctx.ApplicationName} to {newName}"); + } + + private static void RenameContent(UpgradeContext uctx, string newName) + { + uctx.ForeachCodeFile("*.*", c => + { + c.Replace(uctx.ApplicationName.ToLower(), newName.ToLower()); + c.Replace(uctx.ApplicationName.ToUpper(), newName.ToUpper()); + c.Replace(uctx.ApplicationName, newName); + }); + } + + static void InternalRenameDirectoryTree(DirectoryInfo di, string[] ignoreDirectoryNames, Func renamingRule) + { + foreach (var item in di.GetFileSystemInfos()) + { + var subdir = item as DirectoryInfo; + if (subdir != null) + { + if (!ignoreDirectoryNames.Contains(subdir.Name)) + { + InternalRenameDirectoryTree(subdir, ignoreDirectoryNames, renamingRule); + + var currentName = subdir.Name; + var newName = renamingRule(currentName); + if (currentName != newName) + { + var newDirname = Path.Combine(subdir.Parent!.FullName, newName); + if (Directory.Exists(newDirname) && SafeConsole.Ask($"{newDirname} already exist. Delete?")) + Directory.Delete(newDirname, true); + subdir.MoveTo(newDirname); + } + } + } + + var file = item as FileInfo; + if (file != null) + { + var currentName = Path.GetFileNameWithoutExtension(file.Name); + var newName = renamingRule(currentName); + if (currentName != newName) + { + var newFilename = Path.Combine(file.DirectoryName!, newName + file.Extension); + file.MoveTo(newFilename); + } + } + } + + } + + private static void Commit(UpgradeContext uctx, string message) + { + using (Repository rep = new Repository(uctx.RootFolder)) + { + if (rep.RetrieveStatus().IsDirty) + { + Commands.Stage(rep, "*"); + var sign = rep.Config.BuildSignature(DateTimeOffset.Now); + rep.Commit(message, sign, sign); + SafeConsole.WriteLineColor(ConsoleColor.White, "A commit with text message '{0}' has been created".FormatWith(message)); + } + else + { + Console.WriteLine("Nothing to commit"); + } + } + } +} diff --git a/Signum.Upgrade/CodeUpgradeRunner.cs b/Signum.Upgrade/CodeUpgradeRunner.cs index 8d772de761..780956e524 100644 --- a/Signum.Upgrade/CodeUpgradeRunner.cs +++ b/Signum.Upgrade/CodeUpgradeRunner.cs @@ -92,7 +92,7 @@ bool Prompt(UpgradeContext uctx, string signumUpgradeFile) } - static bool IsDirtyExceptSubmodules(string folder) + internal static bool IsDirtyExceptSubmodules(string folder) { using (Repository rep = new Repository(folder)) { diff --git a/Signum.Upgrade/Program.cs b/Signum.Upgrade/Program.cs index 1deb7e847e..4f579e0d98 100644 --- a/Signum.Upgrade/Program.cs +++ b/Signum.Upgrade/Program.cs @@ -21,7 +21,7 @@ static void Main(string[] args) Console.Write(" ApplicationName = "); SafeConsole.WriteLineColor(ConsoleColor.DarkGray, uctx.ApplicationName); - //UpgradeContext.DefaultIgnoreDirectories = UpgradeContext.DefaultIgnoreDirectories.Where(a => a != "Framework").ToArray(); + UpgradeContext.DefaultIgnoreDirectories = UpgradeContext.DefaultIgnoreDirectories.Where(a => a != "Framework").ToArray(); new CodeUpgradeRunner(autoDiscover: true).Run(uctx); } diff --git a/Signum.Upgrade/RegexExtensions.cs b/Signum.Upgrade/RegexExtensions.cs new file mode 100644 index 0000000000..11fef0e873 --- /dev/null +++ b/Signum.Upgrade/RegexExtensions.cs @@ -0,0 +1,15 @@ +namespace Signum.Upgrade; + +public static class RegexExtensions +{ + public static Regex WithMacros(this Regex regex) + { + var pattern = regex.ToString(); + + var newPattern = pattern + .Replace("::EXPR::", new Regex(@"(?:[^()]|(?[(])|(?<-Open>[)]))+").ToString()) /*Just for validation and coloring*/ + .Replace("::IDENT::", new Regex(@"([a-zA-Z_][0-9a-zA-Z_]*)").ToString()); + + return new Regex(newPattern, regex.Options); + } +} diff --git a/Signum.Upgrade/Upgrades/Upgrade_20200920_remove_numbro.cs b/Signum.Upgrade/Upgrades/Upgrade_20200920_remove_numbro.cs index 5f97629ee0..cbb9dcdd3f 100644 --- a/Signum.Upgrade/Upgrades/Upgrade_20200920_remove_numbro.cs +++ b/Signum.Upgrade/Upgrades/Upgrade_20200920_remove_numbro.cs @@ -46,15 +46,3 @@ public override void Execute(UpgradeContext uctx) } - -public static class RegexExpressions -{ - public static Regex WithMacros(this Regex regex) - { - var pattern = regex.ToString(); - - var newPattern = pattern.Replace("::EXPR::", new Regex(@"(?:[^()]|(?[(])|(?<-Open>[)]))+").ToString()); /*Just for validation and coloring*/ - - return new Regex(newPattern, regex.Options); - } -} diff --git a/Signum.Upgrade/Upgrades/Upgrade_20220623_ToStrGetToString.cs b/Signum.Upgrade/Upgrades/Upgrade_20220623_ToStrGetToString.cs new file mode 100644 index 0000000000..09b16775ae --- /dev/null +++ b/Signum.Upgrade/Upgrades/Upgrade_20220623_ToStrGetToString.cs @@ -0,0 +1,16 @@ +namespace Signum.Upgrade.Upgrades; + +class Upgrade_20220623_ToStrGetToString : CodeUpgradeBase +{ + public override string Description => "Replace a.toStr to getToString(a) in .ts/.tsx"; + + static Regex Regex = new Regex(@"(?(::IDENT::)((\?|\!)?\.::IDENT::)*)(\?|\!)?\.toStr\b").WithMacros(); + + public override void Execute(UpgradeContext uctx) + { + uctx.ForeachCodeFile(@"*.tsx, *.ts", file => + { + file.Replace(Regex, m => "getToString(" + m.Groups["expr"] + ")"); + }); + } +} diff --git a/Signum.Utilities/ExpressionTrees/ExpressionComparer.cs b/Signum.Utilities/ExpressionTrees/ExpressionComparer.cs index dd8e468c10..892187e39a 100644 --- a/Signum.Utilities/ExpressionTrees/ExpressionComparer.cs +++ b/Signum.Utilities/ExpressionTrees/ExpressionComparer.cs @@ -288,7 +288,7 @@ protected static bool CompareList(ReadOnlyCollection? a, ReadOnlyCollectio return true; } - protected static bool CompareDictionaries(ReadOnlyDictionary a, ReadOnlyDictionary b, Func comparer) + protected static bool CompareDictionaries(ReadOnlyDictionary? a, ReadOnlyDictionary? b, Func comparer) where K : notnull { if (a == b)