diff --git a/Signum.Engine.Extensions/Cache/CachedTable.cs b/Signum.Engine.Extensions/Cache/CachedTable.cs index d0bfeab937..e1baba3e69 100644 --- a/Signum.Engine.Extensions/Cache/CachedTable.cs +++ b/Signum.Engine.Extensions/Cache/CachedTable.cs @@ -215,7 +215,7 @@ public CachedTable(ICacheLogicController controller, AliasGenerator? aliasGenera { object obj = rowReader(fr); result[idGetter(obj)] = obj; //Could be repeated joins - }); + }); tr.Commit(); } diff --git a/Signum.Engine.Extensions/Cache/CachedTableConstructor.cs b/Signum.Engine.Extensions/Cache/CachedTableConstructor.cs index d40b709d32..5d7870ec76 100644 --- a/Signum.Engine.Extensions/Cache/CachedTableConstructor.cs +++ b/Signum.Engine.Extensions/Cache/CachedTableConstructor.cs @@ -140,7 +140,9 @@ public Expression MaterializeField(Field field) if (field is FieldImplementedByAll iba) { - Expression id = GetTupleProperty(iba.Column); + Expression id = iba.IdColumns.Values + .Select(c => (Expression)Expression.Convert(GetTupleProperty(c), typeof(IComparable))) + .Aggregate((a, b) => Expression.Coalesce(a, b)); Expression typeId = GetTupleProperty(iba.TypeColumn); if (isLite) @@ -148,11 +150,11 @@ public Expression MaterializeField(Field field) var liteCreate = Expression.Call(miGetIBALite.MakeGenericMethod(field.FieldType.CleanType()), Expression.Constant(Schema.Current), NewPrimaryKey(typeId.UnNullify()), - id.UnNullify()); + id); var liteRequest = Expression.Call(retriever, miRequestLite.MakeGenericMethod(Lite.Extract(field.FieldType)!), liteCreate); - return Expression.Condition(Expression.NotEqual(WrapPrimaryKey(id), NullId), liteRequest, nullRef); + return Expression.Condition(Expression.NotEqual(typeId.Nullify(), Expression.Constant(null, iba.TypeColumn.Type.Nullify())), liteRequest, nullRef); } else { @@ -311,11 +313,11 @@ internal static Expression WrapPrimaryKey(Expression expression) static MethodInfo miGetIBALite = ReflectionTools.GetMethodInfo((Schema s) => GetIBALite(null!, 1, "")).GetGenericMethodDefinition(); - public static Lite GetIBALite(Schema schema, PrimaryKey typeId, string id) where T : Entity + public static Lite GetIBALite(Schema schema, PrimaryKey typeId, IComparable id) where T : Entity { Type type = schema.GetType(typeId); - return (Lite)Lite.Create(type, PrimaryKey.Parse(id, type)); + return (Lite)Lite.Create(type, new PrimaryKey(id)); } public static MemberExpression peModified = Expression.Property(retriever, ReflectionTools.GetPropertyInfo((IRetriever me) => me.ModifiedState)); diff --git a/Signum.Engine/BulkInserter.cs b/Signum.Engine/BulkInserter.cs index fb4375e792..dbf7d5f2b7 100644 --- a/Signum.Engine/BulkInserter.cs +++ b/Signum.Engine/BulkInserter.cs @@ -161,7 +161,8 @@ public static int BulkInsertTable(IEnumerable entities, if (!e.IsNew) throw new InvalidOperationException("Entites should be new"); t.SetToStrField(e); - dt.Rows.Add(t.BulkInsertDataRow(e)); + var values = t.BulkInsertDataRow(e); + dt.Rows.Add(values); } } diff --git a/Signum.Engine/Engine/SchemaSynchronizer.cs b/Signum.Engine/Engine/SchemaSynchronizer.cs index 4e459a1991..200d01dbbf 100644 --- a/Signum.Engine/Engine/SchemaSynchronizer.cs +++ b/Signum.Engine/Engine/SchemaSynchronizer.cs @@ -56,9 +56,14 @@ public static class SchemaSynchronizer { var key = Replacements.KeyColumnsForTable(tn); + //START IBA Migration 2022.04.04 + var newIBAs = tab.Columns.OfType().Where(a => !diff.Columns.ContainsKey(a.Name) && diff.Columns.ContainsKey(a.Name.BeforeLast("_"))).Select(a => a.Name).ToList(); + var oldIBAs = diff.Columns.Values.Where(c => !tab.Columns.ContainsKey(c.Name) && Schema.Current.Settings.ImplementedByAllPrimaryKeyTypes.All(t => tab.Columns.TryGetC(c.Name + "_" + t.Name) is ImplementedByAllIdColumn)).Select(a => a.Name).ToList(); + //END + replacements.AskForReplacements( - diff.Columns.Keys.ToHashSet(), - tab.Columns.Keys.ToHashSet(), key); + diff.Columns.Keys.Except(oldIBAs).ToHashSet(), + tab.Columns.Keys.Except(newIBAs).ToHashSet(), key); var incompatibleTypes = diff.Columns.JoinDictionary(tab.Columns, (cn, diff, col) => new { cn, diff, col }).Values.Where(a => !a.diff.CompatibleTypes(a.col) || a.diff.Identity != a.col.Identity).ToList(); @@ -233,15 +238,55 @@ public static class SchemaSynchronizer tab.Columns, dif.Columns, - createNew: (cn, tabCol) => SqlPreCommand.Combine(Spacing.Simple, - tabCol.PrimaryKey && dif.PrimaryKeyName != null ? sqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, - AlterTableAddColumnDefault(sqlBuilder, tab, tabCol, replacements, - forceDefaultValue: cn.EndsWith("_HasValue") && dif.Columns.Values.Any(c => c.Name.StartsWith(cn.Before("HasValue")) && c.Nullable == false) ? "1" : null, - hasValueFalse: hasValueFalse)), + createNew: (cn, tabCol) => + { + //START IBA Migration 2022.04.04 + var settings = Schema.Current.Settings; + var isNewImplementedIBAColumn = tabCol is ImplementedByAllIdColumn && settings.ImplementedByAllPrimaryKeyTypes.Any(t => + { + var before = tabCol.Name.TryBefore("_" + t.Name); + return before != null && dif.Columns.ContainsKey(before) && !tab.Columns.ContainsKey(before); + }); + //END + + var result = SqlPreCommand.Combine(Spacing.Simple, + tabCol.PrimaryKey && dif.PrimaryKeyName != null ? sqlBuilder.DropPrimaryKeyConstraint(tab.Name) : null, + AlterTableAddColumnDefault(sqlBuilder, tab, tabCol, replacements, + forceDefaultValue: cn.EndsWith("_HasValue") && dif.Columns.Values.Any(c => c.Name.StartsWith(cn.Before("HasValue")) && c.Nullable == false) ? "1" : null, + hasValueFalse: hasValueFalse, + avoidDefault: isNewImplementedIBAColumn && tabCol.Nullable == IsNullable.Forced)); + + return result; + }, + + removeOld: (cn, difCol) => + { - removeOld: (cn, difCol) => SqlPreCommand.Combine(Spacing.Simple, - difCol.DefaultConstraint != null && difCol.DefaultConstraint.Name != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint!.Name) : null, - sqlBuilder.AlterTableDropColumn(tab, cn)), + var result = SqlPreCommand.Combine(Spacing.Simple, + difCol.DefaultConstraint != null && difCol.DefaultConstraint.Name != null ? sqlBuilder.AlterTableDropConstraint(tab.Name, difCol.DefaultConstraint!.Name) : null, + sqlBuilder.AlterTableDropColumn(tab, cn)); + + + //START IBA Migration 2022.04.04 + if (difCol.DbType.IsString()) + { + var settings = Schema.Current.Settings; + + var update = (from t in settings.ImplementedByAllPrimaryKeyTypes + let c = tab.Columns.TryGetC(difCol.Name + "_" + t.Name) + where c is ImplementedByAllIdColumn + select new SqlPreCommandSimple($"UPDATE {tab.Name} SET {c.Name} = TRY_CONVERT({settings.GetSqlDbTypePair(t).DbType}, {difCol.Name})")).Combine(Spacing.Simple); + + if (update != null) + { + update.GoBefore = true; + return SqlPreCommandSimple.Combine(Spacing.Double, update, result); + } + } + //END + + return result; + }, mergeBoth: (cn, tabCol, difCol) => { @@ -539,9 +584,9 @@ private static bool DifferentDatabase(ObjectName name, ObjectName name2) public static Func IgnoreSchema = s => s.Name.Contains("\\"); - private static SqlPreCommand AlterTableAddColumnDefault(SqlBuilder sqlBuilder, ITable table, IColumn column, Replacements rep, string? forceDefaultValue, HashSet hasValueFalse) + private static SqlPreCommand AlterTableAddColumnDefault(SqlBuilder sqlBuilder, ITable table, IColumn column, Replacements rep, string? forceDefaultValue, bool avoidDefault, HashSet hasValueFalse) { - if (column.Nullable == IsNullable.Yes || column.Identity || column.Default != null || column is ImplementationColumn) + if (column.Nullable == IsNullable.Yes || column.Identity || column.Default != null || column is ImplementationColumn || avoidDefault) return sqlBuilder.AlterTableAddColumn(table, column); if (column.Nullable == IsNullable.Forced) diff --git a/Signum.Engine/Linq/DbExpressions.Signum.cs b/Signum.Engine/Linq/DbExpressions.Signum.cs index b429f69aaa..d6fe6ce56d 100644 --- a/Signum.Engine/Linq/DbExpressions.Signum.cs +++ b/Signum.Engine/Linq/DbExpressions.Signum.cs @@ -57,8 +57,8 @@ public override string ToString() ExternalId.ToString()); return constructor + - (Bindings == null ? null : ("\r\n{\r\n " + Bindings.ToString(",\r\n ").Indent(4) + "\r\n}")) + - (Mixins == null ? null : ("\r\n" + Mixins.ToString(m => ".Mixin({0})".FormatWith(m), "\r\n"))); + (Bindings == null ? null : ("\n{\n " + Bindings.ToString(",\n ").Indent(4) + "\n}")) + + (Mixins == null ? null : ("\n" + Mixins.ToString(m => ".Mixin({0})".FormatWith(m), "\n"))); } public Expression GetBinding(FieldInfo fi) @@ -131,10 +131,10 @@ public override string ToString() { string constructor = "new {0}".FormatWith(Type.TypeName()); - string bindings = Bindings?.Let(b => b.ToString(",\r\n ")) ?? ""; + string bindings = Bindings?.Let(b => b.ToString(",\n ")) ?? ""; return bindings.HasText() ? - constructor + "\r\n{" + bindings.Indent(4) + "\r\n}" : + constructor + "\n{" + bindings.Indent(4) + "\n}" : constructor; } @@ -197,10 +197,10 @@ public override string ToString() { string constructor = "new {0}".FormatWith(Type.TypeName()); - string bindings = Bindings?.Let(b => b.ToString(",\r\n ")) ?? ""; + string bindings = Bindings?.Let(b => b.ToString(",\n ")) ?? ""; return bindings.HasText() ? - constructor + "\r\n{" + bindings.Indent(4) + "\r\n}" : + constructor + "\n{" + bindings.Indent(4) + "\n}" : constructor; } @@ -251,8 +251,8 @@ public ImplementedByExpression(Type type, CombineStrategy strategy, IDictionary< public override string ToString() { - return "ImplementedBy({0}){{\r\n{1}\r\n}}".FormatWith(Strategy, - Implementations.ToString(kvp => "{0} -> {1}".FormatWith(kvp.Key.TypeName(), kvp.Value.ToString()), "\r\n").Indent(4) + return "ImplementedBy({0}){{\n{1}\n}}".FormatWith(Strategy, + Implementations.ToString(kvp => "{0} -> {1}".FormatWith(kvp.Key.TypeName(), kvp.Value.ToString()), "\n").Indent(4) ); } @@ -269,20 +269,22 @@ internal class ImplementedByAllExpression : DbExpression public readonly IntervalExpression? ExternalPeriod; - public ImplementedByAllExpression(Type type, ReadOnlyDictionary ids, TypeImplementedByAllExpression typeId, IntervalExpression? externalPeriod) + public ImplementedByAllExpression(Type type, IDictionary ids, TypeImplementedByAllExpression typeId, IntervalExpression? externalPeriod) : base(DbExpressionType.ImplementedByAll, type) { if (ids == null) throw new ArgumentNullException(nameof(ids)); - this.Ids = ids; + this.Ids = ids.ToReadOnly(); this.TypeId = typeId ?? throw new ArgumentNullException(nameof(typeId)); this.ExternalPeriod = externalPeriod; } public override string ToString() { - return "ImplementedByAll{{ Ids = {0}, Type = {1} }}".FormatWith(Ids, TypeId); + return "ImplementedByAll{{\n Ids = {0},\n Type = {1}\n}}".FormatWith( + Ids.ToString(kvp => "{0} -> {1}".FormatWith(kvp.Key.TypeName(), kvp.Value.ToString()), "\n"), + TypeId); } protected override Expression Accept(DbExpressionVisitor visitor) @@ -345,11 +347,11 @@ internal LiteReferenceExpression WithExpandLite(ExpandLite expandLite) internal class LiteValueExpression : DbExpression { public readonly Expression TypeId; - public readonly Expression Id; + public readonly PrimaryKeyExpression Id; public readonly Expression? ToStr; - public LiteValueExpression(Type type, Expression typeId, Expression id, Expression? toStr) : + public LiteValueExpression(Type type, Expression typeId, PrimaryKeyExpression id, Expression? toStr) : base(DbExpressionType.LiteValue, type) { this.TypeId = typeId ?? throw new ArgumentNullException(nameof(typeId)); @@ -368,7 +370,15 @@ protected override Expression Accept(DbExpressionVisitor visitor) } } -internal class TypeEntityExpression : DbExpression +internal abstract class TypeDbExpression : DbExpression +{ + public TypeDbExpression(DbExpressionType dbType, Type type) + : base(dbType, type) + { + } +} + +internal class TypeEntityExpression : TypeDbExpression { public readonly PrimaryKeyExpression ExternalId; public readonly Type TypeValue; @@ -391,7 +401,7 @@ protected override Expression Accept(DbExpressionVisitor visitor) } } -internal class TypeImplementedByExpression : DbExpression +internal class TypeImplementedByExpression : TypeDbExpression { public readonly ReadOnlyDictionary TypeImplementations; @@ -415,7 +425,8 @@ protected override Expression Accept(DbExpressionVisitor visitor) } } -internal class TypeImplementedByAllExpression : DbExpression + +internal class TypeImplementedByAllExpression : TypeDbExpression { public readonly PrimaryKeyExpression TypeColumn; @@ -539,7 +550,7 @@ public MListElementExpression(PrimaryKeyExpression rowId, EntityExpression paren public override string ToString() { - return "MListElement({0})\r\n{{\r\nParent={1},\r\nOrder={2},\r\nElement={3}}})".FormatWith( + return "MListElement({0})\n{{\nParent={1},\nOrder={2},\nElement={3}}})".FormatWith( RowId.ToString(), Parent.ToString(), Order?.ToString(), diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs index 604a32989f..1b52a243b5 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionNominator.cs @@ -138,7 +138,7 @@ protected internal override Expression VisitLiteReference(LiteReferenceExpressio if (lite.Reference is ImplementedByExpression ib) return Add(GetImplmentedById(ib)); if (lite.Reference is ImplementedByAllExpression iba) - return Add(iba.Id); + return Add(iba.TypeId.TypeColumn.Value); } return base.VisitLiteReference(lite); @@ -187,7 +187,7 @@ protected internal override Expression VisitTypeImplementedBy(TypeImplementedByE protected internal override Expression VisitImplementedByAll(ImplementedByAllExpression iba) { if (iba == isNotNullRoot) - return Add(iba.Id); + return Add(iba.TypeId.TypeColumn.Value); return base.VisitImplementedByAll(iba); } @@ -1416,9 +1416,6 @@ protected override Expression VisitMember(MemberExpression m) if (exp is UnaryExpression ue) exp = ue.Operand; - if (exp is PrimaryKeyStringExpression) - return null; - var pk = (PrimaryKeyExpression)exp; return Visit(pk.Value); diff --git a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs index fb0f3e1a68..98321edf57 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/DbExpressionVisitor.cs @@ -69,7 +69,7 @@ protected internal virtual Expression VisitLiteReference(LiteReferenceExpression protected internal virtual Expression VisitLiteValue(LiteValueExpression lite) { var newTypeId = Visit(lite.TypeId); - var newId = Visit(lite.Id); + 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); @@ -205,12 +205,12 @@ protected internal virtual Expression VisitColumn(ColumnExpression column) protected internal virtual Expression VisitImplementedByAll(ImplementedByAllExpression iba) { - var id = Visit(iba.Id); + var ids = Visit(iba.Ids, v => Visit(v)); var typeId = (TypeImplementedByAllExpression)Visit(iba.TypeId); var externalPeriod = (IntervalExpression?)Visit(iba.ExternalPeriod); - if (id != iba.Id || typeId != iba.TypeId || externalPeriod != iba.ExternalPeriod) - return new ImplementedByAllExpression(iba.Type, id, typeId, externalPeriod); + if (ids != iba.Ids || typeId != iba.TypeId || externalPeriod != iba.ExternalPeriod) + return new ImplementedByAllExpression(iba.Type, ids, typeId, externalPeriod); return iba; } diff --git a/Signum.Engine/Linq/ExpressionVisitor/EntityCompleter.cs b/Signum.Engine/Linq/ExpressionVisitor/EntityCompleter.cs index f6cc0bf9b0..00206c1f7d 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/EntityCompleter.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/EntityCompleter.cs @@ -30,10 +30,7 @@ protected internal override Expression VisitLiteReference(LiteReferenceExpressio if (lite.EagerEntity) return base.VisitLiteReference(lite); - var id = lite.Reference is ImplementedByAllExpression || - lite.Reference is ImplementedByExpression ib && ib.Implementations.Select(imp=>imp.Value.ExternalId.ValueType.Nullify()).Distinct().Count() > 1 ? - (Expression)binder.GetIdString(lite.Reference) : - (Expression)binder.GetId(lite.Reference); + var id = binder.GetId(lite.Reference); var typeId = binder.GetEntityType(lite.Reference); var toStr = LiteToString(lite, typeId); diff --git a/Signum.Engine/Linq/ExpressionVisitor/GroupEntityCleaner.cs b/Signum.Engine/Linq/ExpressionVisitor/GroupEntityCleaner.cs index 11dd49d58a..64f4f128f3 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/GroupEntityCleaner.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/GroupEntityCleaner.cs @@ -1,3 +1,4 @@ +using Signum.Engine.Maps; using Signum.Entities.Basics; using System.Diagnostics.CodeAnalysis; @@ -96,7 +97,10 @@ static Expression CombineEntities(Expression a, Expression b, Type type, Func combiner) { - return new ImplementedByAllExpression(type, combiner(a.Id, b.Id), + var keys = a.Ids.Keys.Union(b.Ids.Keys).ToList(); + + return new ImplementedByAllExpression(type, + keys.ToDictionary(t => t, t => combiner(a.Ids.GetOrThrow(t), b.Ids.GetOrThrow(t))), new TypeImplementedByAllExpression(new PrimaryKeyExpression(combiner(a.TypeId.TypeColumn, b.TypeId.TypeColumn))), null); } @@ -115,19 +119,21 @@ private static EntityExpression CombineEntity(EntityExpression a, EntityExpressi static ImplementedByAllExpression ToIBA(Expression node, Type type) { + var types = Schema.Current.Settings.TypeValues.Keys.ToList(); + if (node.IsNull()) - return new ImplementedByAllExpression(type, - Expression.Constant(null, typeof(string)), + return new ImplementedByAllExpression(type, + types.ToDictionary(t => t, t => (Expression)new SqlConstantExpression(null, t.Nullify())), new TypeImplementedByAllExpression(new PrimaryKeyExpression(Expression.Constant(null, PrimaryKey.Type(typeof(TypeEntity))))), null); if (node is EntityExpression e) - return new ImplementedByAllExpression(type, - new SqlCastExpression(typeof(string), e.ExternalId.Value), + return new ImplementedByAllExpression(type, + types.ToDictionary(t => t, t => t == PrimaryKey.Type(e.Type) ? e.ExternalId.Value : new SqlConstantExpression(null, t.Nullify())), new TypeImplementedByAllExpression(new PrimaryKeyExpression(QueryBinder.TypeConstant(e.Type))), null); if (node is ImplementedByExpression ib) return new ImplementedByAllExpression(type, - new PrimaryKeyExpression(QueryBinder.Coalesce(ib.Implementations.Values.Select(a => a.ExternalId.ValueType.Nullify()).Distinct().SingleEx(), ib.Implementations.Select(e => e.Value.ExternalId))), + types.ToDictionary(t => t, t => QueryBinder.Coalesce(t, ib.Implementations.Values.Where(e => PrimaryKey.Type(e.Type) == t))), new TypeImplementedByAllExpression(new PrimaryKeyExpression( ib.Implementations.Select(imp => new When(imp.Value.ExternalId.NotEqualsNulll(), QueryBinder.TypeConstant(imp.Key))).ToList() .ToCondition(PrimaryKey.Type(typeof(TypeEntity)).Nullify()))), null); diff --git a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs index 32391bc864..bb38550d96 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/QueryBinder.cs @@ -374,14 +374,14 @@ private Expression BindWithHints(Expression source, ConstantExpression hint) } - static MethodInfo miSplitCase = ReflectionTools.GetMethodInfo((Entity e) => e.CombineCase()).GetGenericMethodDefinition(); - static MethodInfo miSplitUnion = ReflectionTools.GetMethodInfo((Entity e) => e.CombineUnion()).GetGenericMethodDefinition(); + static MethodInfo miCombineCase = ReflectionTools.GetMethodInfo((Entity e) => e.CombineCase()).GetGenericMethodDefinition(); + static MethodInfo miCombineUnion = ReflectionTools.GetMethodInfo((Entity e) => e.CombineUnion()).GetGenericMethodDefinition(); private static CombineStrategy GetStrategy(MethodInfo methodInfo) { - if (methodInfo.IsInstantiationOf(miSplitCase)) + if (methodInfo.IsInstantiationOf(miCombineCase)) return CombineStrategy.Case; - if (methodInfo.IsInstantiationOf(miSplitUnion)) + if (methodInfo.IsInstantiationOf(miCombineUnion)) return CombineStrategy.Union; throw new InvalidOperationException("Method {0} not expected".FormatWith(methodInfo.Name)); @@ -641,6 +641,7 @@ private Expression BindToString(Expression source, Expression separator, MethodI } } + static MethodInfo miToString = ReflectionTools.GetMethodInfo((object obj) => obj.ToString()); static MethodInfo miStringConcat = ReflectionTools.GetMethodInfo(() => string.Concat("", "")); static (Expression newSource, LambdaExpression? selector, bool distinct) DisassembleAggregate(AggregateSqlFunction aggregate, Expression source, LambdaExpression? selectorOrPredicate, bool isRoot) @@ -1249,16 +1250,18 @@ protected virtual Expression BindOrderBy(Type resultType, Expression source, Lam this.thenBys = null; ProjectionExpression projection = this.VisitCastProjection(source); - List orderings = new List - { - new OrderExpression(orderType, GetOrderExpression(orderSelector, projection)) - }; + List orderings = GetOrderExpression(orderSelector, projection).Select(a => new OrderExpression(orderType, a)).ToList(); if (myThenBys != null) { for (int i = myThenBys.Count - 1; i >= 0; i--) { OrderExpression tb = myThenBys[i]; - orderings.Add(new OrderExpression(tb.OrderType, GetOrderExpression((LambdaExpression)tb.Expression, projection))); + var columns = GetOrderExpression((LambdaExpression)tb.Expression, projection); + + foreach (var c in columns) + { + orderings.Add(new OrderExpression(tb.OrderType, c)); + } } } @@ -1269,7 +1272,7 @@ protected virtual Expression BindOrderBy(Type resultType, Expression source, Lam pc.Projector, null, resultType); } - private Expression GetOrderExpression(LambdaExpression lambda, ProjectionExpression projection) + private Expression[] GetOrderExpression(LambdaExpression lambda, ProjectionExpression projection) { using (SetCurrentSource(projection.Select)) { @@ -1292,40 +1295,32 @@ Expression GetExpressionOrder(EntityExpression exp) return BindMethodCall(Expression.Call(exp, EntityExpression.ToStringMethod)); } - if (expr is LiteReferenceExpression lite) - { - expr = lite.Reference is ImplementedByAllExpression iba ? iba.Id : - lite.Reference is EntityExpression e ? GetExpressionOrder(e) : - lite.Reference is ImplementedByExpression ib ? DispatchIb(ib, typeof(string), ee => GetExpressionOrder(ee)) : - throw new NotImplementedException(""); - } - else if (expr is EntityExpression e) + Expression[] results = expr switch { - expr = GetExpressionOrder(e); - } - else if (expr is ImplementedByExpression ib) - { - expr = DispatchIb(ib, typeof(string), ee => GetExpressionOrder(ee)); - } - else if (expr is ImplementedByAllExpression iba) - { - expr = iba.Id; - } - else if (expr is MethodCallExpression mce && ReflectionTools.MethodEqual(mce.Method, miToUserInterface)) - { - expr = mce.Arguments[0]; - } - else if (expr.Type == typeof(Type)) - { - expr = ExtractTypeId(expr); - } + 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(""), + + EntityExpression e => new[] { GetExpressionOrder(e), e.ExternalId }, - if (expr.Type.UnNullify() == typeof(PrimaryKey)) + ImplementedByExpression ib => ib.Implementations.Values.SelectMany(e => new[] { GetExpressionOrder(e), e.ExternalId }).ToArray(), + + ImplementedByAllExpression iba => iba.Ids.Values.PreAnd(iba.TypeId).ToArray(), + + MethodCallExpression mce when ReflectionTools.MethodEqual(mce.Method, miToUserInterface) => new[] { mce.Arguments[0] }, + + var e when e.Type == typeof(Type) => new[] { ExtractTypeId(expr) }, + + var e => new[] { e }, + }; + + return results.Select(e => { - expr = SmartEqualizer.UnwrapPrimaryKey(expr); - } + var clean = e.Type.UnNullify() == typeof(PrimaryKey) ? SmartEqualizer.UnwrapPrimaryKey(e) : e; - return DbExpressionNominator.FullNominate(expr)!; + return DbExpressionNominator.FullNominate(clean); + }).ToArray(); } } @@ -1931,7 +1926,10 @@ public Expression BindMemberAccess(MemberExpression m) ImplementedByAllExpression iba = (ImplementedByAllExpression)source; FieldInfo fi = m.Member as FieldInfo ?? Reflector.FindFieldInfo(iba.Type, (PropertyInfo)m.Member); if (fi != null && fi.FieldEquals((Entity ie) => ie.id)) - return new PrimaryKeyStringExpression(iba.Id, iba.TypeId).UnNullify(); + return new PrimaryKeyStringExpression( + iba.Ids.Values.Select(a => (Expression)Expression.Call(a, miToString)).Reverse().Aggregate((a, b) => Expression.Coalesce(a, b)), + iba.TypeId + ).UnNullify(); throw new InvalidOperationException("The member {0} of ImplementedByAll is not accesible on queries".FormatWith(m.Member)); } @@ -2072,11 +2070,13 @@ private Expression CombineImplementations(ICombineStrategy strategy, Dictionary< if (expressions.Any(e => e.Value is ImplementedByAllExpression)) { - Expression id = CombineImplementations(strategy, expressions.SelectDictionary(w => GetIdString(w)), typeof(string)); + var ids = Schema.Current.Settings.ImplementedByAllPrimaryKeyTypes.ToDictionary(t => t, t => + CombineImplementations(strategy, expressions.SelectDictionary(w => GetIdAsType(w, t)), t.Nullify())); + TypeImplementedByAllExpression typeId = (TypeImplementedByAllExpression) CombineImplementations(strategy, expressions.SelectDictionary(w => GetEntityType(w)), typeof(Type)); - return new ImplementedByAllExpression(returnType, id, typeId, null); + return new ImplementedByAllExpression(returnType, ids, typeId, null); } if (expressions.All(e => e.Value is EntityExpression || e.Value is ImplementedByExpression)) @@ -2384,12 +2384,14 @@ public Expression SimplifyRedundandConverts(UnaryExpression unary) else if (operand is ImplementedByAllExpression iba) { if (uType.IsAssignableFrom(iba.Type)) - return new ImplementedByAllExpression(uType, iba.Id, iba.TypeId, iba.ExternalPeriod); + return new ImplementedByAllExpression(uType, iba.Ids, iba.TypeId, iba.ExternalPeriod); + + var pkType = PrimaryKey.Type(uType); var conditionalId = new PrimaryKeyExpression( Expression.Condition(SmartEqualizer.EqualNullable(iba.TypeId.TypeColumn.Value, TypeConstant(uType)), - new SqlCastExpression(PrimaryKey.Type(uType).Nullify(), iba.Id), - Expression.Constant(null, PrimaryKey.Type(uType).Nullify()))); + new SqlCastExpression(pkType.Nullify(), iba.Ids.GetOrThrow(pkType)), + Expression.Constant(null, pkType.Nullify()))); return new EntityExpression(uType, conditionalId, iba.ExternalPeriod, null, null, null, null, avoidExpandOnRetrieving: false); } @@ -2752,11 +2754,11 @@ private ColumnAssignment[] Assign(Expression colExpression, Expression expressio } else if (colExpression is ImplementedByAllExpression colIba && expression is ImplementedByAllExpression expIba) { - return new[] - { - AssignColumn(colIba.Id, expIba.Id), - AssignColumn(colIba.TypeId.TypeColumn.Value, expIba.TypeId.TypeColumn.Value) - }; + var ids = colIba.Ids + .Select(idc => AssignColumn(idc.Value, expIba.Ids.GetOrThrow(idc.Key))) + .ToArray(); + + return ids.PreAnd(AssignColumn(colIba.TypeId.TypeColumn.Value, expIba.TypeId.TypeColumn.Value)).ToArray(); } throw new NotImplementedException("{0} can not be assigned from expression:\n{1}".FormatWith(colExpression.Type.TypeName(), expression.ToString())); @@ -2936,11 +2938,17 @@ public PrimaryKeyExpression GetId(Expression expression) if (expression is ImplementedByExpression ib) { - var type = ib.Implementations.Select(imp => imp.Value.ExternalId.ValueType.Nullify()).Distinct().SingleOrDefaultEx() ?? typeof(int?); + var type = ib.Implementations.Select(imp => imp.Value.ExternalId.ValueType.Nullify()).Distinct().SingleOrDefaultEx() ?? typeof(IComparable); var aggregate = new PrimaryKeyExpression(Coalesce(type, ib.Implementations.Select(imp => imp.Value.ExternalId.Value))); return aggregate; } + if (expression is ImplementedByAllExpression iba) + { + var aggregate = new PrimaryKeyExpression(Coalesce(typeof(IComparable), iba.Ids.Values.Select(id => Expression.Convert(id, typeof(IComparable))))); + return aggregate; + } + if (expression.NodeType == ExpressionType.Conditional) { var con = (ConditionalExpression)expression; @@ -2968,21 +2976,28 @@ public PrimaryKeyExpression GetId(Expression expression) throw new NotSupportedException("Id for {0}".FormatWith(expression.ToString())); } - public Expression GetIdString(Expression expression) + public Expression GetIdAsType(Expression expression, Type primaryKeyType) { if (expression is EntityExpression ee) - return Expression.Convert(ee.ExternalId.Value, typeof(string)); + { + if (PrimaryKey.Type(ee.Type) == primaryKeyType) + return ee.ExternalId.Value; + + return new SqlConstantExpression(null, primaryKeyType.Nullify()); + } if (expression is ImplementedByExpression ib) { - var aggregate = Coalesce(typeof(string), - ib.Implementations.Select(imp => new SqlCastExpression(typeof(string), imp.Value.ExternalId.Value))); + var aggregate = ib.Implementations.Where(imp => PrimaryKey.Type(imp.Key) == primaryKeyType).Select(imp => imp.Value.ExternalId.Value); - return aggregate; + if (aggregate.Any()) + return aggregate.Aggregate((a, b) => Expression.Coalesce(a, b)); + + return new SqlConstantExpression(null, primaryKeyType.Nullify()); } if (expression is ImplementedByAllExpression iba) - return iba.Id; + return iba.Ids.GetOrThrow(primaryKeyType); if (expression.NodeType == ExpressionType.Conditional) { @@ -2994,16 +3009,14 @@ public Expression GetIdString(Expression expression) { var bin = (BinaryExpression)expression; - var left = GetIdString(bin.Left); - var right = GetIdString(bin.Right); + var left = GetIdAsType(bin.Left, primaryKeyType); + var right = GetIdAsType(bin.Right, primaryKeyType); - return Condition(Expression.NotEqual(left.Nullify(), Expression.Constant(null, typeof(string))), - left.Nullify(), - right.Nullify()); + return Expression.Coalesce(left, right); } if (expression.IsNull()) - return Expression.Constant(null, typeof(string)); + return new SqlConstantExpression(null, primaryKeyType.Nullify()); throw new NotSupportedException("Id for {0}".FormatWith(expression.ToString())); } @@ -3629,7 +3642,7 @@ protected override Expression VisitConditional(ConditionalExpression c) if (colExpression is ImplementedByAllExpression) return Combiner(ifTrue, ifFalse, (col, t, f) => new ImplementedByAllExpression(col.Type, - Expression.Condition(test, t.Id.Nullify(), f.Id.Nullify()), + col.Ids.ToDictionary(a => a.Key, a => (Expression)Expression.Condition(test, t.Ids.GetOrThrow(a.Key).Nullify(), f.Ids.GetOrThrow(a.Key).Nullify())), new TypeImplementedByAllExpression( new PrimaryKeyExpression(ConditionFlexible(test, t.TypeId.TypeColumn.Value.Nullify(), @@ -3766,7 +3779,7 @@ protected override Expression VisitUnary(UnaryExpression node) if (colExpression is ImplementedByAllExpression) return Combiner(left, right, (col, l, r) => new ImplementedByAllExpression(col.Type, - Expression.Coalesce(l.Id, r.Id), + col.Ids.Keys.ToDictionary(t => t, t => (Expression)Expression.Coalesce(l.Ids.GetOrThrow(t), r.Ids.GetOrThrow(t))), new TypeImplementedByAllExpression(new PrimaryKeyExpression(CoalesceFlexible( l.TypeId.TypeColumn.Value.Nullify(), r.TypeId.TypeColumn.Value.Nullify()))), null)); @@ -3845,9 +3858,9 @@ protected internal override Expression VisitLiteReference(LiteReferenceExpressio protected internal override Expression VisitEntity(EntityExpression ee) { - if (colExpression is ImplementedByAllExpression) + if (colExpression is ImplementedByAllExpression iba) return new ImplementedByAllExpression(colExpression.Type, - new SqlCastExpression(typeof(string), ee.ExternalId.Value), + iba.Ids.Keys.ToDictionary(a=>a, a => PrimaryKey.Type(ee.Type) == a ? ee.ExternalId.Value : new SqlConstantExpression(null, a)), new TypeImplementedByAllExpression(new PrimaryKeyExpression( Expression.Condition(Expression.Equal(ee.ExternalId.Value.Nullify(), new SqlConstantExpression(null, ee.ExternalId.ValueType.Nullify())), new SqlConstantExpression(null, PrimaryKey.Type(typeof(TypeEntity)).Nullify()), @@ -3873,10 +3886,15 @@ protected internal override Expression VisitEmbeddedEntity(EmbeddedEntityExpress protected internal override Expression VisitImplementedBy(ImplementedByExpression ib) { - if (colExpression is ImplementedByAllExpression) + if (colExpression is ImplementedByAllExpression iba) { return new ImplementedByAllExpression(colExpression.Type, - new PrimaryKeyExpression(QueryBinder.Coalesce(ib.Implementations.Values.Select(a => a.ExternalId.ValueType.Nullify()).Distinct().SingleEx(), ib.Implementations.Select(e => e.Value.ExternalId))), + iba.Ids.Keys.ToDictionary(a => a, a => + { + var imp = ib.Implementations.Values.Where(e => PrimaryKey.Type(e.Type) == a).Select(a => a.ExternalId.Value); + + return imp.Any() ? imp.Aggregate((a, b) => Expression.Coalesce(a, b)) : new SqlConstantExpression(null, a); + }), new TypeImplementedByAllExpression(new PrimaryKeyExpression( ib.Implementations.Select(imp => new When(imp.Value.ExternalId.NotEqualsNulll(), QueryBinder.TypeConstant(imp.Key))).ToList() .ToCondition(PrimaryKey.Type(typeof(TypeEntity)).Nullify()))), null); @@ -3923,7 +3941,7 @@ colExpression is ImplementedByExpression || var entity = GetEntityConstant( lite == null ? Expression.Constant(null, type) : Expression.Constant(lite.Id.Object, type), - lite?.GetType().CleanType()); + lite?.EntityType); return new LiteReferenceExpression(colLite.Type, entity, null, false, false); } @@ -3942,9 +3960,9 @@ private Expression GetEntityConstant(Expression id, Type? type) return new EntityExpression(colExpression.Type, new PrimaryKeyExpression(id), null, null, null, null, null, false); } - if (colExpression is ImplementedByAllExpression) + if (colExpression is ImplementedByAllExpression iba) return new ImplementedByAllExpression(colExpression.Type, - new SqlCastExpression(typeof(string), id), + iba.Ids.Keys.ToDictionary(t => t, t => type != null && PrimaryKey.Type(type) == t ? id : new SqlConstantExpression(null, t)), new TypeImplementedByAllExpression(new PrimaryKeyExpression(id.IsNull() ? Expression.Constant(null, PrimaryKey.Type(typeof(TypeEntity)).Nullify()) : QueryBinder.TypeConstant(type!).Nullify())), null); diff --git a/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs b/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs index fa4312bd7e..1c8213a6e0 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/SmartEqualizer.cs @@ -517,7 +517,7 @@ private static Expression TypeIbIbEquals(TypeImplementedByExpression typeIb1, Ty { var joins = (from imp1 in typeIb1.TypeImplementations join imp2 in typeIb2.TypeImplementations on imp1.Key equals imp2.Key - select Expression.And(Expression.And(NotEqualToNull(imp1.Value), NotEqualToNull(imp2.Value)), EqualNullable(imp1.Value, imp2.Value)) + select EqualNullable(imp1.Value, imp2.Value) ).ToList(); return joins.AggregateOr(); @@ -802,14 +802,10 @@ static Expression IbaIbaEquals(ImplementedByAllExpression iba, ImplementedByAllE { var joins = (from id1 in iba.Ids join id2 in iba2.Ids on id1.Key equals id2.Key - select Expression.And(Expression.And( - NotEqualToNull(id1.Value, new SqlConstantExpression(null, id1.Value.Type)), - NotEqualToNull(id2.Value, new SqlConstantExpression(null, id2.Value.Type)), - EqualNullable(id1.Value, id2.Value))) - )).ToList(); + select (Expression)EqualNullable(id1.Value, id2.Value)).ToList(); - return Expression.And(EqualNullable(iba.TypeId.TypeColumn.Value, iba2.TypeId.TypeColumn.Value), joins); + return Expression.And(EqualNullable(iba.TypeId.TypeColumn.Value, iba2.TypeId.TypeColumn.Value), joins.AggregateOr()); } static Expression EqualsToNull(PrimaryKeyExpression exp) diff --git a/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs b/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs index 43c0739fbd..3cec96971a 100644 --- a/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs +++ b/Signum.Engine/Linq/ExpressionVisitor/TranslatorBuilder.cs @@ -370,7 +370,7 @@ protected internal override Expression VisitImplementedByAll(ImplementedByAllExp { return Expression.Call(retriever, miRequestIBA.MakeGenericMethod(rba.Type), Visit(NullifyColumn(rba.TypeId.TypeColumn)), - Visit(NullifyColumn(rba.Id))); + Visit(rba.Ids.Values.Select(a => (Expression)Expression.Convert(NullifyColumn(a), typeof(IComparable))).Aggregate((a, b) => Expression.Coalesce(a, b)))); } static readonly ConstantExpression NullType = Expression.Constant(null, typeof(Type)); @@ -452,7 +452,7 @@ protected internal override Expression VisitLiteValue(LiteValueExpression lite) else if (typeId is TypeImplementedByAllExpression tiba) { var tid = Visit(NullifyColumn(tiba.TypeColumn)); - liteConstructor = Expression.Convert(Expression.Call(miLiteCreateParse, Expression.Constant(Schema.Current), tid, id.UnNullify(), toStringOrNull), lite.Type); + liteConstructor = Expression.Convert(Expression.Call(miTryLiteCreate, Expression.Constant(Schema.Current), tid, id.Nullify(), toStringOrNull), lite.Type); } else { @@ -467,16 +467,16 @@ protected internal override Expression VisitLiteValue(LiteValueExpression lite) return Expression.Call(retriever, miRequestLite.MakeGenericMethod(Lite.Extract(lite.Type)!), liteConstructor); } - static readonly MethodInfo miLiteCreateParse = ReflectionTools.GetMethodInfo(() => LiteCreateParse(null!, null, null!, null!)); + static readonly MethodInfo miTryLiteCreate = ReflectionTools.GetMethodInfo(() => TryLiteCreate(null!, null, null!, null!)); - static Lite? LiteCreateParse(Schema schema, PrimaryKey? typeId, string id, string toString) + static Lite? TryLiteCreate(Schema schema, PrimaryKey? typeId, PrimaryKey? id, string toString) { if (typeId == null) return null; Type type = schema.GetType(typeId.Value); - return Lite.Create(type, PrimaryKey.Parse(id, type), toString); + return Lite.Create(type, id.Value, toString); } static MethodInfo miLiteCreate = ReflectionTools.GetMethodInfo(() => Lite.Create(null!, 0, null)); diff --git a/Signum.Engine/Retriever.cs b/Signum.Engine/Retriever.cs index d71de16c43..ff7c61a919 100644 --- a/Signum.Engine/Retriever.cs +++ b/Signum.Engine/Retriever.cs @@ -11,7 +11,7 @@ public interface IRetriever : IDisposable T? Complete(PrimaryKey? id, Action complete) where T : Entity; T? Request(PrimaryKey? id) where T : Entity; - T? RequestIBA(PrimaryKey? typeId, string? id) where T : class, IEntity; + T? RequestIBA(PrimaryKey? typeId, IComparable? id) where T : class, IEntity; Lite? RequestLite(Lite? lite) where T : class, IEntity; T? ModifiablePostRetrieving(T? entity) where T : Modifiable; IRetriever? Parent { get; } @@ -119,14 +119,14 @@ bool TryGetRequest((Type type, PrimaryKey id) key, [NotNullWhen(true)]out Entity return entity; } - public T? RequestIBA(PrimaryKey? typeId, string? id) where T : class, IEntity + public T? RequestIBA(PrimaryKey? typeId, IComparable? id) where T : class, IEntity { if (id == null) return null; Type type = TypeLogic.IdToType[typeId!.Value]; - var parsedId = PrimaryKey.Parse(id, type); + var parsedId = new PrimaryKey(id); return (T)(IEntity)giRequest.GetInvoker(type)(this, parsedId); } @@ -358,7 +358,7 @@ public ChildRetriever(IRetriever parent, EntityCache.RealEntityCache entityCache return parent.Request(id); } - public T? RequestIBA(PrimaryKey? typeId, string? id) where T : class, IEntity + public T? RequestIBA(PrimaryKey? typeId, IComparable? id) where T : class, IEntity { return parent.RequestIBA(typeId, id); } diff --git a/Signum.Engine/Schema/Schema.Basics.cs b/Signum.Engine/Schema/Schema.Basics.cs index dde367b16c..34d811bdd4 100644 --- a/Signum.Engine/Schema/Schema.Basics.cs +++ b/Signum.Engine/Schema/Schema.Basics.cs @@ -1093,18 +1093,23 @@ 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 FieldImplementedByAll(PropertyRoute route, IEnumerable columnIds, ImplementationColumn columnType) : base(route) + public FieldImplementedByAll(PropertyRoute route, IEnumerable columnIds, ImplementationColumn columnType) : base(route) { - this.IdColumns = columnIds.ToDictionaryEx(a => a.Type); + this.IdColumns = columnIds.ToDictionaryEx(a => a.Type.UnNullify()); this.TypeColumn = columnType; } public override IEnumerable Columns() { - return IdColumns.Values.Cast().And(TypeColumn); + yield return TypeColumn; + + foreach (var item in IdColumns.Values) + { + yield return item; + } } internal override IEnumerable> GetTables() @@ -1171,9 +1176,14 @@ public ImplementationColumn(string name, Table referenceTable) Name = name; ReferenceTable = referenceTable; } + + public override string ToString() + { + return this.Name; + } } -public partial class ImplementationIdColumn : IColumn +public partial class ImplementedByAllIdColumn : IColumn { public string Name { get; set; } public IsNullable Nullable { get; set; } @@ -1191,12 +1201,17 @@ public partial class ImplementationIdColumn : IColumn public bool AvoidForeignKey => false; public string? Default { get; set; } - public ImplementationIdColumn(string name, Type type, AbstractDbType dbType) + public ImplementedByAllIdColumn(string name, Type type, AbstractDbType dbType) { Name = name; this.DbType = dbType; this.Type = type; } + + public override string ToString() + { + return this.Name; + } } public partial class FieldMList : Field, IFieldFinder diff --git a/Signum.Engine/Schema/Schema.Save.cs b/Signum.Engine/Schema/Schema.Save.cs index 97a59471a8..0384230789 100644 --- a/Signum.Engine/Schema/Schema.Save.cs +++ b/Signum.Engine/Schema/Schema.Save.cs @@ -1502,11 +1502,7 @@ protected internal override void CreateParameter(List trios, List(); instructions.Add(Expression.Assign(id, Expression.Call(miUnWrap, this.GetIdFactory(value, forbidden)))); instructions.Add(Expression.Call(Expression.Constant(this), miAssertPrimaryKeyTypes, id)); - instructions.Add(Expression.Condition( - Expression.And( - Expression.NotEqual(id, Expression.Constant(null, typeof(IComparable))), - Expression.TypeIs(id, col.Type)), - Expression.Convert(id, col.Type), Expression.Constant(id, col.Type))); + instructions.Add(Expression.Condition(Expression.TypeIs(id, col.Type), Expression.Convert(id, col.Type), Expression.Constant(null, col.Type))); trios.Add(new Table.Trio(col, Expression.Block(col.Type, variables, instructions), suffix)); } diff --git a/Signum.Engine/Schema/Schema.cs b/Signum.Engine/Schema/Schema.cs index ba55038714..a378409aa7 100644 --- a/Signum.Engine/Schema/Schema.cs +++ b/Signum.Engine/Schema/Schema.cs @@ -499,6 +499,32 @@ public void WhenIncluded(Action action) where T : Entity }; } + public static void CheckImplementedByAllPrimaryKeyTypes() + { + var schema = Schema.Current; + + var should = schema.tables.Values.GroupToDictionary(a => a.PrimaryKey.Type); + + var current = schema.Settings.ImplementedByAllPrimaryKeyTypes; + + var missing = should.Where(kvp => !current.Contains(kvp.Key)); + + var extra = current.Except(should.Keys); + + if (extra.Any() || missing.Any()) + { + throw new InvalidOperationException($"{nameof(SchemaSettings)}.{nameof(SchemaSettings.ImplementedByAllPrimaryKeyTypes)}" + + (missing.Any() ? $" does not contain " + missing.CommaAnd(kvp => $"'{kvp.Key.TypeName()}' (required for {kvp.Value.CommaAnd(a => a.Type.TypeName())})") : null) + + (extra.Any() ? $" contains {extra.CommaAnd(a => "'" + a.TypeName() + "'")} that are not needed" : null) + + "\n\nConsider writing something like this at the beginning of your Starter.Start method:\n" + + missing.Select(kvp => $"sb.Schema.Settings.ImplementedByAllPrimaryKeyTypes.Add(typeof({kvp.Key.TypeName()}))") + .Concat(extra.Select(t => $"sb.Schema.Settings.ImplementedByAllPrimaryKeyTypes.Remove(typeof({t.TypeName()}))")) + .ToString("\n") + .Indent(4) + ); + } + } + public event Action? BeforeDatabaseAccess; public void OnBeforeDatabaseAccess() @@ -574,6 +600,8 @@ internal Schema(SchemaSettings settings) Synchronizing += Assets.Schema_Synchronizing; InvalidateCache += GlobalLazy.ResetAll; + + SchemaCompleted += Schema.CheckImplementedByAllPrimaryKeyTypes; } public static Schema Current diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs index c7d52f9e51..339a1a74a6 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaBuilder.cs @@ -648,7 +648,7 @@ protected virtual FieldImplementedBy GenerateFieldImplementedBy(ITable table, Pr bool avoidForeignKey = Settings.FieldAttribute(route) != null; - var implementations = types.ToDictionary(t => t, t => + var implementations = types.ToDictionary(t => t, t => { var rt = Include(t, route); @@ -674,10 +674,13 @@ protected virtual FieldImplementedByAll GenerateFieldImplementedByAll(PropertyRo var primaryKeyTypes = Settings.ImplementedByAllPrimaryKeyTypes; - var columns = Settings.ImplementedByAllPrimaryKeyTypes.Select(t => new ImplementationIdColumn(preName.Add("_" + t.Name).ToString(), t, Settings.DefaultSqlType(t)) + if(primaryKeyTypes.Count() > 1 && nullable == IsNullable.No) + nullable = IsNullable.Forced; + + var columns = Settings.ImplementedByAllPrimaryKeyTypes.Select(t => new ImplementedByAllIdColumn(preName.Add(t.Name).ToString(), nullable.ToBool() ? t.Nullify() : t, Settings.DefaultSqlType(t)) { - Nullable = primaryKeyTypes.Count() > 1 && nullable == IsNullable.No ? IsNullable.Forced : nullable, - Size = Settings.DefaultImplementedBySize, + Nullable = nullable, + Size = t == typeof(string) ? Settings.ImplementedByAllStringSize : null, }); var columnType = new ImplementationColumn(preName.Add("Type").ToString(), Include(typeof(TypeEntity), route)) diff --git a/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs index c973c6c4ca..027a5a4264 100644 --- a/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs +++ b/Signum.Engine/Schema/SchemaBuilder/SchemaSettings.cs @@ -17,7 +17,6 @@ public SchemaSettings() public bool PostresVersioningFunctionNoChecks { get; set; } public PrimaryKeyAttribute DefaultPrimaryKeyAttribute = new PrimaryKeyAttribute(typeof(int), "ID"); - public int DefaultImplementedBySize = 40; public Action AssertNotIncluded = null!; @@ -25,6 +24,7 @@ public SchemaSettings() public int MaxNumberOfStatementsInSaveQueries = 16; public List ImplementedByAllPrimaryKeyTypes = new List { typeof(Guid), typeof(int) }; + public int ImplementedByAllStringSize = 40; public ConcurrentDictionary FieldAttributesCache = new ConcurrentDictionary(); public ConcurrentDictionary TypeAttributesCache = new ConcurrentDictionary(); diff --git a/Signum.Test/Environment/Entities.cs b/Signum.Test/Environment/Entities.cs index c1c9604a4c..9f07ec9c77 100644 --- a/Signum.Test/Environment/Entities.cs +++ b/Signum.Test/Environment/Entities.cs @@ -79,7 +79,7 @@ public class ArtistEntity : Entity, IAuthorEntity public MList> Friends { get; set; } = new MList>(); - [Ignore] + [Ignore, QueryableProperty] [NoRepeatValidator] public MList Nominations { get; set; } = new MList(); diff --git a/Signum.Test/Environment/MusicStarter.cs b/Signum.Test/Environment/MusicStarter.cs index 414725fc91..f30d94d932 100644 --- a/Signum.Test/Environment/MusicStarter.cs +++ b/Signum.Test/Environment/MusicStarter.cs @@ -55,7 +55,7 @@ public static void Start(string connectionString) } sb.Schema.Version = typeof(MusicStarter).Assembly.GetName().Version!; - + sb.Schema.Settings.ImplementedByAllPrimaryKeyTypes.Add(typeof(long)); sb.Schema.Settings.FieldAttributes((OperationLogEntity ol) => ol.User).Add(new ImplementedByAttribute()); sb.Schema.Settings.FieldAttributes((ExceptionEntity e) => e.User).Add(new ImplementedByAttribute());