diff --git a/Signum.Engine/Administrator.cs b/Signum.Engine/Administrator.cs index 8dac3c0747..eb1c3769cd 100644 --- a/Signum.Engine/Administrator.cs +++ b/Signum.Engine/Administrator.cs @@ -41,7 +41,7 @@ from c in t.Columns() select new DiffColumn { Name = c.name, - SqlDbType = SchemaSynchronizer.ToSqlDbType(c.Type().name), + SqlDbType = SchemaSynchronizer.ToSqlDbType(c.Type()!.name), UserTypeName = null, Identity = c.is_identity, Nullable = c.is_nullable, diff --git a/Signum.Engine/Basics/ExceptionLogic.cs b/Signum.Engine/Basics/ExceptionLogic.cs index 9361ffea8b..21d7f7866b 100644 --- a/Signum.Engine/Basics/ExceptionLogic.cs +++ b/Signum.Engine/Basics/ExceptionLogic.cs @@ -14,8 +14,6 @@ namespace Signum.Engine.Basics { public static class ExceptionLogic { - public static Func? GetCurrentVersion; - public static void Start(SchemaBuilder sb) { if (sb.NotDefined(MethodInfo.GetCurrentMethod())) @@ -117,7 +115,7 @@ static ExceptionEntity SaveForceNew(this ExceptionEntity entity) static readonly Variable overridenEnvironment = Statics.ThreadVariable("exceptionEnviroment"); - public static IDisposable OverrideEnviroment(string newEnviroment) + public static IDisposable OverrideEnviroment(string? newEnviroment) { string? oldEnviroment = overridenEnvironment.Value; overridenEnvironment.Value = newEnviroment; diff --git a/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs b/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs index 24b80413ce..d814bae1ae 100644 --- a/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs +++ b/Signum.Engine/CodeGeneration/EntityCodeGenerator.cs @@ -498,7 +498,7 @@ protected virtual string GetTicksColumnAttribute(DiffTable table) protected virtual string? GetPrimaryKeyAttribute(DiffTable table) { - DiffColumn primaryKey = GetPrimaryKeyColumn(table); + DiffColumn? primaryKey = GetPrimaryKeyColumn(table); if (primaryKey == null) return null; @@ -529,7 +529,7 @@ protected virtual string GetTicksColumnAttribute(DiffTable table) return null; } - protected virtual DiffColumn GetPrimaryKeyColumn(DiffTable table) + protected virtual DiffColumn? GetPrimaryKeyColumn(DiffTable table) { return table.Columns.Values.SingleOrDefaultEx(a => a.PrimaryKey); } diff --git a/Signum.Engine/Connection/FieldReader.cs b/Signum.Engine/Connection/FieldReader.cs index 8ce8de6d26..096f890390 100644 --- a/Signum.Engine/Connection/FieldReader.cs +++ b/Signum.Engine/Connection/FieldReader.cs @@ -1,582 +1,583 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Signum.Utilities.Reflection; -using System.Linq.Expressions; -using System.Reflection; -using Signum.Utilities; -using Signum.Engine.Maps; -using Signum.Entities; -using System.Data.SqlTypes; -using System.Data.Common; -//using Microsoft.SqlServer.Types; -using Signum.Utilities.ExpressionTrees; -using System.Data.SqlClient; -using Microsoft.SqlServer.Server; -using System.IO; - -namespace Signum.Engine -{ - public class FieldReader - { - DbDataReader reader; - TypeCode[] typeCodes; - - private const TypeCode tcGuid = (TypeCode)20; - private const TypeCode tcTimeSpan = (TypeCode)21; - private const TypeCode tcDateTimeOffset = (TypeCode)22; - - public int LastOrdinal; - - TypeCode GetTypeCode(int ordinal) - { - Type type = reader.GetFieldType(ordinal); - TypeCode tc = Type.GetTypeCode(type); - if (tc == TypeCode.Object) - { - if (type == typeof(Guid)) - tc = tcGuid; - - if (type == typeof(TimeSpan)) - tc = tcTimeSpan; - - if (type == typeof(DateTimeOffset)) - tc = tcDateTimeOffset; - } - return tc; - } - - public FieldReader(DbDataReader reader) - { - this.reader = reader; - - this.typeCodes = new TypeCode[reader.FieldCount]; - for (int i = 0; i < typeCodes.Length; i++) - typeCodes[i] = GetTypeCode(i); - } - - public bool IsNull(int ordinal) - { - LastOrdinal = ordinal; - return reader.IsDBNull(ordinal); - } - - public string? GetString(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - - switch (typeCodes[ordinal]) - { - case TypeCode.Byte: - return reader.GetByte(ordinal).ToString(); - case TypeCode.Int16: - return reader.GetInt16(ordinal).ToString(); - case TypeCode.Int32: - return reader.GetInt32(ordinal).ToString(); - case TypeCode.Int64: - return reader.GetInt64(ordinal).ToString(); - case TypeCode.Double: - return reader.GetDouble(ordinal).ToString(); - case TypeCode.Single: - return reader.GetFloat(ordinal).ToString(); - case TypeCode.Decimal: - return reader.GetDecimal(ordinal).ToString(); - case TypeCode.DateTime: - return reader.GetDateTime(ordinal).ToString(); - case tcGuid: - return reader.GetGuid(ordinal).ToString(); - case TypeCode.String: - return reader.GetString(ordinal); - default: - return reader.GetValue(ordinal).ToString(); - } - } - - public byte[]? GetByteArray(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - - return (byte[])reader.GetValue(ordinal); - } - - public bool GetBoolean(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case TypeCode.Boolean: - return reader.GetBoolean(ordinal); - case TypeCode.Byte: - return reader.GetByte(ordinal) != 0; - case TypeCode.Int16: - return reader.GetInt16(ordinal) != 0; - case TypeCode.Int32: - return reader.GetInt32(ordinal) != 0; - case TypeCode.Int64: - return reader.GetInt64(ordinal) != 0; - case TypeCode.String: - return bool.Parse(reader.GetString(ordinal)); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public bool? GetNullableBoolean(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetBoolean(ordinal); - } - - - public Byte GetByte(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case TypeCode.Byte: - return reader.GetByte(ordinal); - case TypeCode.Int16: - return (Byte)reader.GetInt16(ordinal); - case TypeCode.Int32: - return (Byte)reader.GetInt32(ordinal); - case TypeCode.Int64: - return (Byte)reader.GetInt64(ordinal); - case TypeCode.Double: - return (Byte)reader.GetDouble(ordinal); - case TypeCode.Single: - return (Byte)reader.GetFloat(ordinal); - case TypeCode.Decimal: - return (Byte)reader.GetDecimal(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public Byte? GetNullableByte(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetByte(ordinal); - } - - - public Char GetChar(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case TypeCode.Byte: - return (Char)reader.GetByte(ordinal); - case TypeCode.Int16: - return (Char)reader.GetInt16(ordinal); - case TypeCode.Int32: - return (Char)reader.GetInt32(ordinal); - case TypeCode.Int64: - return (Char)reader.GetInt64(ordinal); - case TypeCode.Double: - return (Char)reader.GetDouble(ordinal); - case TypeCode.Single: - return (Char)reader.GetFloat(ordinal); - case TypeCode.Decimal: - return (Char)reader.GetDecimal(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public Char? GetNullableChar(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetChar(ordinal); - } - - - public Single GetFloat(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case TypeCode.Byte: - return (Single)reader.GetByte(ordinal); - case TypeCode.Int16: - return (Single)reader.GetInt16(ordinal); - case TypeCode.Int32: - return (Single)reader.GetInt32(ordinal); - case TypeCode.Int64: - return (Single)reader.GetInt64(ordinal); - case TypeCode.Double: - return (Single)reader.GetDouble(ordinal); - case TypeCode.Single: - return reader.GetFloat(ordinal); - case TypeCode.Decimal: - return (Single)reader.GetDecimal(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public Single? GetNullableFloat(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetFloat(ordinal); - } - - - public Double GetDouble(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case TypeCode.Byte: - return (Double)reader.GetByte(ordinal); - case TypeCode.Int16: - return (Double)reader.GetInt16(ordinal); - case TypeCode.Int32: - return (Double)reader.GetInt32(ordinal); - case TypeCode.Int64: - return (Double)reader.GetInt64(ordinal); - case TypeCode.Double: - return reader.GetDouble(ordinal); - case TypeCode.Single: - return (Double)reader.GetFloat(ordinal); - case TypeCode.Decimal: - return (Double)reader.GetDecimal(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public Double? GetNullableDouble(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetDouble(ordinal); - } - - - public Decimal GetDecimal(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case TypeCode.Byte: - return (Decimal)reader.GetByte(ordinal); - case TypeCode.Int16: - return (Decimal)reader.GetInt16(ordinal); - case TypeCode.Int32: - return (Decimal)reader.GetInt32(ordinal); - case TypeCode.Int64: - return (Decimal)reader.GetInt64(ordinal); - case TypeCode.Double: - return (Decimal)reader.GetDouble(ordinal); - case TypeCode.Single: - return (Decimal)reader.GetFloat(ordinal); - case TypeCode.Decimal: - return reader.GetDecimal(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public Decimal? GetNullableDecimal(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetDecimal(ordinal); - } - - - public Int16 GetInt16(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case TypeCode.Byte: - return (Int16)reader.GetByte(ordinal); - case TypeCode.Int16: - return reader.GetInt16(ordinal); - case TypeCode.Int32: - return (Int16)reader.GetInt32(ordinal); - case TypeCode.Int64: - return (Int16)reader.GetInt64(ordinal); - case TypeCode.Double: - return (Int16)reader.GetDouble(ordinal); - case TypeCode.Single: - return (Int16)reader.GetFloat(ordinal); - case TypeCode.Decimal: - return (Int16)reader.GetDecimal(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public Int16? GetNullableInt16(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetInt16(ordinal); - } - - - public Int32 GetInt32(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case TypeCode.Byte: - return (Int32)reader.GetByte(ordinal); - case TypeCode.Int16: - return (Int32)reader.GetInt16(ordinal); - case TypeCode.Int32: - return reader.GetInt32(ordinal); - case TypeCode.Int64: - return (Int32)reader.GetInt64(ordinal); - case TypeCode.Double: - return (Int32)reader.GetDouble(ordinal); - case TypeCode.Single: - return (Int32)reader.GetFloat(ordinal); - case TypeCode.Decimal: - return (Int32)reader.GetDecimal(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public Int32? GetNullableInt32(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetInt32(ordinal); - } - - - public Int64 GetInt64(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case TypeCode.Byte: - return (Int64)reader.GetByte(ordinal); - case TypeCode.Int16: - return (Int64)reader.GetInt16(ordinal); - case TypeCode.Int32: - return (Int64)reader.GetInt32(ordinal); - case TypeCode.Int64: - return (Int64)reader.GetInt64(ordinal); - case TypeCode.Double: - return (Int64)reader.GetDouble(ordinal); - case TypeCode.Single: - return (Int64)reader.GetFloat(ordinal); - case TypeCode.Decimal: - return (Int64)reader.GetDecimal(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public Int64? GetNullableInt64(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetInt64(ordinal); - } - - - public DateTime GetDateTime(int ordinal) - { - LastOrdinal = ordinal; - DateTime dt; - switch (typeCodes[ordinal]) - { - case TypeCode.DateTime: - dt = reader.GetDateTime(ordinal); - break; - default: - dt = ReflectionTools.ChangeType(reader.GetValue(ordinal)); - break; - } - - if (Schema.Current.TimeZoneMode == TimeZoneMode.Utc) - return new DateTime(dt.Ticks, DateTimeKind.Utc); - return dt; - } - - public DateTime? GetNullableDateTime(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetDateTime(ordinal); - } - - - public DateTimeOffset GetDateTimeOffset(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case tcDateTimeOffset: - return ((SqlDataReader)reader).GetDateTimeOffset(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public DateTimeOffset? GetNullableDateTimeOffset(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetDateTimeOffset(ordinal); - } - - - public TimeSpan GetTimeSpan(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case tcTimeSpan: - return ((SqlDataReader)reader).GetTimeSpan(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public TimeSpan? GetNullableTimeSpan(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetTimeSpan(ordinal); - } - - - public Guid GetGuid(int ordinal) - { - LastOrdinal = ordinal; - switch (typeCodes[ordinal]) - { - case tcGuid: - return reader.GetGuid(ordinal); - default: - return ReflectionTools.ChangeType(reader.GetValue(ordinal)); - } - } - - public Guid? GetNullableGuid(int ordinal) - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return null; - } - return GetGuid(ordinal); - } - - static MethodInfo miGetUdt = ReflectionTools.GetMethodInfo((FieldReader r) => r.GetUdt(0)).GetGenericMethodDefinition(); - - public T GetUdt(int ordinal) - where T : IBinarySerialize - { - LastOrdinal = ordinal; - if (reader.IsDBNull(ordinal)) - { - return (T)(object)null!; - } - - var udt = Activator.CreateInstance(); - udt.Read(new BinaryReader(reader.GetStream(ordinal))); - return udt; - } - - static Dictionary methods = - typeof(FieldReader).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) - .Where(m => m.Name != "GetExpression" && m.Name != "IsNull") - .ToDictionary(a => a.ReturnType); - - - public static Expression GetExpression(Expression reader, int ordinal, Type type) - { - MethodInfo? mi = methods.TryGetC(type); - if (mi != null) - return Expression.Call(reader, mi, Expression.Constant(ordinal)); - - if (typeof(IBinarySerialize).IsAssignableFrom(type.UnNullify())) - { - if (type.IsNullable()) - return Expression.Call(reader, miGetUdt.MakeGenericMethod(type.UnNullify()), Expression.Constant(ordinal)).Nullify(); - else - return Expression.Call(reader, miGetUdt.MakeGenericMethod(type.UnNullify()), Expression.Constant(ordinal)); - } - - throw new InvalidOperationException("Type {0} not supported".FormatWith(type)); - } - - static MethodInfo miIsNull = ReflectionTools.GetMethodInfo((FieldReader r) => r.IsNull(0)); - - public static Expression GetIsNull(Expression reader, int ordinal) - { - return Expression.Call(reader, miIsNull, Expression.Constant(ordinal)); - } - - internal FieldReaderException CreateFieldReaderException(Exception ex) - { +using System; +using System.Collections.Generic; +using System.Linq; +using Signum.Utilities.Reflection; +using System.Linq.Expressions; +using System.Reflection; +using Signum.Utilities; +using Signum.Engine.Maps; +using Signum.Entities; +using System.Data.SqlTypes; +using System.Data.Common; +//using Microsoft.SqlServer.Types; +using Signum.Utilities.ExpressionTrees; +using System.Data.SqlClient; +using Microsoft.SqlServer.Server; +using System.IO; + +namespace Signum.Engine +{ + public class FieldReader + { + DbDataReader reader; + TypeCode[] typeCodes; + + private const TypeCode tcGuid = (TypeCode)20; + private const TypeCode tcTimeSpan = (TypeCode)21; + private const TypeCode tcDateTimeOffset = (TypeCode)22; + + public int LastOrdinal; + + TypeCode GetTypeCode(int ordinal) + { + Type type = reader.GetFieldType(ordinal); + TypeCode tc = Type.GetTypeCode(type); + if (tc == TypeCode.Object) + { + if (type == typeof(Guid)) + tc = tcGuid; + + if (type == typeof(TimeSpan)) + tc = tcTimeSpan; + + if (type == typeof(DateTimeOffset)) + tc = tcDateTimeOffset; + } + return tc; + } + + public FieldReader(DbDataReader reader) + { + this.reader = reader; + + this.typeCodes = new TypeCode[reader.FieldCount]; + for (int i = 0; i < typeCodes.Length; i++) + typeCodes[i] = GetTypeCode(i); + } + + public bool IsNull(int ordinal) + { + LastOrdinal = ordinal; + return reader.IsDBNull(ordinal); + } + + public string? GetString(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + + switch (typeCodes[ordinal]) + { + case TypeCode.Byte: + return reader.GetByte(ordinal).ToString(); + case TypeCode.Int16: + return reader.GetInt16(ordinal).ToString(); + case TypeCode.Int32: + return reader.GetInt32(ordinal).ToString(); + case TypeCode.Int64: + return reader.GetInt64(ordinal).ToString(); + case TypeCode.Double: + return reader.GetDouble(ordinal).ToString(); + case TypeCode.Single: + return reader.GetFloat(ordinal).ToString(); + case TypeCode.Decimal: + return reader.GetDecimal(ordinal).ToString(); + case TypeCode.DateTime: + return reader.GetDateTime(ordinal).ToString(); + case tcGuid: + return reader.GetGuid(ordinal).ToString(); + case TypeCode.String: + return reader.GetString(ordinal); + default: + return reader.GetValue(ordinal).ToString(); + } + } + + public byte[]? GetByteArray(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + + return (byte[])reader.GetValue(ordinal); + } + + public bool GetBoolean(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case TypeCode.Boolean: + return reader.GetBoolean(ordinal); + case TypeCode.Byte: + return reader.GetByte(ordinal) != 0; + case TypeCode.Int16: + return reader.GetInt16(ordinal) != 0; + case TypeCode.Int32: + return reader.GetInt32(ordinal) != 0; + case TypeCode.Int64: + return reader.GetInt64(ordinal) != 0; + case TypeCode.String: + return bool.Parse(reader.GetString(ordinal)); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public bool? GetNullableBoolean(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetBoolean(ordinal); + } + + + public Byte GetByte(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case TypeCode.Byte: + return reader.GetByte(ordinal); + case TypeCode.Int16: + return (Byte)reader.GetInt16(ordinal); + case TypeCode.Int32: + return (Byte)reader.GetInt32(ordinal); + case TypeCode.Int64: + return (Byte)reader.GetInt64(ordinal); + case TypeCode.Double: + return (Byte)reader.GetDouble(ordinal); + case TypeCode.Single: + return (Byte)reader.GetFloat(ordinal); + case TypeCode.Decimal: + return (Byte)reader.GetDecimal(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public Byte? GetNullableByte(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetByte(ordinal); + } + + + public Char GetChar(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case TypeCode.Byte: + return (Char)reader.GetByte(ordinal); + case TypeCode.Int16: + return (Char)reader.GetInt16(ordinal); + case TypeCode.Int32: + return (Char)reader.GetInt32(ordinal); + case TypeCode.Int64: + return (Char)reader.GetInt64(ordinal); + case TypeCode.Double: + return (Char)reader.GetDouble(ordinal); + case TypeCode.Single: + return (Char)reader.GetFloat(ordinal); + case TypeCode.Decimal: + return (Char)reader.GetDecimal(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public Char? GetNullableChar(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetChar(ordinal); + } + + + public Single GetFloat(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case TypeCode.Byte: + return (Single)reader.GetByte(ordinal); + case TypeCode.Int16: + return (Single)reader.GetInt16(ordinal); + case TypeCode.Int32: + return (Single)reader.GetInt32(ordinal); + case TypeCode.Int64: + return (Single)reader.GetInt64(ordinal); + case TypeCode.Double: + return (Single)reader.GetDouble(ordinal); + case TypeCode.Single: + return reader.GetFloat(ordinal); + case TypeCode.Decimal: + return (Single)reader.GetDecimal(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public Single? GetNullableFloat(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetFloat(ordinal); + } + + + public Double GetDouble(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case TypeCode.Byte: + return (Double)reader.GetByte(ordinal); + case TypeCode.Int16: + return (Double)reader.GetInt16(ordinal); + case TypeCode.Int32: + return (Double)reader.GetInt32(ordinal); + case TypeCode.Int64: + return (Double)reader.GetInt64(ordinal); + case TypeCode.Double: + return reader.GetDouble(ordinal); + case TypeCode.Single: + return (Double)reader.GetFloat(ordinal); + case TypeCode.Decimal: + return (Double)reader.GetDecimal(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public Double? GetNullableDouble(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetDouble(ordinal); + } + + + public Decimal GetDecimal(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case TypeCode.Byte: + return (Decimal)reader.GetByte(ordinal); + case TypeCode.Int16: + return (Decimal)reader.GetInt16(ordinal); + case TypeCode.Int32: + return (Decimal)reader.GetInt32(ordinal); + case TypeCode.Int64: + return (Decimal)reader.GetInt64(ordinal); + case TypeCode.Double: + return (Decimal)reader.GetDouble(ordinal); + case TypeCode.Single: + return (Decimal)reader.GetFloat(ordinal); + case TypeCode.Decimal: + return reader.GetDecimal(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public Decimal? GetNullableDecimal(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetDecimal(ordinal); + } + + + public Int16 GetInt16(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case TypeCode.Byte: + return (Int16)reader.GetByte(ordinal); + case TypeCode.Int16: + return reader.GetInt16(ordinal); + case TypeCode.Int32: + return (Int16)reader.GetInt32(ordinal); + case TypeCode.Int64: + return (Int16)reader.GetInt64(ordinal); + case TypeCode.Double: + return (Int16)reader.GetDouble(ordinal); + case TypeCode.Single: + return (Int16)reader.GetFloat(ordinal); + case TypeCode.Decimal: + return (Int16)reader.GetDecimal(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public Int16? GetNullableInt16(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetInt16(ordinal); + } + + + public Int32 GetInt32(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case TypeCode.Byte: + return (Int32)reader.GetByte(ordinal); + case TypeCode.Int16: + return (Int32)reader.GetInt16(ordinal); + case TypeCode.Int32: + return reader.GetInt32(ordinal); + case TypeCode.Int64: + return (Int32)reader.GetInt64(ordinal); + case TypeCode.Double: + return (Int32)reader.GetDouble(ordinal); + case TypeCode.Single: + return (Int32)reader.GetFloat(ordinal); + case TypeCode.Decimal: + return (Int32)reader.GetDecimal(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public Int32? GetNullableInt32(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetInt32(ordinal); + } + + + public Int64 GetInt64(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case TypeCode.Byte: + return (Int64)reader.GetByte(ordinal); + case TypeCode.Int16: + return (Int64)reader.GetInt16(ordinal); + case TypeCode.Int32: + return (Int64)reader.GetInt32(ordinal); + case TypeCode.Int64: + return (Int64)reader.GetInt64(ordinal); + case TypeCode.Double: + return (Int64)reader.GetDouble(ordinal); + case TypeCode.Single: + return (Int64)reader.GetFloat(ordinal); + case TypeCode.Decimal: + return (Int64)reader.GetDecimal(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public Int64? GetNullableInt64(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetInt64(ordinal); + } + + + public DateTime GetDateTime(int ordinal) + { + LastOrdinal = ordinal; + DateTime dt; + switch (typeCodes[ordinal]) + { + case TypeCode.DateTime: + dt = reader.GetDateTime(ordinal); + break; + default: + dt = ReflectionTools.ChangeType(reader.GetValue(ordinal)); + break; + } + + if (Schema.Current.TimeZoneMode == TimeZoneMode.Utc) + return new DateTime(dt.Ticks, DateTimeKind.Utc); + + return new DateTime(dt.Ticks, DateTimeKind.Local); + } + + public DateTime? GetNullableDateTime(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetDateTime(ordinal); + } + + + public DateTimeOffset GetDateTimeOffset(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case tcDateTimeOffset: + return ((SqlDataReader)reader).GetDateTimeOffset(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public DateTimeOffset? GetNullableDateTimeOffset(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetDateTimeOffset(ordinal); + } + + + public TimeSpan GetTimeSpan(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case tcTimeSpan: + return ((SqlDataReader)reader).GetTimeSpan(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public TimeSpan? GetNullableTimeSpan(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetTimeSpan(ordinal); + } + + + public Guid GetGuid(int ordinal) + { + LastOrdinal = ordinal; + switch (typeCodes[ordinal]) + { + case tcGuid: + return reader.GetGuid(ordinal); + default: + return ReflectionTools.ChangeType(reader.GetValue(ordinal)); + } + } + + public Guid? GetNullableGuid(int ordinal) + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return null; + } + return GetGuid(ordinal); + } + + static MethodInfo miGetUdt = ReflectionTools.GetMethodInfo((FieldReader r) => r.GetUdt(0)).GetGenericMethodDefinition(); + + public T GetUdt(int ordinal) + where T : IBinarySerialize + { + LastOrdinal = ordinal; + if (reader.IsDBNull(ordinal)) + { + return (T)(object)null!; + } + + var udt = Activator.CreateInstance(); + udt.Read(new BinaryReader(reader.GetStream(ordinal))); + return udt; + } + + static Dictionary methods = + typeof(FieldReader).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Where(m => m.Name != "GetExpression" && m.Name != "IsNull") + .ToDictionary(a => a.ReturnType); + + + public static Expression GetExpression(Expression reader, int ordinal, Type type) + { + MethodInfo? mi = methods.TryGetC(type); + if (mi != null) + return Expression.Call(reader, mi, Expression.Constant(ordinal)); + + if (typeof(IBinarySerialize).IsAssignableFrom(type.UnNullify())) + { + if (type.IsNullable()) + return Expression.Call(reader, miGetUdt.MakeGenericMethod(type.UnNullify()), Expression.Constant(ordinal)).Nullify(); + else + return Expression.Call(reader, miGetUdt.MakeGenericMethod(type.UnNullify()), Expression.Constant(ordinal)); + } + + throw new InvalidOperationException("Type {0} not supported".FormatWith(type)); + } + + static MethodInfo miIsNull = ReflectionTools.GetMethodInfo((FieldReader r) => r.IsNull(0)); + + public static Expression GetIsNull(Expression reader, int ordinal) + { + return Expression.Call(reader, miIsNull, Expression.Constant(ordinal)); + } + + internal FieldReaderException CreateFieldReaderException(Exception ex) + { return new FieldReaderException(ex, ordinal: LastOrdinal, - columnName: reader.GetName(LastOrdinal), - columnType: reader.GetFieldType(LastOrdinal) - ); - } - } - - [Serializable] - public class FieldReaderException : SqlTypeException - { + columnName: reader.GetName(LastOrdinal), + columnType: reader.GetFieldType(LastOrdinal) + ); + } + } + + [Serializable] + public class FieldReaderException : SqlTypeException + { public FieldReaderException(Exception inner, int ordinal, string columnName, Type columnType) : base(null, inner) { this.Ordinal = ordinal; @@ -586,35 +587,35 @@ public FieldReaderException(Exception inner, int ordinal, string columnName, Typ #pragma warning disable CS8618 // Non-nullable field is uninitialized. protected FieldReaderException( #pragma warning restore CS8618 // Non-nullable field is uninitialized. - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) - : base(info, context) { } - - public override string Message - { - get - { - string text = "{0}\r\nOrdinal: {1}\r\nColumnName: {2}\r\nRow: {3}".FormatWith( - InnerException!.Message, Ordinal, ColumnName, Row); - - if (Projector != null) - { - text += "\r\nCalling: row.Reader.Get{0}({1})".FormatWith(ColumnType.Name, Ordinal); - text += "\r\nProjector:\r\n{0}".FormatWith(Projector.ToString().Indent(4)); - } - - if(Command != null) - text += "\r\nCommand:\r\n{0}".FormatWith(Command.PlainSql().Indent(4)); - - return text; - } - } - - public int Ordinal { get; internal set; } - public string ColumnName { get; internal set; } - public Type ColumnType { get; internal set; } - public int Row { get; internal set; } - public SqlPreCommand? Command { get; internal set; } - public LambdaExpression? Projector { get; internal set; } - } -} + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) + : base(info, context) { } + + public override string Message + { + get + { + string text = "{0}\r\nOrdinal: {1}\r\nColumnName: {2}\r\nRow: {3}".FormatWith( + InnerException!.Message, Ordinal, ColumnName, Row); + + if (Projector != null) + { + text += "\r\nCalling: row.Reader.Get{0}({1})".FormatWith(ColumnType.Name, Ordinal); + text += "\r\nProjector:\r\n{0}".FormatWith(Projector.ToString().Indent(4)); + } + + if(Command != null) + text += "\r\nCommand:\r\n{0}".FormatWith(Command.PlainSql().Indent(4)); + + return text; + } + } + + public int Ordinal { get; internal set; } + public string ColumnName { get; internal set; } + public Type ColumnType { get; internal set; } + public int Row { get; internal set; } + public SqlPreCommand? Command { get; internal set; } + public LambdaExpression? Projector { get; internal set; } + } +} diff --git a/Signum.Engine/Connection/Transaction.cs b/Signum.Engine/Connection/Transaction.cs index 31ddd605ef..fd7436c2b3 100644 --- a/Signum.Engine/Connection/Transaction.cs +++ b/Signum.Engine/Connection/Transaction.cs @@ -24,9 +24,9 @@ public class Transaction : IDisposableException interface ICoreTransaction { - event Action?> PostRealCommit; + event Action?>? PostRealCommit; void CallPostRealCommit(); - event Action?> PreRealCommit; + event Action?>? PreRealCommit; DbConnection? Connection { get; } DbTransaction? Transaction { get; } @@ -60,13 +60,13 @@ public FakedTransaction(ICoreTransaction parent) this.parent = parent; } - public event Action?> PostRealCommit + public event Action?>? PostRealCommit { add { parent.PostRealCommit += value; } remove { parent.PostRealCommit -= value; } } - public event Action?> PreRealCommit + public event Action?>? PreRealCommit { add { parent.PreRealCommit += value; } remove { parent.PreRealCommit -= value; } diff --git a/Signum.Engine/Database.cs b/Signum.Engine/Database.cs index 6d0874409f..801ba0a4dd 100644 --- a/Signum.Engine/Database.cs +++ b/Signum.Engine/Database.cs @@ -166,7 +166,7 @@ public static T Retrieve(PrimaryKey id) where T : Entity } } - T retrieved = Database.Query().SingleOrDefaultEx(a => a.Id == id); + T? retrieved = Database.Query().SingleOrDefaultEx(a => a.Id == id); if (retrieved == null) throw new EntityNotFoundException(typeof(T), id); diff --git a/Signum.Engine/DynamicQuery/AutoCompleteUtils.cs b/Signum.Engine/DynamicQuery/AutoCompleteUtils.cs index 570022104c..34cfd233bf 100644 --- a/Signum.Engine/DynamicQuery/AutoCompleteUtils.cs +++ b/Signum.Engine/DynamicQuery/AutoCompleteUtils.cs @@ -299,7 +299,7 @@ public static List> Autocomplete(this IQueryable> query, stri if (TryParsePrimaryKey(subString, typeof(T), out PrimaryKey id)) { - Lite entity = query.SingleOrDefaultEx(e => e.Id == id); + Lite? entity = query.SingleOrDefaultEx(e => e.Id == id); if (entity != null) results.Add(entity); @@ -358,7 +358,7 @@ public static List> Autocomplete(this IEnumerable> collection if (TryParsePrimaryKey(subString, typeof(T), out PrimaryKey id)) { - Lite entity = collection.SingleOrDefaultEx(e => e.Id == id); + Lite? entity = collection.SingleOrDefaultEx(e => e.Id == id); if (entity != null) results.Add(entity); diff --git a/Signum.Engine/Engine/SchemaSynchronizer.cs b/Signum.Engine/Engine/SchemaSynchronizer.cs index 0a352cc6a2..26bc0fe75b 100644 --- a/Signum.Engine/Engine/SchemaSynchronizer.cs +++ b/Signum.Engine/Engine/SchemaSynchronizer.cs @@ -1053,7 +1053,7 @@ public class DiffTable { public ObjectName Name; - public ObjectName PrimaryKeyName; + public ObjectName? PrimaryKeyName; public Dictionary Columns; diff --git a/Signum.Engine/Engine/SqlBuilder.cs b/Signum.Engine/Engine/SqlBuilder.cs index 4afbbbd1ae..95b087d6e2 100644 --- a/Signum.Engine/Engine/SqlBuilder.cs +++ b/Signum.Engine/Engine/SqlBuilder.cs @@ -367,9 +367,9 @@ public static int DuplicateCount(UniqueTableIndex uniqueIndex, Replacements rep) ( SELECT MIN({oldPrimaryKey}) FROM {oldTableName} - {(string.IsNullOrWhiteSpace(uniqueIndex.Where) ? "" : "WHERE " + uniqueIndex.Where.Replace(columnReplacement))} + {(!uniqueIndex.Where.HasText() ? "" : "WHERE " + uniqueIndex.Where.Replace(columnReplacement))} GROUP BY {oldColumns} -){(string.IsNullOrWhiteSpace(uniqueIndex.Where) ? "" : "AND " + uniqueIndex.Where.Replace(columnReplacement))}")!; +){(!uniqueIndex.Where.HasText() ? "" : "AND " + uniqueIndex.Where.Replace(columnReplacement))}")!; } public static SqlPreCommand? RemoveDuplicatesIfNecessary(UniqueTableIndex uniqueIndex, Replacements rep) diff --git a/Signum.Engine/Engine/Sys.Tables.cs b/Signum.Engine/Engine/Sys.Tables.cs index 8195d40006..58a5f872ba 100644 --- a/Signum.Engine/Engine/Sys.Tables.cs +++ b/Signum.Engine/Engine/Sys.Tables.cs @@ -196,7 +196,7 @@ public class SysColumns : IView public GeneratedAlwaysType generated_always_type; [AutoExpressionField] - public SysTypes Type() => + public SysTypes? Type() => As.Expression(() => Database.View().SingleOrDefaultEx(a => a.system_type_id == this.system_type_id)); } diff --git a/Signum.Engine/Exceptions.cs b/Signum.Engine/Exceptions.cs index 4dc4b88347..ae9ade0359 100644 --- a/Signum.Engine/Exceptions.cs +++ b/Signum.Engine/Exceptions.cs @@ -150,7 +150,7 @@ public ForeignKeyException(Exception inner) : base(null, inner) .Select(p => p.Key) .SingleOrDefaultEx(); - ReferedTableType = EnumEntity.Extract(ReferedTableType) ?? ReferedTableType; + ReferedTableType = ReferedTableType == null ? null : EnumEntity.Extract(ReferedTableType) ?? ReferedTableType; } } } diff --git a/Signum.Engine/Linq/AliasGenerator.cs b/Signum.Engine/Linq/AliasGenerator.cs index 48b8dce4e0..16dfe4b07b 100644 --- a/Signum.Engine/Linq/AliasGenerator.cs +++ b/Signum.Engine/Linq/AliasGenerator.cs @@ -46,7 +46,7 @@ public Alias NextTableAlias(string tableName) string? abv = tableName.Any(char.IsUpper) ? new string(tableName.Where(c => char.IsUpper(c)).ToArray()) : tableName.Any(a => a == '_') ? new string(tableName.SplitNoEmpty('_' ).Select(s => s[0]).ToArray()) : null; - if (string.IsNullOrEmpty(abv)) + if (!abv.HasText()) abv = tableName.TryStart(3)!; else abv = abv.ToLower(); diff --git a/Signum.Engine/Operations/OperationLogic.cs b/Signum.Engine/Operations/OperationLogic.cs index bc9b3d69b3..73fb9cb13e 100644 --- a/Signum.Engine/Operations/OperationLogic.cs +++ b/Signum.Engine/Operations/OperationLogic.cs @@ -622,9 +622,9 @@ public static OperationType OperationType(Type type, OperationSymbol operationSy return FindOperation(type, operationSymbol).OperationType; } - public static Dictionary? GetContextualCanExecute(IEnumerable> lites, List operationSymbols) + public static Dictionary GetContextualCanExecute(IEnumerable> lites, List operationSymbols) { - Dictionary? result = null; + Dictionary result = new Dictionary(); using (ExecutionMode.Global()) { foreach (var grLites in lites.GroupBy(a => a.EntityType)) @@ -634,8 +634,8 @@ public static OperationType OperationType(Type type, OperationSymbol operationSy foreach (var grOperations in operations.GroupBy(a => a.GetType().GetGenericArguments().Let(arr => Tuple.Create(arr[0], arr[1])))) { var dic = giGetContextualGraphCanExecute.GetInvoker(grLites.Key, grOperations.Key.Item1, grOperations.Key.Item2)(grLites, grOperations); - if (result == null) - result = dic; + if (result.IsEmpty()) + result.AddRange(dic); else { foreach (var kvp in dic) diff --git a/Signum.Engine/Schema/ObjectName.cs b/Signum.Engine/Schema/ObjectName.cs index 1222980106..0a2e178292 100644 --- a/Signum.Engine/Schema/ObjectName.cs +++ b/Signum.Engine/Schema/ObjectName.cs @@ -45,7 +45,7 @@ public override int GetHashCode() public static ServerName? Parse(string? name) { - if (string.IsNullOrEmpty(name)) + if (!name.HasText()) return null; return new ServerName(name.UnScapeSql()); @@ -93,7 +93,7 @@ public override int GetHashCode() public static DatabaseName? Parse(string? name) { - if (string.IsNullOrEmpty(name)) + if (!name.HasText()) return null; var tuple = ObjectName.SplitLast(name); @@ -159,7 +159,7 @@ public override int GetHashCode() public static SchemaName Parse(string? name) { - if (string.IsNullOrEmpty(name)) + if (!name.HasText()) return SchemaName.Default; var tuple = ObjectName.SplitLast(name); @@ -200,7 +200,7 @@ public override int GetHashCode() public static ObjectName Parse(string? name) { - if (string.IsNullOrEmpty(name)) + if (!name.HasText()) throw new ArgumentNullException(nameof(name)); var tuple = SplitLast(name); diff --git a/Signum.Engine/Schema/UniqueTableIndex.cs b/Signum.Engine/Schema/UniqueTableIndex.cs index 4bce6a39e6..edabda8c69 100644 --- a/Signum.Engine/Schema/UniqueTableIndex.cs +++ b/Signum.Engine/Schema/UniqueTableIndex.cs @@ -99,7 +99,7 @@ public string? ViewName { get { - if (string.IsNullOrEmpty(Where)) + if (!Where.HasText()) return null; if (Connector.Current.AllowsIndexWithWhere(Where)) diff --git a/Signum.Engine/Signum.Engine.csproj b/Signum.Engine/Signum.Engine.csproj index 507c5cfe73..7226e09438 100644 --- a/Signum.Engine/Signum.Engine.csproj +++ b/Signum.Engine/Signum.Engine.csproj @@ -12,7 +12,7 @@ - + diff --git a/Signum.Entities/DynamicQuery/ResultTable.cs b/Signum.Entities/DynamicQuery/ResultTable.cs index f39098c917..a6ba5f3237 100644 --- a/Signum.Entities/DynamicQuery/ResultTable.cs +++ b/Signum.Entities/DynamicQuery/ResultTable.cs @@ -218,7 +218,7 @@ static object DeserializeLite(string str, Type defaultEntityType) [Serializable] public class ResultTable { - internal ResultColumn entityColumn; + internal ResultColumn? entityColumn; public ColumnDescription? EntityColumn { get { return entityColumn == null ? null : ((ColumnToken)entityColumn.Column.Token).Column; } @@ -435,7 +435,7 @@ internal ResultRow(int index, ResultTable table) public Lite Entity { - get { return (Lite)Table.entityColumn.Values[Index]!; } + get { return (Lite)Table.entityColumn!.Values[Index]; } } public Lite? TryEntity diff --git a/Signum.Entities/DynamicQuery/Tokens/QueryToken.cs b/Signum.Entities/DynamicQuery/Tokens/QueryToken.cs index e54110201d..a5e9d1d91a 100644 --- a/Signum.Entities/DynamicQuery/Tokens/QueryToken.cs +++ b/Signum.Entities/DynamicQuery/Tokens/QueryToken.cs @@ -52,7 +52,7 @@ public virtual bool IsGroupable var pp = Validator.TryGetPropertyValidator(route); if (pp != null) { - DateTimePrecisionValidatorAttribute datetimePrecision = pp.Validators.OfType().SingleOrDefaultEx(); + DateTimePrecisionValidatorAttribute? datetimePrecision = pp.Validators.OfType().SingleOrDefaultEx(); if (datetimePrecision != null && datetimePrecision.Precision == DateTimePrecision.Days) return true; diff --git a/Signum.Entities/EnumEntity.cs b/Signum.Entities/EnumEntity.cs index 21fcec8d91..411dd0b74a 100644 --- a/Signum.Entities/EnumEntity.cs +++ b/Signum.Entities/EnumEntity.cs @@ -118,7 +118,7 @@ public static Type Generate(Type enumType) class FromEnumMethodExpander : IMethodExpander { - internal static MethodInfo miQuery = null!; + internal static MethodInfo miQuery = null!; /*Initialized in Logic*/ static readonly MethodInfo miSingleOrDefault = ReflectionTools.GetMethodInfo(() => Enumerable.SingleOrDefault(null, i => true)).GetGenericMethodDefinition(); public Expression Expand(Expression instance, Expression[] arguments, System.Reflection.MethodInfo mi) diff --git a/Signum.Entities/EnumMessages.cs b/Signum.Entities/EnumMessages.cs index 2046e905dc..41f6db6a8c 100644 --- a/Signum.Entities/EnumMessages.cs +++ b/Signum.Entities/EnumMessages.cs @@ -213,7 +213,7 @@ public enum SelectorMessage ConstructorSelector, [Description("Please choose a value to continue:")] PleaseChooseAValueToContinue, - [Description("Please select a Constructor")] + [Description("Please select a constructor")] PleaseSelectAConstructor, [Description("Please select one of the following types: ")] PleaseSelectAType, @@ -223,7 +223,11 @@ public enum SelectorMessage ValueMustBeSpecifiedFor0, ChooseAValue, SelectAnElement, - PleaseSelectAnElement + PleaseSelectAnElement, + [Description("{0} selector")] + _0Selector, + [Description("Please choose a {0} to continue:")] + PleaseChooseA0ToContinue, } public enum ConnectionMessage diff --git a/Signum.Entities/ModifiableEntity.cs b/Signum.Entities/ModifiableEntity.cs index 6b8c634284..ccadd229b7 100644 --- a/Signum.Entities/ModifiableEntity.cs +++ b/Signum.Entities/ModifiableEntity.cs @@ -217,18 +217,18 @@ protected virtual void ChildPropertyChanged(object sender, PropertyChangedEventA ModifiableEntity? parentEntity; public T? TryGetParentEntity() - where T: ModifiableEntity + where T: class, IModifiableEntity { - return parentEntity as T; + return ((IModifiableEntity?)parentEntity) as T; } public T GetParentEntity() - where T : ModifiableEntity + where T : IModifiableEntity { if (parentEntity == null) throw new InvalidOperationException("parentEntity is null"); - return (T)parentEntity; + return (T)(IModifiableEntity)parentEntity; } private void SetParentEntity(ModifiableEntity? p) diff --git a/Signum.Entities/PropertyAttributes.cs b/Signum.Entities/PropertyAttributes.cs index fc7607cf69..6b6536b016 100644 --- a/Signum.Entities/PropertyAttributes.cs +++ b/Signum.Entities/PropertyAttributes.cs @@ -1,3 +1,4 @@ +using Signum.Utilities; using System; using System.Collections.Generic; @@ -31,7 +32,7 @@ public class UnitAttribute : Attribute public static string? GetTranslation(string? unitName) { - if (string.IsNullOrEmpty(unitName)) + if (!unitName.HasText()) return null; if (UnitTranslations.TryGetValue(unitName, out var func)) diff --git a/Signum.Entities/Reflection/Reflector.cs b/Signum.Entities/Reflection/Reflector.cs index 43e974a426..875477573e 100644 --- a/Signum.Entities/Reflection/Reflector.cs +++ b/Signum.Entities/Reflection/Reflector.cs @@ -408,19 +408,19 @@ public static bool QueryableProperty(Type type, PropertyInfo pi) var pp = Validator.TryGetPropertyValidator(simpleRoute); if (pp != null) { - DateTimePrecisionValidatorAttribute datetimePrecision = pp.Validators.OfType().SingleOrDefaultEx(); + DateTimePrecisionValidatorAttribute? datetimePrecision = pp.Validators.OfType().SingleOrDefaultEx(); if (datetimePrecision != null) return datetimePrecision.FormatString; - TimeSpanPrecisionValidatorAttribute timeSpanPrecision = pp.Validators.OfType().SingleOrDefaultEx(); + TimeSpanPrecisionValidatorAttribute? timeSpanPrecision = pp.Validators.OfType().SingleOrDefaultEx(); if (timeSpanPrecision != null) return timeSpanPrecision.FormatString; - DecimalsValidatorAttribute decimals = pp.Validators.OfType().SingleOrDefaultEx(); + DecimalsValidatorAttribute? decimals = pp.Validators.OfType().SingleOrDefaultEx(); if (decimals != null) return "N" + decimals.DecimalPlaces; - StringCaseValidatorAttribute stringCase = pp.Validators.OfType().SingleOrDefaultEx(); + StringCaseValidatorAttribute? stringCase = pp.Validators.OfType().SingleOrDefaultEx(); if (stringCase != null) return stringCase.TextCase == StringCase.Lowercase ? "L" : "U"; } diff --git a/Signum.Entities/Signum.Entities.csproj b/Signum.Entities/Signum.Entities.csproj index becfcbe311..ca451d8f18 100644 --- a/Signum.Entities/Signum.Entities.csproj +++ b/Signum.Entities/Signum.Entities.csproj @@ -1,35 +1,35 @@ - - - - netcoreapp3.0 - preview - true - enable - true - x64;x86;AnyCPU - NU1605 - - - - - - - - - - - - - - - - PreserveNewest - - - - - - - - - + + + + netcoreapp3.0 + preview + true + enable + true + x64;x86;AnyCPU + NU1605 + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + + diff --git a/Signum.Entities/Translations/Signum.Entities.de.xml b/Signum.Entities/Translations/Signum.Entities.de.xml index f6288a15b7..ab65caa625 100644 --- a/Signum.Entities/Translations/Signum.Entities.de.xml +++ b/Signum.Entities/Translations/Signum.Entities.de.xml @@ -14,7 +14,12 @@ - + + + + + + @@ -25,6 +30,7 @@ + @@ -87,7 +93,7 @@ - + @@ -114,7 +120,10 @@ - + + + + @@ -141,11 +150,13 @@ + + @@ -181,6 +192,7 @@ + @@ -203,7 +215,6 @@ - @@ -224,7 +235,7 @@ - + @@ -235,6 +246,8 @@ + + @@ -244,6 +257,7 @@ + @@ -275,15 +289,17 @@ - + + + @@ -298,9 +314,12 @@ + + + @@ -321,21 +340,26 @@ + + + + + @@ -343,22 +367,35 @@ - + + + + + + + + + + + + + + @@ -382,8 +419,16 @@ - - + + + + + + + + + + @@ -395,13 +440,16 @@ + + + @@ -415,6 +463,7 @@ + @@ -431,30 +480,34 @@ - + + + - + - - - - + + + + - - + + + + - + diff --git a/Signum.Entities/Translations/Signum.Entities.es.xml b/Signum.Entities/Translations/Signum.Entities.es.xml index 949ac027bc..f20c51146b 100644 --- a/Signum.Entities/Translations/Signum.Entities.es.xml +++ b/Signum.Entities/Translations/Signum.Entities.es.xml @@ -289,7 +289,7 @@ - + @@ -504,4 +504,4 @@ - \ No newline at end of file + diff --git a/Signum.Entities/ValidationAttributes.cs b/Signum.Entities/ValidationAttributes.cs index 86eb5e2550..6663e967c8 100644 --- a/Signum.Entities/ValidationAttributes.cs +++ b/Signum.Entities/ValidationAttributes.cs @@ -80,19 +80,18 @@ public class NotNullValidatorAttribute : ValidatorAttribute return null; } - public override string HelpMessage - { - get { return ValidationMessage.BeNotNull.NiceToString(); } - } + public override string HelpMessage => ValidationMessage.BeNotNull.NiceToString(); + - } public static class NotNullValidatorExtensions { public static string? IsSetOnlyWhen(this (PropertyInfo pi, object? nullableValue) tuple, bool shouldBeSet) { - var isNull = tuple.nullableValue == null || tuple.nullableValue is string s && string.IsNullOrEmpty(s); + var isNull = tuple.nullableValue == null || + tuple.nullableValue is string s && string.IsNullOrEmpty(s) || + tuple.nullableValue is ICollection col && col.Count == 0; if (isNull && shouldBeSet) return ValidationMessage._0IsNotSet.NiceToString(tuple.pi.NiceName()); @@ -172,7 +171,9 @@ public override string HelpMessage string result = min != -1 && max != -1 ? ValidationMessage.HaveBetween0And1Characters.NiceToString().FormatWith(min, max) : min != -1 ? ValidationMessage.HaveMinimum0Characters.NiceToString().FormatWith(min) : - max != -1 ? ValidationMessage.HaveMaximum0Characters.NiceToString().FormatWith(max) : ValidationMessage.BeAString.NiceToString(); + max != -1 ? ValidationMessage.HaveMaximum0Characters.NiceToString().FormatWith(max) : + MultiLine ? ValidationMessage.BeAMultilineString.NiceToString() : + ValidationMessage.BeAString.NiceToString(); return result; } @@ -215,13 +216,7 @@ public abstract string FormatName return ValidationMessage._0DoesNotHaveAValid1Format.NiceToString().FormatWith("{0}", FormatName); } - public override string HelpMessage - { - get - { - return ValidationMessage.HaveValid0Format.NiceToString().FormatWith(FormatName); - } - } + public override string HelpMessage => ValidationMessage.HaveValid0Format.NiceToString().FormatWith(FormatName); } public class EMailValidatorAttribute : RegexValidatorAttribute @@ -369,15 +364,9 @@ public FileNameValidatorAttribute() { } - public string FormatName - { - get { return ValidationMessage.FileName.NiceToString(); } - } + public string FormatName => ValidationMessage.FileName.NiceToString(); - public override string HelpMessage - { - get { return ValidationMessage.HaveValid0Format.NiceToString().FormatWith(FormatName); } - } + public override string HelpMessage => ValidationMessage.HaveValid0Format.NiceToString().FormatWith(FormatName); protected override string? OverrideError(object? value) { @@ -425,10 +414,7 @@ public DecimalsValidatorAttribute(int decimalPlaces) return null; } - public override string HelpMessage - { - get { return ValidationMessage.Have0Decimals.NiceToString().FormatWith(DecimalPlaces); } - } + public override string HelpMessage => ValidationMessage.Have0Decimals.NiceToString().FormatWith(DecimalPlaces); } @@ -493,13 +479,10 @@ public NumberIsValidatorAttribute(ComparisonType comparison, long number) if (ok) return null; - return ValidationMessage._0ShouldBe12.NiceToString().FormatWith("{0}", ComparisonType.NiceToString(), number.ToString()); + return ValidationMessage._0ShouldBe12.NiceToString().FormatWith("{0}", ComparisonType.NiceToString().ToLower(), number.ToString()); } - public override string HelpMessage - { - get { return ValidationMessage.Be.NiceToString() + ComparisonType.NiceToString() + " " + number.ToString(); } - } + public override string HelpMessage => ValidationMessage.Be0.NiceToString(ComparisonType.NiceToString().ToLower() + " " + number.ToString()); } //Not using C intervals to please user! @@ -564,10 +547,7 @@ public NumberBetweenValidatorAttribute(long min, long max) return ValidationMessage._0HasToBeBetween1And2.NiceToString("{0}", min, max); } - public override string HelpMessage - { - get { return ValidationMessage.BeBetween0And1.NiceToString(min, max); } - } + public override string HelpMessage => ValidationMessage.BeBetween0And1.NiceToString(min, max); } @@ -599,10 +579,7 @@ static bool IsPowerOfTwo(long n) return null; } - public override string HelpMessage - { - get { return ValidationMessage.Be.NiceToString() + ValidationMessage.PowerOf.NiceToString() + " " + 2; } - } + public override string HelpMessage => ValidationMessage.Be0.NiceToString(ValidationMessage.PowerOf.NiceToString() + " " + 2); } public class NoRepeatValidatorAttribute : ValidatorAttribute @@ -618,10 +595,7 @@ public class NoRepeatValidatorAttribute : ValidatorAttribute return null; } - public override string HelpMessage - { - get { return ValidationMessage.HaveNoRepeatedElements.NiceToString(); } - } + public override string HelpMessage => ValidationMessage.HaveNoRepeatedElements.NiceToString(); public static string? ByKey(IEnumerable collection, Func keySelector) { @@ -662,10 +636,7 @@ public CountIsValidatorAttribute(ComparisonType comparison, int number) return ValidationMessage.TheNumberOfElementsOf0HasToBe12.NiceToString().FormatWith("{0}", ComparisonType.NiceToString().FirstLower(), number.ToString()); } - public override string HelpMessage - { - get { return ValidationMessage.HaveANumberOfElements01.NiceToString().FormatWith(ComparisonType.NiceToString().FirstLower(), number.ToString()); } - } + public override string HelpMessage => ValidationMessage.HaveANumberOfElements01.NiceToString().FormatWith(ComparisonType.NiceToString().FirstLower(), number.ToString()); } [DescriptionOptions(DescriptionOptions.Members)] @@ -742,13 +713,7 @@ public string FormatString } } - public override string HelpMessage - { - get - { - return ValidationMessage.HaveAPrecisionOf.NiceToString() + " " + Precision.NiceToString().ToLower(); - } - } + public override string HelpMessage => ValidationMessage.HaveAPrecisionOf0.NiceToString(Precision.NiceToString().ToLower()); } public class DateInPastValidator : ValidatorAttribute @@ -764,13 +729,7 @@ public class DateInPastValidator : ValidatorAttribute return null; } - public override string HelpMessage - { - get - { - return ValidationMessage.BeInThePast.NiceToString(); - } - } + public override string HelpMessage => ValidationMessage.BeInThePast.NiceToString(); } public class YearGreaterThanValidator : ValidatorAttribute @@ -793,13 +752,7 @@ public YearGreaterThanValidator(int minYear) return null; } - public override string HelpMessage - { - get - { - return ValidationMessage.BeInThePast.NiceToString(); - } - } + public override string HelpMessage => ValidationMessage.BeInThePast.NiceToString(); } @@ -842,13 +795,7 @@ public string FormatString } } - public override string HelpMessage - { - get - { - return ValidationMessage.HaveAPrecisionOf.NiceToString() + " " + Precision.NiceToString().ToLower(); - } - } + public override string HelpMessage => ValidationMessage.HaveAPrecisionOf0.NiceToString(Precision.NiceToString().ToLower()); } public class TimeOfDayValidatorAttribute : ValidatorAttribute @@ -868,14 +815,8 @@ public TimeOfDayValidatorAttribute() return null; } - - public override string HelpMessage - { - get - { - return ValidationMessage.IsATimeOfTheDay.NiceToString(); - } - } + + public override string HelpMessage => ValidationMessage.IsATimeOfTheDay.NiceToString(); } public class StringCaseValidatorAttribute : ValidatorAttribute @@ -895,7 +836,7 @@ public StringCaseValidatorAttribute(StringCase textCase) protected override string? OverrideError(object? value) { string? str = (string?)value; - if (string.IsNullOrEmpty(str)) + if (!str.HasText()) return null; if ((this.textCase == StringCase.Uppercase) && (str != str.ToUpper())) @@ -907,10 +848,7 @@ public StringCaseValidatorAttribute(StringCase textCase) return null; } - public override string HelpMessage - { - get { return ValidationMessage.Be.NiceToString() + textCase.NiceToString(); } - } + public override string HelpMessage => ValidationMessage.Be0.NiceToString(textCase.NiceToString()); } [DescriptionOptions(DescriptionOptions.Members)] @@ -945,10 +883,7 @@ public IsAssignableToValidatorAttribute(Type type) return null; } - public override string HelpMessage - { - get { return ValidationMessage.BeA0_G.NiceToString().ForGenderAndNumber(Type.GetGender()).FormatWith(Type.NiceName()); } - } + public override string HelpMessage => ValidationMessage.BeA0_G.NiceToString().ForGenderAndNumber(Type.GetGender()).FormatWith(Type.NiceName()); } public class IpValidatorAttribute : RegexValidatorAttribute @@ -1107,8 +1042,8 @@ public enum ValidationMessage _0IsNotA1_G, [Description("be a {0}")] BeA0_G, - [Description("be ")] - Be, + [Description("be {0}")] + Be0, [Description("be between {0} and {1}")] BeBetween0And1, [Description("be not null")] @@ -1119,8 +1054,8 @@ public enum ValidationMessage Have0Decimals, [Description("have a number of elements {0} {1}")] HaveANumberOfElements01, - [Description("have a precision of ")] - HaveAPrecisionOf, + [Description("have a precision of {0}")] + HaveAPrecisionOf0, [Description("have between {0} and {1} characters")] HaveBetween0And1Characters, [Description("have maximum {0} characters")] @@ -1209,6 +1144,7 @@ public enum ValidationMessage _AtLeastOneValueIsNeeded, PowerOf, BeAString, + BeAMultilineString, IsATimeOfTheDay, } } diff --git a/Signum.React/ApiControllers/OperationController.cs b/Signum.React/ApiControllers/OperationController.cs index f9b742ed1d..7d11345b3b 100644 --- a/Signum.React/ApiControllers/OperationController.cs +++ b/Signum.React/ApiControllers/OperationController.cs @@ -217,7 +217,7 @@ public StateCanExecuteResponse StateCanExecutes([Required, FromBody]StateCanExec .Select(operationKey => types.Select(t => BaseOperationRequest.ParseOperationAssert(operationKey, t)).Distinct().SingleEx()) .ToList(); - var result = OperationLogic.GetContextualCanExecute(request.lites, operationSymbols)!; + var result = OperationLogic.GetContextualCanExecute(request.lites, operationSymbols); var anyReadonly = AnyReadonly.GetInvocationListTyped().Any(f => f(request.lites)); return new StateCanExecuteResponse(result.SelectDictionary(a => a.Key, v => v)) diff --git a/Signum.React/Facades/SignumServer.cs b/Signum.React/Facades/SignumServer.cs index 9ba7549a56..685298d480 100644 --- a/Signum.React/Facades/SignumServer.cs +++ b/Signum.React/Facades/SignumServer.cs @@ -88,17 +88,18 @@ public static EntityPackTS GetEntityPack(Entity entity) canExecutes.ToDictionary(a => a.Key.Key, a => a.Value) ); - foreach (var action in EntityPackTS.AddExtension.GetInvocationListTyped()) - { - try - { - action(result); - } - catch (Exception) when (StartParameters.IgnoredDatabaseMismatches != null) + if (EntityPackTS.AddExtension != null) + foreach (var action in EntityPackTS.AddExtension.GetInvocationListTyped()) { - + try + { + action(result); + } + catch (Exception) when (StartParameters.IgnoredDatabaseMismatches != null) + { + + } } - } return result; } diff --git a/Signum.React/Filters/SignumExceptionFilterAttribute.cs b/Signum.React/Filters/SignumExceptionFilterAttribute.cs index b3c15c9c08..fad8dd72d9 100644 --- a/Signum.React/Filters/SignumExceptionFilterAttribute.cs +++ b/Signum.React/Filters/SignumExceptionFilterAttribute.cs @@ -24,7 +24,9 @@ namespace Signum.React.Filters { public class SignumExceptionFilterAttribute : IAsyncResourceFilter { - public static Func IncludeErrorDetails = ctx => true; + public static Func TranslateExceptionMessage = ex => ex is ApplicationException; + + public static Func IncludeErrorDetails = ex => true; public static readonly List IgnoreExceptions = new List { typeof(OperationCanceledException) }; @@ -52,21 +54,27 @@ public async Task OnResourceExecutionAsync(ResourceExecutingContext precontext, e.UrlReferer = Try(int.MaxValue, () => req.Headers["Referer"].ToString()); e.UserHostAddress = Try(100, () => connFeature.RemoteIpAddress.ToString()); e.UserHostName = Try(100, () => Dns.GetHostEntry(connFeature.RemoteIpAddress).HostName); - e.User = UserHolder.Current?.ToLite(); + e.User = (UserHolder.Current ?? (IUserEntity)context.HttpContext.Items[SignumAuthenticationFilter.Signum_User_Key])?.ToLite(); e.QueryString = Try(int.MaxValue, () => req.QueryString.ToString()); e.Form = Try(int.MaxValue, () => ReadAllBody(context.HttpContext)); e.Session = null; }); - + if (ExpectsJsonResult(context)) { - var error = new HttpError(context.Exception,IncludeErrorDetails(context.Exception)); - var response = context.HttpContext.Response; - response.StatusCode = (int)statusCode; - response.ContentType = "application/json"; - await response.WriteAsync(JsonConvert.SerializeObject(error, SignumServer.JsonSerializerSettings)); - context.ExceptionHandled = true; + var ci = TranslateExceptionMessage(context.Exception) ? SignumCultureSelectorFilter.GetCurrentCulture?.Invoke(precontext) : null; + + using (ci == null ? null : CultureInfoUtils.ChangeBothCultures(ci)) + { + var error = new HttpError(context.Exception, IncludeErrorDetails(context.Exception)); + + var response = context.HttpContext.Response; + response.StatusCode = (int)statusCode; + response.ContentType = "application/json"; + await response.WriteAsync(JsonConvert.SerializeObject(error, SignumServer.JsonSerializerSettings)); + context.ExceptionHandled = true; + } } } } diff --git a/Signum.React/Filters/SignumFilters.cs b/Signum.React/Filters/SignumFilters.cs index 88948355cc..7204837cf2 100644 --- a/Signum.React/Filters/SignumFilters.cs +++ b/Signum.React/Filters/SignumFilters.cs @@ -43,7 +43,9 @@ public void OnResourceExecuted(ResourceExecutedContext context) public class SignumAuthenticationFilter : SignumDisposableResourceFilter { - public SignumAuthenticationFilter() : base("Signum_User") { } + public const string Signum_User_Key = "Signum_User"; + + public SignumAuthenticationFilter() : base("Signum_User_Session") { } public static readonly IList> Authenticators = new List>(); @@ -66,19 +68,21 @@ public SignumAuthenticationFilter() : base("Signum_User") { } if (result == null) return null; + context.HttpContext.Items[Signum_User_Key] = result.User; + return result.User != null ? UserHolder.UserSession(result.User) : null; } } public class SignumCultureSelectorFilter : IResourceFilter { - public static Func? GetCurrentCultures; + public static Func? GetCurrentCulture; const string Culture_Key = "OldCulture"; const string UICulture_Key = "OldUICulture"; public void OnResourceExecuting(ResourceExecutingContext context) { - var culture = GetCurrentCultures?.Invoke(context); + var culture = GetCurrentCulture?.Invoke(context); if (culture != null) { context.HttpContext.Items[Culture_Key] = CultureInfo.CurrentCulture; diff --git a/Signum.React/JsonConverters/EntityJsonConverter.cs b/Signum.React/JsonConverters/EntityJsonConverter.cs index 9d32e843f0..003fb971e5 100644 --- a/Signum.React/JsonConverters/EntityJsonConverter.cs +++ b/Signum.React/JsonConverters/EntityJsonConverter.cs @@ -6,6 +6,7 @@ using Signum.Entities.Reflection; using Signum.React.Facades; using Signum.Utilities; +using Signum.Utilities.DataStructures; using Signum.Utilities.ExpressionTrees; using Signum.Utilities.Reflection; using System; @@ -389,7 +390,7 @@ public void ReadJsonProperty(JsonReader reader, JsonSerializer serializer, Modif private bool IsEquals(object newValue, object? oldValue) { if (newValue is byte[] && oldValue is byte[]) - return MemCompare.Compare((byte[])newValue, (byte[])oldValue); + return MemComparer.Equals((byte[])newValue, (byte[])oldValue); if (newValue is DateTime && oldValue is DateTime) return Math.Abs(((DateTime)newValue).Subtract((DateTime)oldValue).TotalMilliseconds) < 10; //Json dates get rounded @@ -531,18 +532,5 @@ static Type GetEntityType(string typeStr, Type objectType) } - static class MemCompare - { - [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] -#pragma warning disable IDE1006 // Naming Styles - static extern int memcmp(byte[] b1, byte[] b2, long count); -#pragma warning restore IDE1006 // Naming Styles - - public static bool Compare(byte[] b1, byte[] b2) - { - // Validate buffers are the same length. - // This also ensures that the count does not exceed the length of either buffer. - return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0; - } - } + } diff --git a/Signum.React/Scripts/Components/Collapse.tsx b/Signum.React/Scripts/Components/Collapse.tsx index e7cd425a9d..8b6c412aa1 100644 --- a/Signum.React/Scripts/Components/Collapse.tsx +++ b/Signum.React/Scripts/Components/Collapse.tsx @@ -4,7 +4,7 @@ import { classes } from '../Globals'; interface CollapseProps { isOpen?: boolean; - tag?: React.ComponentType>; + tag?: string | React.ComponentType>; attrs?: React.HTMLAttributes; navbar?: boolean; diff --git a/Signum.React/Scripts/Finder.tsx b/Signum.React/Scripts/Finder.tsx index f9e63781cf..45715abae3 100644 --- a/Signum.React/Scripts/Finder.tsx +++ b/Signum.React/Scripts/Finder.tsx @@ -71,6 +71,10 @@ export function getSettings(queryName: PseudoType | QueryKey): QuerySettings | u return querySettings[getQueryKey(queryName)]; } +export function getOrAddSettings(queryName: PseudoType | QueryKey): QuerySettings { + return querySettings[getQueryKey(queryName)] || (querySettings[getQueryKey(queryName)] = { queryName: queryName }); +} + export const isFindableEvent: Array<(queryKey: string, fullScreen: boolean) => boolean> = []; export function isFindable(queryName: PseudoType | QueryKey, fullScreen: boolean): boolean { @@ -500,14 +504,14 @@ export function getDefaultOrder(qd: QueryDescription, qs: QuerySettings | undefi } as OrderOption; } -export function getDefaultFilter(qd: QueryDescription, qs: QuerySettings | undefined): FilterOption[] | undefined { +export function getDefaultFilter(qd: QueryDescription | undefined, qs: QuerySettings | undefined): FilterOption[] | undefined { if (qs && qs.simpleFilterBuilder) return undefined; if (qs && qs.defaultFilters) return qs.defaultFilters; - if (qd.columns["Entity"]) { + if (qd == null || qd.columns["Entity"]) { return [ { groupOperation: "Or", diff --git a/Signum.React/Scripts/Frames/FrameModal.tsx b/Signum.React/Scripts/Frames/FrameModal.tsx index ac19ce3877..08621c3752 100644 --- a/Signum.React/Scripts/Frames/FrameModal.tsx +++ b/Signum.React/Scripts/Frames/FrameModal.tsx @@ -219,7 +219,7 @@ export default class FrameModal extends React.Component {this.renderTitle()} - {pack && this.renderBody()} + {pack && this.renderBody(pack)} ); } @@ -235,33 +235,32 @@ export default class FrameModal extends React.Component) { const frame: EntityFrame = { frameComponent: this, entityComponent: this.entityComponent, - onReload: (pack, reloadComponent, callback) => { - var newPack = pack || this.state.pack!; + onReload: (newPack, reloadComponent, callback) => { + var newPackOrPack = newPack || pack; if (reloadComponent) { - this.setState({ getComponent: undefined }, () => this.loadComponent(newPack, callback).done()); //For AutoFocus and potentialy another view + this.setState({ getComponent: undefined }, () => this.loadComponent(newPackOrPack, callback).done()); //For AutoFocus and potentialy another view } else { - this.setPack(newPack, callback); + this.setPack(newPackOrPack, callback); } }, - pack: this.state.pack, - onClose: (ok?: boolean) => this.props.onExited!(ok ? this.state.pack!.entity : undefined), + pack: pack, + onClose: (ok?: boolean) => this.props.onExited!(ok ? pack.entity : undefined), revalidate: () => this.validationErrors && this.validationErrors.forceUpdate(), setError: (modelState, initialPrefix = "") => { - GraphExplorer.setModelState(this.state.pack!.entity, modelState, initialPrefix!); + GraphExplorer.setModelState(pack.entity, modelState, initialPrefix!); this.forceUpdate(); }, refreshCount: this.state.refreshCount, allowChangeEntity: this.props.isNavigate || false, }; - const pack = this.state.pack!; const styleOptions: StyleOptions = { readOnly: this.props.readOnly != undefined ? this.props.readOnly : Navigator.isReadOnly(pack), @@ -270,13 +269,13 @@ export default class FrameModal extends React.Component = { ctx: ctx, pack: pack }; + const wc: WidgetContext = { ctx: ctx, frame: frame }; const embeddedWidgets = renderEmbeddedWidgets(wc); return (
- {renderWidgets({ ctx: ctx, pack: pack })} + {renderWidgets(wc)} {this.entityComponent && this.buttonBar = bb} frame={frame} pack={pack} isOperationVisible={this.props.isOperationVisible} />} this.validationErrors = ve} prefix={this.prefix} /> {embeddedWidgets.top} @@ -392,4 +391,17 @@ export class FunctionalAdapter extends React.Component { return {element} } } + + static isInstanceOf(component: React.Component | null | undefined, type: React.ComponentType) { + + if (component instanceof type) + return true; + + if (component instanceof FunctionalAdapter) { + var only = React.Children.only(component.props.children); + return React.isValidElement(only) && only.type == type; + } + + return false + } } diff --git a/Signum.React/Scripts/Frames/FramePage.tsx b/Signum.React/Scripts/Frames/FramePage.tsx index 99786a919c..714a8f8e23 100644 --- a/Signum.React/Scripts/Frames/FramePage.tsx +++ b/Signum.React/Scripts/Frames/FramePage.tsx @@ -200,7 +200,7 @@ export default class FramePage extends React.Component = { ctx: ctx, - pack: this.state.pack, + frame: frame, }; const embeddedWidgets = renderEmbeddedWidgets(wc); diff --git a/Signum.React/Scripts/Frames/Widgets.tsx b/Signum.React/Scripts/Frames/Widgets.tsx index e08b788755..f993c90778 100644 --- a/Signum.React/Scripts/Frames/Widgets.tsx +++ b/Signum.React/Scripts/Frames/Widgets.tsx @@ -1,11 +1,11 @@ import * as React from 'react' import { EntityPack, ModifiableEntity } from '../Signum.Entities' -import { TypeContext } from '../TypeContext' +import { TypeContext, EntityFrame } from '../TypeContext' import "./Widgets.css" export interface WidgetContext { ctx: TypeContext; - pack: EntityPack; + frame: EntityFrame; } export const onWidgets: Array<(ctx: WidgetContext) => React.ReactElement | undefined> = []; diff --git a/Signum.React/Scripts/Globals.ts b/Signum.React/Scripts/Globals.ts index 689d37865d..8e8371c70e 100644 --- a/Signum.React/Scripts/Globals.ts +++ b/Signum.React/Scripts/Globals.ts @@ -3,6 +3,10 @@ declare global { function require(path: string): T; function require(paths: string[], callback: (...modules: any[]) => void): void; + interface RegExpConstructor { + escape(s: string): string; + } + interface Promise { done(this: Promise): void; } @@ -78,6 +82,12 @@ declare global { extract(this: Array, filter: (element: T) => boolean): T[]; findIndex(this: Array, filter: (element: T, index: number, obj: Array) => boolean): number; findLastIndex(this: Array, filter: (element: T) => boolean): number; + toTree(this: Array, getKey: (element: T) => string, getParentKey: (element: T) => string | null | undefined): TreeNode[]; + } + + interface TreeNode { + value: T; + children: TreeNode[]; } interface ArrayConstructor { @@ -577,6 +587,33 @@ if (!Array.prototype.findLastIndex) { }; } +if (!Array.prototype.toTree) { + Array.prototype.toTree = function toTree(this: any[], getKey: (element: any) => string, getParentKey: (element: any) => string | null | undefined) { + + var top: TreeNode = { value: null, children: [] }; + + var dic: { [key: string]: TreeNode } = {}; + + function createNode(item: any) { + + var key = getKey(item); + if (dic[key]) + return dic[key]; + + var itemNode: TreeNode = { value: item, children: [] }; + + var parentKey = getParentKey(item); + var parent = parentKey ? dic[parentKey] : top; + parent.children.push(itemNode); + return dic[key] = itemNode; + } + + this.forEach(n => createNode(n)); + + return top.children; + } +} + Array.range = function (min: number, maxNotIncluded: number) { const length = maxNotIncluded - min; diff --git a/Signum.React/Scripts/Hooks.ts b/Signum.React/Scripts/Hooks.ts index a683625e85..89a9de48da 100644 --- a/Signum.React/Scripts/Hooks.ts +++ b/Signum.React/Scripts/Hooks.ts @@ -15,18 +15,25 @@ interface APIHookOptions{ avoidReset?: boolean; } +export function useTitle(title: string, deps?: readonly any[]) { + React.useEffect(() => { + Navigator.setTitle(title); + return () => Navigator.setTitle(); + }, deps); +} + export function useAPI(defaultValue: T, key: ReadonlyArray | undefined, makeCall: (signal: AbortSignal) => Promise, options?: APIHookOptions): T { - const [data, updateData] = React.useState(defaultValue) + const [data, setData] = React.useState(defaultValue); React.useEffect(() => { var abortController = new AbortController(); if (options == null || !options.avoidReset) - updateData(defaultValue); + setData(defaultValue); makeCall(abortController.signal) - .then(result => !abortController.signal.aborted && updateData(result)) + .then(result => !abortController.signal.aborted && setData(result)) .done(); return () => { diff --git a/Signum.React/Scripts/Lines/AutoCompleteConfig.tsx b/Signum.React/Scripts/Lines/AutoCompleteConfig.tsx index f0af1feb6f..42e6771350 100644 --- a/Signum.React/Scripts/Lines/AutoCompleteConfig.tsx +++ b/Signum.React/Scripts/Lines/AutoCompleteConfig.tsx @@ -1,11 +1,12 @@ import * as React from 'react' import * as Finder from '../Finder' import { AbortableRequest } from '../Services' -import { FindOptions, FilterOptionParsed, OrderOptionParsed, OrderRequest, ResultRow, ColumnOptionParsed, ColumnRequest } from '../FindOptions' -import { getTypeInfo, getQueryKey, QueryTokenString } from '../Reflection' -import { ModifiableEntity, Lite, Entity, toLite, is, isLite, isEntity, getToString, liteKey } from '../Signum.Entities' +import { FindOptions, FilterOptionParsed, OrderOptionParsed, OrderRequest, ResultRow, ColumnOptionParsed, ColumnRequest, QueryDescription } from '../FindOptions' +import { getTypeInfo, getQueryKey, QueryTokenString, getTypeName } from '../Reflection' +import { ModifiableEntity, Lite, Entity, toLite, is, isLite, isEntity, getToString, liteKey, SearchMessage } from '../Signum.Entities' import { Typeahead } from '../Components' import { toFilterRequests } from '../Finder'; +import { AutocompleteConstructor, getAutocompleteConstructors } from '../Navigator'; export interface AutocompleteConfig { getItems: (subStr: string) => Promise; @@ -19,9 +20,13 @@ export interface AutocompleteConfig { abort(): void; } -export class LiteAutocompleteConfig implements AutocompleteConfig>{ +export function isAutocompleteConstructor(a: any): a is AutocompleteConstructor { + return (a as AutocompleteConstructor).onClick != null; +} + +export class LiteAutocompleteConfig implements AutocompleteConfig | AutocompleteConstructor>{ constructor( - public getItemsFunction: (signal: AbortSignal, subStr: string) => Promise[]>, + public getItemsFunction: (signal: AbortSignal, subStr: string) => Promise<(Lite | AutocompleteConstructor)[]>, public requiresInitialLoad: boolean, public showType: boolean) { } @@ -36,7 +41,13 @@ export class LiteAutocompleteConfig implements AutocompleteCon return this.abortableRequest.getData(subStr); } - renderItem(item: Lite, subStr: string) { + renderItem(item: Lite | AutocompleteConstructor, subStr: string) { + + if (isAutocompleteConstructor(item)) { + var ti = getTypeInfo(item.type); + return {SearchMessage.CreateNew0_G.niceToString().forGenderAndNumber(ti.gender).formatWith(ti.niceName)} "{subStr}"; + } + var toStr = getToString(item); var text = Typeahead.highlightedText(toStr, subStr); if (this.showType) @@ -45,11 +56,19 @@ export class LiteAutocompleteConfig implements AutocompleteCon return text; } - getEntityFromItem(item: Lite): Promise | ModifiableEntity> { + getEntityFromItem(item: Lite | AutocompleteConstructor): Promise | ModifiableEntity | undefined> { + + if (isAutocompleteConstructor(item)) + return item.onClick() as Promise | ModifiableEntity | undefined>; + return Promise.resolve(item); } - getDataKeyFromItem(item: Lite): string | undefined { + getDataKeyFromItem(item: Lite | AutocompleteConstructor): string | undefined { + + if (isAutocompleteConstructor(item)) + return "create-" + getTypeName(item.type); + return liteKey(item); } @@ -65,7 +84,7 @@ export class LiteAutocompleteConfig implements AutocompleteCon return this.abortableRequest.getData(lite.id!.toString()).then(lites => { - const result = lites.filter(a => is(a, lite)).firstOrNull(); + const result = lites.filter(a => isLite(a) && is(a, lite)).firstOrNull() as Lite | null; if (!result) throw new Error("Impossible to getInitialItem with the current implementation of getItems"); @@ -86,13 +105,18 @@ export class LiteAutocompleteConfig implements AutocompleteCon } } -export class FindOptionsAutocompleteConfig implements AutocompleteConfig{ +interface FindOptionsAutocompleteConfigOptions { + getAutocompleteConstructor?: (str: string, foundRows: ResultRow[]) => AutocompleteConstructor[], + count?: number, + requiresInitialLoad?: boolean, + showType?: boolean, +} + +export class FindOptionsAutocompleteConfig implements AutocompleteConfig>{ constructor( public findOptions: FindOptions, - public count: number = 5, - public requiresInitialLoad: boolean = false, - public showType: boolean = false, + public options?: FindOptionsAutocompleteConfigOptions ) { Finder.expandParentColumn(this.findOptions); } @@ -102,68 +126,83 @@ export class FindOptionsAutocompleteConfig implements AutocompleteConfig { + getParsedFilters(qd: QueryDescription): Promise { if (this.parsedFilters) return Promise.resolve(this.parsedFilters); - return Finder.getQueryDescription(this.findOptions.queryName) - .then(qd => Finder.parseFilterOptions(this.findOptions.filterOptions || [], false, qd)) + return Finder.parseFilterOptions(this.findOptions.filterOptions || [], false, qd) .then(filters => this.parsedFilters = filters); } parsedOrders?: OrderOptionParsed[]; - getParsedOrders(): Promise { + getParsedOrders(qd: QueryDescription): Promise { if (this.parsedOrders) return Promise.resolve(this.parsedOrders); - return Finder.getQueryDescription(this.findOptions.queryName) - .then(qd => Finder.parseOrderOptions(this.findOptions.orderOptions || [], false, qd)) + return Finder.parseOrderOptions(this.findOptions.orderOptions || [], false, qd) .then(orders => this.parsedOrders = orders); } parsedColumns?: ColumnOptionParsed[]; - getParsedColumns(): Promise { + getParsedColumns(qd: QueryDescription): Promise { if (this.parsedColumns) return Promise.resolve(this.parsedColumns); - return Finder.getQueryDescription(this.findOptions.queryName) - .then(qd => Finder.parseColumnOptions(this.findOptions.columnOptions || [], false, qd)) + return Finder.parseColumnOptions(this.findOptions.columnOptions || [], false, qd) .then(columns => this.parsedColumns = columns); } abortableRequest = new AbortableRequest((abortController, request: Finder.API.AutocompleteQueryRequest) => Finder.API.FindRowsLike(request, abortController)); - getItems(subStr: string): Promise { - return this.getParsedFilters().then(filters => - this.getParsedOrders().then(orders => - this.getParsedColumns().then(columns => + async getItems(subStr: string): Promise<(ResultRow | AutocompleteConstructor)[]> { + + return Finder.getQueryDescription(this.findOptions.queryName) + .then(qd => Promise.all( + [ + this.getParsedFilters(qd), + this.getParsedOrders(qd), + this.getParsedColumns(qd) + ]).then(([filters, orders, columns]) => this.abortableRequest.getData({ queryKey: getQueryKey(this.findOptions.queryName), columns: columns.map(c => ({ token: c.token!.fullKey, displayName: c.displayName }) as ColumnRequest), filters: toFilterRequests(filters), orders: orders.map(o => ({ token: o.token!.fullKey, orderType: o.orderType }) as OrderRequest), - count: this.count, + count: this.options && this.options.count || 5, subString: subStr - }).then(rt => rt.rows) + }).then(rt => [ + ...rt.rows, + ...this.options && this.options.getAutocompleteConstructor && this.options.getAutocompleteConstructor(subStr, rt.rows) || [] + ]) ) - ) - ); + ); } - renderItem(item: ResultRow, subStr: string) { + renderItem(item: ResultRow | AutocompleteConstructor, subStr: string) { + if (isAutocompleteConstructor(item)) { + var ti = getTypeInfo(item.type); + return {SearchMessage.CreateNew0_G.niceToString().forGenderAndNumber(ti.gender).formatWith(ti.niceName)} "{subStr}"; + } + var toStr = getToString(item.entity!); var text = Typeahead.highlightedText(toStr, subStr); - if (this.showType) + if (this.options && this.options.showType) return {getTypeInfo(item.entity!.EntityType).niceName} {text}; else return text; } - getEntityFromItem(item: ResultRow): Promise | ModifiableEntity> { + getEntityFromItem(item: ResultRow): Promise | ModifiableEntity | undefined> { + if (isAutocompleteConstructor(item)) + return item.onClick() as Promise | ModifiableEntity | undefined>; + return Promise.resolve(item.entity!); } getDataKeyFromItem(item: ResultRow): string | undefined { + if (isAutocompleteConstructor(item)) + return "create-" + getTypeName(item.type); + return liteKey(item.entity!); } @@ -171,13 +210,13 @@ export class FindOptionsAutocompleteConfig implements AutocompleteConfig + return Finder.getQueryDescription(this.findOptions.queryName).then(qd => this.getParsedColumns(qd)).then(columns => Finder.API.FindRowsLike({ queryKey: getQueryKey(this.findOptions.queryName), diff --git a/Signum.React/Scripts/Lines/DynamicComponent.tsx b/Signum.React/Scripts/Lines/DynamicComponent.tsx index 17262399b7..46142eb004 100644 --- a/Signum.React/Scripts/Lines/DynamicComponent.tsx +++ b/Signum.React/Scripts/Lines/DynamicComponent.tsx @@ -1,13 +1,14 @@ import * as React from 'react' import { Dic } from '../Globals' -import { getTypeInfos } from '../Reflection' +import { getTypeInfos, TypeReference } from '../Reflection' import { ModifiableEntity } from '../Signum.Entities' import * as Navigator from '../Navigator' import { ViewReplacer } from '../Frames/ReactVisitor' -import { ValueLine, EntityLine, EntityCombo, EntityDetail, EntityStrip, TypeContext, EntityCheckboxList, EnumCheckboxList, EntityTable } from '../Lines' +import { ValueLine, EntityLine, EntityCombo, EntityDetail, EntityStrip, TypeContext, EntityCheckboxList, EnumCheckboxList, EntityTable, PropertyRoute } from '../Lines' import { Type } from '../Reflection'; import { EntityRepeater } from './EntityRepeater'; import { MultiValueLine } from './MultiValueLine'; +import { faUnderline } from '@fortawesome/free-solid-svg-icons'; export default class DynamicComponent extends React.Component<{ ctx: TypeContext, viewName?: string }> { render() { @@ -47,78 +48,88 @@ export default class DynamicComponent extends React.Component<{ ctx: TypeContext } static getAppropiateComponent(ctx: TypeContext): React.ReactElement | undefined { - const mi = ctx.propertyRoute.member; + return DynamicComponent.getAppropiateComponentFactory(ctx.propertyRoute)(ctx); + } + static getAppropiateComponentFactory(pr: PropertyRoute): (ctx: TypeContext) => React.ReactElement | undefined { + const mi = pr.member; if (mi && (mi.name == "Id" || mi.notVisible == true)) - return undefined; + return ctx => undefined; - const ccProp = DynamicComponent.customPropertyComponent[ctx.propertyRoute.toString()]; + const ccProp = DynamicComponent.customPropertyComponent[pr.toString()]; if (ccProp) { - return ccProp(ctx) || undefined; + return ctx => ccProp(ctx) || undefined; } - const tr = ctx.propertyRoute.typeReference(); + const tr = pr.typeReference(); const ccType = DynamicComponent.customTypeComponent[tr.name]; if (ccType) { - var result = ccType(ctx); - if (result != "continue") - return result || undefined; + var basic = DynamicComponent.getAppropiateComponentFactoryBasic(tr); + + return ctx => { + var result = ccType(ctx); + return result == "continue" ? basic(ctx) : result || undefined; + }; } + return DynamicComponent.getAppropiateComponentFactoryBasic(tr); + } + + static getAppropiateComponentFactoryBasic(tr: TypeReference): (ctx: TypeContext) => React.ReactElement | undefined { let tis = getTypeInfos(tr); if (tis.length == 1 && tis[0] == undefined) tis = []; if (tr.isCollection) { if (tr.name == "[ALL]") - return ; + return ctx => ; if (tis.length) { if (tis.length == 1 && tis.first().kind == "Enum") - return ; + return ctx => ; if (tis.length == 1 && (tis.first().entityKind == "Part" || tis.first().entityKind == "SharedPart")) - return ; + return ctx => ; if (tis.every(t => t.entityKind == "Part" || t.entityKind == "SharedPart")) - return ; + return ctx => ; if (tis.every(t => t.isLowPopulation == true)) - return ; + return ctx => ; - return ; + return ctx => ; } if (tr.isEmbedded) - return ; + return ctx => ; - return ; + return ctx => ; } else { if (tr.name == "[ALL]") - return ; + return ctx => ; if (tis.length) { if (tis.length == 1 && tis.first().kind == "Enum") - return ; + return ctx => ; if (tis.every(t => t.entityKind == "Part" || t.entityKind == "SharedPart")) - return ; + return ctx => ; if (tis.every(t => t.isLowPopulation == true)) - return ; + return ctx => ; - return ; + return ctx => ; } if (tr.isEmbedded) - return ; + return ctx =>; if (ValueLine.getValueLineType(tr) != undefined) - return ; + return ctx =>; - return undefined; + return ctx => undefined; } } } diff --git a/Signum.React/Scripts/Lines/EntityBase.tsx b/Signum.React/Scripts/Lines/EntityBase.tsx index fbf365a887..0559096f0c 100644 --- a/Signum.React/Scripts/Lines/EntityBase.tsx +++ b/Signum.React/Scripts/Lines/EntityBase.tsx @@ -19,6 +19,7 @@ export interface EntityBaseProps extends LineBaseProps { viewOnCreate?: boolean; navigate?: boolean; create?: boolean; + createOnFind?: boolean; find?: boolean; remove?: boolean | ((item: any /*T*/) => boolean); @@ -272,12 +273,12 @@ export abstract class EntityBase | undefined> { if (this.state.findOptions) { - return Finder.find(this.state.findOptions); + return Finder.find(this.state.findOptions, { searchControlProps: { create: this.props.createOnFind } }); } return this.chooseType(ti => Finder.isFindable(ti, false)) .then | undefined>(qn => - qn == undefined ? undefined : Finder.find({ queryName: qn } as FindOptions)); + qn == undefined ? undefined : Finder.find({ queryName: qn } as FindOptions, { searchControlProps: { create: this.props.createOnFind } })); } handleFindClick = (event: React.SyntheticEvent) => { diff --git a/Signum.React/Scripts/Lines/EntityCombo.tsx b/Signum.React/Scripts/Lines/EntityCombo.tsx index 74301b1b0b..ee47fc905e 100644 --- a/Signum.React/Scripts/Lines/EntityCombo.tsx +++ b/Signum.React/Scripts/Lines/EntityCombo.tsx @@ -166,7 +166,7 @@ class EntityComboSelect extends React.Component{ctx.value && getToString(lite, this.props.liteToString)}; + return {ctx.value && getToString(lite, this.props.liteToString)}; return ( ); } else { return ( - + ); } } @@ -39,7 +39,7 @@ export class FormControlReadonly extends React.Component +
{this.props.children ||  }
); diff --git a/Signum.React/Scripts/Lines/MultiValueLine.tsx b/Signum.React/Scripts/Lines/MultiValueLine.tsx index 6b3c6ccdb4..9eb99464bb 100644 --- a/Signum.React/Scripts/Lines/MultiValueLine.tsx +++ b/Signum.React/Scripts/Lines/MultiValueLine.tsx @@ -66,12 +66,12 @@ export class MultiValueLine extends LineBase { mlistItemContext(s.ctx.subCtx({ formGroupStyle: "None" })).map((mlec, i) => - ( - + { e.preventDefault(); this.handleDeleteValue(i); }} onRenderItem={this.props.onRenderItem} /> - )) + ) } @@ -100,6 +100,8 @@ export class MultiValueLineElement extends React.Component @@ -111,7 +113,7 @@ export class MultiValueLineElement extends React.Component} - {this.props.onRenderItem ? this.props.onRenderItem(ctx) : DynamicComponent.getAppropiateComponent(ctx)} + {renderItem(ctx)} ); diff --git a/Signum.React/Scripts/Lines/RenderEntity.tsx b/Signum.React/Scripts/Lines/RenderEntity.tsx index 5c57475719..d2a3b0faa9 100644 --- a/Signum.React/Scripts/Lines/RenderEntity.tsx +++ b/Signum.React/Scripts/Lines/RenderEntity.tsx @@ -155,7 +155,7 @@ export class RenderEntity extends React.Component this.props.ctx.frame && this.props.ctx.frame.revalidate(), onClose: () => { throw new Error("Not implemented Exception"); }, onReload: pack => { throw new Error("Not implemented Exception"); }, diff --git a/Signum.React/Scripts/Lines/ValueLine.tsx b/Signum.React/Scripts/Lines/ValueLine.tsx index 05931df1df..a79270eca1 100644 --- a/Signum.React/Scripts/Lines/ValueLine.tsx +++ b/Signum.React/Scripts/Lines/ValueLine.tsx @@ -193,6 +193,7 @@ ValueLine.renderers["Checkbox" as ValueLineType] = (vl) => { ); } @@ -548,7 +549,7 @@ ValueLine.renderers["DateTime" as ValueLineType] = (vl) => { const momentFormat = toMomentFormat(s.formatText); - const m = s.ctx.value ? moment(s.ctx.value, moment.ISO_8601) : undefined; + const m = s.ctx.value ? moment(s.ctx.value) : undefined; const showTime = momentFormat != "L" && momentFormat != "LL"; if (s.ctx.readOnly) diff --git a/Signum.React/Scripts/Modals/ErrorModal.tsx b/Signum.React/Scripts/Modals/ErrorModal.tsx index 5e9829de5c..e3cfbea5c9 100644 --- a/Signum.React/Scripts/Modals/ErrorModal.tsx +++ b/Signum.React/Scripts/Modals/ErrorModal.tsx @@ -63,7 +63,7 @@ export default class ErrorModal extends React.Component StackTrace {this.state.showDetails &&
{se.httpError.stackTrace}
} diff --git a/Signum.React/Scripts/Navigator.tsx b/Signum.React/Scripts/Navigator.tsx index 7c0112cd02..e87d4de32b 100644 --- a/Signum.React/Scripts/Navigator.tsx +++ b/Signum.React/Scripts/Navigator.tsx @@ -345,18 +345,17 @@ function typeIsCreable(typeName: string): EntityWhen { export const isReadonlyEvent: Array<(typeName: string, entity?: EntityPack) => boolean> = []; -export function isReadOnly(typeOrEntity: PseudoType | EntityPack) { +export function isReadOnly(typeOrEntity: PseudoType | EntityPack, ignoreTypeIsReadonly: boolean = false) { const entityPack = isEntityPack(typeOrEntity) ? typeOrEntity : undefined; const typeName = isEntityPack(typeOrEntity) ? typeOrEntity.entity.Type : getTypeName(typeOrEntity as PseudoType); - const baseIsReadOnly = typeIsReadOnly(typeName); + const baseIsReadOnly = ignoreTypeIsReadonly ? false : typeIsReadOnly(typeName); return baseIsReadOnly || isReadonlyEvent.some(f => f(typeName, entityPack)); } - function typeIsReadOnly(typeName: string): boolean { const es = entitySettings[typeName]; @@ -540,14 +539,16 @@ export function defaultFindOptions(type: TypeReference): FindOptions | undefined return undefined; } -export function getAutoComplete(type: TypeReference, findOptions: FindOptions | undefined, showType?: boolean): AutocompleteConfig | null { +export function getAutoComplete(type: TypeReference, findOptions: FindOptions | undefined, ctx: TypeContext, create: boolean, showType?: boolean): AutocompleteConfig | null { if (type.isEmbedded || type.name == IsByAll) return null; var config: AutocompleteConfig | null = null; if (findOptions) - config = new FindOptionsAutocompleteConfig(findOptions); + config = new FindOptionsAutocompleteConfig(findOptions, { + getAutocompleteConstructor: !create ? undefined : (subStr, rows) => getAutocompleteConstructors(type, subStr, ctx, rows.map(a => a.entity!)) as AutocompleteConstructor[] + }); const types = getTypeInfos(type); var delay: number | undefined; @@ -569,7 +570,7 @@ export function getAutoComplete(type: TypeReference, findOptions: FindOptions | types: type.name, subString: subStr, count: 5 - }, signal), false, showType == null ? type.name.contains(",") : showType); + }, signal).then(lites => [...lites, ...(!create ? []: getAutocompleteConstructors(type, subStr, ctx, lites) as AutocompleteConstructor[])]), false, showType == null ? type.name.contains(",") : showType); } if (!config.getItemsDelay) { @@ -794,6 +795,18 @@ export interface ViewOverride { override: (replacer: ViewReplacer) => void; } +export interface AutocompleteConstructor { + type: PseudoType; + onClick: () => Promise; +} + +export function getAutocompleteConstructors(tr: TypeReference, str: string, ctx: TypeContext, foundLites: Lite[]): AutocompleteConstructor[]{ + return getTypeInfos(tr.name).map(ti => { + var es = getSettings(ti); + return es && es.autocompleteConstructor && es.autocompleteConstructor(str, ctx, foundLites); + }).notNull(); +} + export class EntitySettings { typeName: string; @@ -811,6 +824,7 @@ export class EntitySettings { isReadOnly?: boolean; autocomplete?: AutocompleteConfig; autocompleteDelay?: number; + autocompleteConstructor?: (str: string, ctx: TypeContext, foundLites: Lite[]) => AutocompleteConstructor | null; findOptions?: FindOptions; onNavigate?: (entityOrPack: Lite | T | EntityPack, navigateOptions?: NavigateOptions) => Promise; onView?: (entityOrPack: Lite | T | EntityPack, viewOptions?: ViewOptions) => Promise; @@ -825,7 +839,7 @@ export class EntitySettings { this.viewOverrides.push({ override, viewName }); } - constructor(type: Type | string, getViewModule?: (entity: T) => Promise>, options?: EntitySettingsOptions) { + constructor(type: Type | string, getViewModule?: (entity: T) => Promise>, options?: EntitySettingsOptions) { this.typeName = (type as Type).typeName || type as string; this.getViewPromise = getViewModule && (entity => new ViewPromise(getViewModule(entity))); diff --git a/Signum.React/Scripts/Operations.tsx b/Signum.React/Scripts/Operations.tsx index bb4eba3eb2..7a5637f16b 100644 --- a/Signum.React/Scripts/Operations.tsx +++ b/Signum.React/Scripts/Operations.tsx @@ -49,7 +49,7 @@ export function clearOperationSettings() { } export function addSettings(...settings: OperationSettings[]) { - settings.forEach(s => Dic.addOrThrow(operationSettings, s.operationSymbol.key!, s)); + settings.forEach(s => Dic.addOrThrow(operationSettings, s.operationSymbol, s)); } @@ -107,10 +107,10 @@ export function operationInfos(ti: TypeInfo) { export abstract class OperationSettings { text?: () => string; - operationSymbol: OperationSymbol; + operationSymbol: string; - constructor(operationSymbol: OperationSymbol) { - this.operationSymbol = operationSymbol; + constructor(operationSymbol: OperationSymbol | string) { + this.operationSymbol = typeof operationSymbol == "string" ? operationSymbol : operationSymbol.key; } } @@ -124,7 +124,7 @@ export class ConstructorOperationSettings extends OperationSet isVisible?: (coc: ConstructorOperationContext) => boolean; onConstruct?: (coc: ConstructorOperationContext, props?: Partial) => Promise | undefined> | undefined; - constructor(operationSymbol: ConstructSymbol_Simple, options: ConstructorOperationOptions) { + constructor(operationSymbol: ConstructSymbol_Simple | string, options: ConstructorOperationOptions) { super(operationSymbol); Dic.assign(this, options); @@ -170,7 +170,7 @@ export class ContextualOperationSettings extends OperationSett iconColor?: string; order?: number; - constructor(operationSymbol: ConstructSymbol_FromMany, options: ContextualOperationOptions) { + constructor(operationSymbol: ConstructSymbol_FromMany | string, options: ContextualOperationOptions) { super(operationSymbol); Dic.assign(this, options); @@ -345,7 +345,7 @@ export class EntityOperationSettings extends OperationSettings alternatives?: (ctx: EntityOperationContext) => AlternativeOperationSetting[]; keyboardShortcut?: KeyboardShortcut | null; - constructor(operationSymbol: ExecuteSymbol | DeleteSymbol | ConstructSymbol_From, options: EntityOperationOptions) { + constructor(operationSymbol: ExecuteSymbol | DeleteSymbol | ConstructSymbol_From | string, options: EntityOperationOptions) { super(operationSymbol) Dic.assign(this, options); diff --git a/Signum.React/Scripts/Operations/ContextualOperations.tsx b/Signum.React/Scripts/Operations/ContextualOperations.tsx index d47b4d9abe..7403c7837b 100644 --- a/Signum.React/Scripts/Operations/ContextualOperations.tsx +++ b/Signum.React/Scripts/Operations/ContextualOperations.tsx @@ -114,7 +114,7 @@ export function getEntityOperationsContextualItems(ctx: ContextualItemsContext { contexts.forEach(coc => { coc.canExecute = ep.canExecute[coc.operationInfo.key]; - coc.isReadonly = Navigator.isReadOnly(ep); + coc.isReadonly = Navigator.isReadOnly(ep, true); }); return contexts; }); @@ -130,7 +130,7 @@ export function getEntityOperationsContextualItems(ctx: ContextualItemsContext a.isReadonly = true); } diff --git a/Signum.React/Scripts/Operations/EntityOperations.tsx b/Signum.React/Scripts/Operations/EntityOperations.tsx index d6ff541be0..10b60fa238 100644 --- a/Signum.React/Scripts/Operations/EntityOperations.tsx +++ b/Signum.React/Scripts/Operations/EntityOperations.tsx @@ -53,7 +53,7 @@ export function getEntityOperationButtons(ctx: ButtonsContext): Array(valueOrArray: Seq): T[] { export function getQuickLinkWidget(ctx: WidgetContext): React.ReactElement { - return ; + return ; } export function getQuickLinkContextMenus(ctx: ContextualItemsContext): Promise { @@ -142,12 +142,12 @@ export function getQuickLinkContextMenus(ctx: ContextualItemsContext): P } export interface QuickLinkWidgetProps { - ctx: WidgetContext + wc: WidgetContext } export function QuickLinkWidget(p: QuickLinkWidgetProps) { - const entity = p.ctx.pack.entity; + const entity = p.wc.ctx.value; const links = useAPI(undefined, [p], signal => { if (entity.isNew || !getTypeInfo(entity.Type) || !getTypeInfo(entity.Type).entityKind) @@ -156,7 +156,7 @@ export function QuickLinkWidget(p: QuickLinkWidgetProps) { return getQuickLinks({ lite: toLiteFat(entity as Entity), lites: [toLiteFat(entity as Entity)], - widgetContext: p.ctx as WidgetContext + widgetContext: p.wc as WidgetContext }); }); diff --git a/Signum.React/Scripts/Reflection.ts b/Signum.React/Scripts/Reflection.ts index f14df6642b..db67a240ed 100644 --- a/Signum.React/Scripts/Reflection.ts +++ b/Signum.React/Scripts/Reflection.ts @@ -59,10 +59,10 @@ export interface OperationInfo { key: string, niceName: string; operationType: OperationType; - canBeNew: boolean; - canBeModified: boolean; - hasCanExecute: boolean; - hasStates: boolean; + canBeNew?: boolean; + canBeModified?: boolean; + hasCanExecute?: boolean; + hasStates?: boolean; } export enum OperationType { @@ -172,7 +172,7 @@ export function dateToString(val: any, format?: string) { if (val == null) return ""; - var m = moment(val, moment.ISO_8601); + var m = moment(val); return m.format(toMomentFormat(format)); } @@ -1189,6 +1189,9 @@ let missingSymbols: ISymbol[] = []; function getMember(key: string): MemberInfo | undefined { + if (!key.contains(".")) + return undefined; + const type = _types[key.before(".").toLowerCase()]; if (!type) @@ -1199,11 +1202,16 @@ function getMember(key: string): MemberInfo | undefined { return member; } -export function symbolNiceName(symbol: Entity & ISymbol | Lite) { +export function symbolNiceName(symbol: Entity & ISymbol | Lite) : string { if ((symbol as Entity).Type != null) //Don't use isEntity to avoid cycle - return getMember((symbol as Entity & ISymbol).key)!.niceName; - else - return getMember(symbol.toStr!)!.niceName; + { + var m = getMember((symbol as Entity & ISymbol).key); + return m && m.niceName || symbol.toStr!; + } + else { + var m = getMember(symbol.toStr!); + return m && m.niceName || symbol.toStr!; + } } export function getSymbol(type: Type, key: string) { //Unsafe Type! @@ -1370,6 +1378,8 @@ export class PropertyRoute { addMember(memberType: MemberType, memberName: string): PropertyRoute { + var getErrorContext = () => ` (adding ${memberType} ${memberName} to ${this.toString()})`; + if (memberType == "Member") { if (this.propertyRouteType == "Field" || @@ -1384,16 +1394,16 @@ export class PropertyRoute { return PropertyRoute.liteEntity(this); } - const ti = getTypeInfos(ref).single("Ambiguity due to multiple Implementations"); //[undefined] + const ti = getTypeInfos(ref).single("Ambiguity due to multiple Implementations" + getErrorContext()); //[undefined] if (ti) { const m = ti.members[memberName]; if (!m) - throw new Error(`member '${memberName}' not found`); + throw new Error(`member '${memberName}' not found` + getErrorContext()); return PropertyRoute.member(PropertyRoute.root(ti), m); } else if (this.propertyRouteType == "LiteEntity") { - throw Error("Unexpected lite case"); + throw Error("Unexpected lite case" + getErrorContext()); } } @@ -1403,30 +1413,30 @@ export class PropertyRoute { const m = this.findRootType().members[fullMemberName]; if (!m) - throw new Error(`member '${fullMemberName}' not found`) + throw new Error(`member '${fullMemberName}' not found` + getErrorContext()); return PropertyRoute.member(this, m); } if (memberType == "Mixin") { if (this.propertyRouteType != "Root") - throw new Error("invalid mixin at this stage"); + throw new Error("invalid mixin at this stage" + getErrorContext()); return PropertyRoute.mixin(this, memberName); } if (memberType == "Indexer") { if (this.propertyRouteType != "Field") - throw new Error("invalid indexer at this stage"); + throw new Error("invalid indexer at this stage" + getErrorContext()); const tr = this.typeReference(); if (!tr.isCollection) - throw new Error(`${this.propertyPath()} is not a collection`); + throw new Error("${this.propertyPath()} is not a collection" + getErrorContext()); return PropertyRoute.mlistItem(this); } - throw new Error("not implemented"); + throw new Error("not implemented" + getErrorContext()); } static generateAll(type: PseudoType): PropertyRoute[] { diff --git a/Signum.React/Scripts/Retrieve.tsx b/Signum.React/Scripts/Retrieve.tsx index 70239cf1f6..debaf7c375 100644 --- a/Signum.React/Scripts/Retrieve.tsx +++ b/Signum.React/Scripts/Retrieve.tsx @@ -1,4 +1,4 @@ -import * as Navigator from './Navigator'; +import * as Navigator from './Navigator'; import * as React from 'react' import { Entity, Lite, is } from './Signum.Entities'; @@ -44,6 +44,8 @@ export class Retrieve extends React.Component { if (props.lite == null) this.setState({ entity: null }); + else if (props.lite.entity) + this.setState({ entity: props.lite.entity }); else { this.setState({ entity: undefined }); Navigator.API.fetchAndForget(props.lite) diff --git a/Signum.React/Scripts/SearchControl/SearchControl.tsx b/Signum.React/Scripts/SearchControl/SearchControl.tsx index 9151b5d043..1055f84920 100644 --- a/Signum.React/Scripts/SearchControl/SearchControl.tsx +++ b/Signum.React/Scripts/SearchControl/SearchControl.tsx @@ -51,7 +51,7 @@ export interface SearchControlProps extends React.Props { simpleFilterBuilder?: (sfbc: Finder.SimpleFilterBuilderContext) => React.ReactElement | undefined; onNavigated?: (lite: Lite) => void; onDoubleClick?: (e: React.MouseEvent, row: ResultRow) => void; - onSelectionChanged?: (entity: ResultRow[]) => void; + onSelectionChanged?: (rows: ResultRow[]) => void; onFiltersChanged?: (filters: FilterOptionParsed[]) => void; onHeighChanged?: () => void; onSearch?: (fo: FindOptionsParsed, dataChange: boolean) => void; diff --git a/Signum.React/Scripts/SearchControl/SystemTimeEditor.tsx b/Signum.React/Scripts/SearchControl/SystemTimeEditor.tsx index 70f776430e..6336ab43b4 100644 --- a/Signum.React/Scripts/SearchControl/SystemTimeEditor.tsx +++ b/Signum.React/Scripts/SearchControl/SystemTimeEditor.tsx @@ -140,7 +140,7 @@ export default class SystemTimeEditor extends React.Component diff --git a/Signum.React/Scripts/SelectorModal.tsx b/Signum.React/Scripts/SelectorModal.tsx index 5077c7b0b7..ad5dc56d63 100644 --- a/Signum.React/Scripts/SelectorModal.tsx +++ b/Signum.React/Scripts/SelectorModal.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { openModal, IModalProps } from './Modals'; import { SelectorMessage } from './Signum.Entities' -import { TypeInfo } from './Reflection' +import { TypeInfo, EnumType } from './Reflection' import { Modal, BsSize } from './Components'; interface SelectorModalProps extends React.Props, IModalProps { @@ -86,6 +86,17 @@ export default class SelectorModal extends React.Component); } + static chooseEnum(enumType: EnumType): Promise{ + return SelectorModal.chooseElement(enumType.values(), + { + buttonDisplay: a => enumType.niceToString(a), + buttonName: a => a, + title: SelectorMessage._0Selector.niceToString(enumType.niceTypeName()), + message: SelectorMessage.PleaseChooseA0ToContinue.niceToString(enumType.niceTypeName()), + size: "md", + }); + } + static chooseType(options: TypeInfo[]): Promise { return SelectorModal.chooseElement(options, { diff --git a/Signum.React/Scripts/Services.ts b/Signum.React/Scripts/Services.ts index b0ab37ee57..54fc2dbd4d 100644 --- a/Signum.React/Scripts/Services.ts +++ b/Signum.React/Scripts/Services.ts @@ -189,11 +189,20 @@ let a = document.createElement("a"); document.body.appendChild(a); a.style.display = "none"; - export function saveFile(response: Response) { + var fileName: string; const contentDisposition = response.headers.get("Content-Disposition")!; - const fileNamePart = contentDisposition.split(";").filter(a => a.trim().startsWith("filename=")).singleOrNull(); - const fileName = fileNamePart ? fileNamePart.trim().after("filename=").trimStart("\"").trimEnd("\"") : "file.dat"; + const parts = contentDisposition.split(";"); + + const fileNamePartUTF8 = parts.filter(a => a.trim().startsWith("filename*=")).singleOrNull(); + const fileNamePartAscii = parts.filter(a => a.trim().startsWith("filename=")).singleOrNull(); + + if (fileNamePartUTF8) + fileName = decodeURIComponent(fileNamePartUTF8.trim().after("UTF-8''").trimEnd("\"")); + else if (fileNamePartAscii) + fileName = fileNamePartAscii.trim().after("filename=").trimStart("\"").trimEnd("\""); + else + fileName = "file.dat"; response.blob().then(blob => { saveFileBlob(blob, fileName); diff --git a/Signum.React/Scripts/Signum.Entities.t4s b/Signum.React/Scripts/Signum.Entities.t4s index 354eaec83c..7c34782606 100644 --- a/Signum.React/Scripts/Signum.Entities.t4s +++ b/Signum.React/Scripts/Signum.Entities.t4s @@ -170,7 +170,7 @@ export function parseLite(lite: string): Lite { }; } -export function is(a: Lite | T | null | undefined, b: Lite | T | null | undefined, compareTicks = false) { +export function is(a: Lite | T | null | undefined, b: Lite | T | null | undefined, compareTicks = false, assertTypesFound = true) { if (a == undefined && b == undefined) return true; @@ -181,8 +181,12 @@ export function is(a: Lite | T | null | undefined, b: Lite< const aType = (a as T).Type || (a as Lite).EntityType; const bType = (b as T).Type || (b as Lite).EntityType; - if (!aType || !bType) - throw new Error("No Type found"); + if (!aType || !bType) { + if (assertTypesFound) + throw new Error("No Type found"); + else + return false; + } if (aType != bType) return false; diff --git a/Signum.React/Scripts/Signum.Entities.ts b/Signum.React/Scripts/Signum.Entities.ts index 4048c2aac8..7ecf95c66b 100644 --- a/Signum.React/Scripts/Signum.Entities.ts +++ b/Signum.React/Scripts/Signum.Entities.ts @@ -177,7 +177,7 @@ export function parseLite(lite: string): Lite { }; } -export function is(a: Lite | T | null | undefined, b: Lite | T | null | undefined, compareTicks = false) { +export function is(a: Lite | T | null | undefined, b: Lite | T | null | undefined, compareTicks = false, assertTypesFound = true) { if (a == undefined && b == undefined) return true; @@ -188,8 +188,12 @@ export function is(a: Lite | T | null | undefined, b: Lite< const aType = (a as T).Type || (a as Lite).EntityType; const bType = (b as T).Type || (b as Lite).EntityType; - if (!aType || !bType) - throw new Error("No Type found"); + if (!aType || !bType) { + if (assertTypesFound) + throw new Error("No Type found"); + else + return false; + } if (aType != bType) return false; @@ -474,6 +478,8 @@ export module SelectorMessage { export const ChooseAValue = new MessageKey("SelectorMessage", "ChooseAValue"); export const SelectAnElement = new MessageKey("SelectorMessage", "SelectAnElement"); export const PleaseSelectAnElement = new MessageKey("SelectorMessage", "PleaseSelectAnElement"); + export const _0Selector = new MessageKey("SelectorMessage", "_0Selector"); + export const PleaseChooseA0ToContinue = new MessageKey("SelectorMessage", "PleaseChooseA0ToContinue"); } export interface Symbol extends Entity { @@ -504,13 +510,13 @@ export module ValidationMessage { export const _0IsSet = new MessageKey("ValidationMessage", "_0IsSet"); export const _0IsNotA1_G = new MessageKey("ValidationMessage", "_0IsNotA1_G"); export const BeA0_G = new MessageKey("ValidationMessage", "BeA0_G"); - export const Be = new MessageKey("ValidationMessage", "Be"); + export const Be0 = new MessageKey("ValidationMessage", "Be0"); export const BeBetween0And1 = new MessageKey("ValidationMessage", "BeBetween0And1"); export const BeNotNull = new MessageKey("ValidationMessage", "BeNotNull"); export const FileName = new MessageKey("ValidationMessage", "FileName"); export const Have0Decimals = new MessageKey("ValidationMessage", "Have0Decimals"); export const HaveANumberOfElements01 = new MessageKey("ValidationMessage", "HaveANumberOfElements01"); - export const HaveAPrecisionOf = new MessageKey("ValidationMessage", "HaveAPrecisionOf"); + export const HaveAPrecisionOf0 = new MessageKey("ValidationMessage", "HaveAPrecisionOf0"); export const HaveBetween0And1Characters = new MessageKey("ValidationMessage", "HaveBetween0And1Characters"); export const HaveMaximum0Characters = new MessageKey("ValidationMessage", "HaveMaximum0Characters"); export const HaveMinimum0Characters = new MessageKey("ValidationMessage", "HaveMinimum0Characters"); @@ -558,6 +564,7 @@ export module ValidationMessage { export const _AtLeastOneValueIsNeeded = new MessageKey("ValidationMessage", "_AtLeastOneValueIsNeeded"); export const PowerOf = new MessageKey("ValidationMessage", "PowerOf"); export const BeAString = new MessageKey("ValidationMessage", "BeAString"); + export const BeAMultilineString = new MessageKey("ValidationMessage", "BeAMultilineString"); export const IsATimeOfTheDay = new MessageKey("ValidationMessage", "IsATimeOfTheDay"); } diff --git a/Signum.React/Scripts/TypeContext.ts b/Signum.React/Scripts/TypeContext.ts index f8417407e8..d805964790 100644 --- a/Signum.React/Scripts/TypeContext.ts +++ b/Signum.React/Scripts/TypeContext.ts @@ -442,7 +442,7 @@ export interface IHasChanges { export interface EntityFrame { frameComponent: React.Component; entityComponent: React.Component | null | undefined; - pack: EntityPack | undefined; + pack: EntityPack; onReload: (pack?: EntityPack, reloadComponent?: boolean, callback?: () => void) => void; setError: (modelState: ModelState, initialPrefix?: string) => void; revalidate: () => void; diff --git a/Signum.Test/LinqProvider/SingleFirstTest.cs b/Signum.Test/LinqProvider/SingleFirstTest.cs index be7ed09dbd..aba93dd365 100644 --- a/Signum.Test/LinqProvider/SingleFirstTest.cs +++ b/Signum.Test/LinqProvider/SingleFirstTest.cs @@ -27,12 +27,12 @@ public void SelectFirstOrDefault() var bands1 = Database.Query().Select(b => new { b.Name, Member = b.Members.FirstOrDefault().Name }).ToList(); var bands2 = Database.Query().Select(b => new { b.Name, Member = b.Members.FirstEx().Name }).ToList(); - var bands3 = Database.Query().Select(b => new { b.Name, Member = b.Members.SingleOrDefaultEx().Name }).ToList(); + var bands3 = Database.Query().Select(b => new { b.Name, Member = b.Members.SingleOrDefaultEx()!.Name }).ToList(); var bands4 = Database.Query().Select(b => new { b.Name, Member = b.Members.SingleEx().Name }).ToList(); var bands1b = Database.Query().Select(b => new { b.Name, Member = b.Members.FirstOrDefault(a => a.Sex == Sex.Female).Name }).ToList(); var bands2b = Database.Query().Select(b => new { b.Name, Member = b.Members.FirstEx(a => a.Sex == Sex.Female).Name }).ToList(); - var bands3b = Database.Query().Select(b => new { b.Name, Member = b.Members.SingleOrDefaultEx(a => a.Sex == Sex.Female).Name }).ToList(); + var bands3b = Database.Query().Select(b => new { b.Name, Member = b.Members.SingleOrDefaultEx(a => a.Sex == Sex.Female)!.Name }).ToList(); var bands4b = Database.Query().Select(b => new { b.Name, Member = b.Members.SingleEx(a => a.Sex == Sex.Female).Name }).ToList(); } diff --git a/Signum.Test/Signum.Test.csproj b/Signum.Test/Signum.Test.csproj index c64a8721ad..338ab95bcc 100644 --- a/Signum.Test/Signum.Test.csproj +++ b/Signum.Test/Signum.Test.csproj @@ -35,7 +35,7 @@ - + diff --git a/Signum.Utilities/Csv.cs b/Signum.Utilities/Csv.cs index 4cfebad6c5..fb29cce89d 100644 --- a/Signum.Utilities/Csv.cs +++ b/Signum.Utilities/Csv.cs @@ -10,6 +10,7 @@ using System.Reflection; using System.Collections.Concurrent; using System.Collections; +using Signum.Utilities.ExpressionTrees; namespace Signum.Utilities { @@ -21,7 +22,7 @@ public static class Csv public static CultureInfo? DefaultCulture = null; public static string ToCsvFile(this IEnumerable collection, string fileName, Encoding? encoding = null, CultureInfo? culture = null, bool writeHeaders = true, bool autoFlush = false, bool append = false, - Func, CultureInfo, Func>? toStringFactory = null) + Func, CultureInfo, Func>? toStringFactory = null) { using (FileStream fs = append ? new FileStream(fileName, FileMode.Append, FileAccess.Write) : File.Create(fileName)) ToCsv(collection, fs, encoding, culture, writeHeaders, autoFlush, toStringFactory); @@ -30,7 +31,7 @@ public static string ToCsvFile(this IEnumerable collection, string fileNam } public static byte[] ToCsvBytes(this IEnumerable collection, Encoding? encoding = null, CultureInfo? culture = null, bool writeHeaders = true, bool autoFlush = false, - Func, CultureInfo, Func>? toStringFactory = null) + Func, CultureInfo, Func>? toStringFactory = null) { using (MemoryStream ms = new MemoryStream()) { @@ -40,7 +41,7 @@ public static byte[] ToCsvBytes(this IEnumerable collection, Encoding? enc } public static void ToCsv(this IEnumerable collection, Stream stream, Encoding? encoding = null, CultureInfo? culture = null, bool writeHeaders = true, bool autoFlush = false, - Func, CultureInfo, Func>? toStringFactory = null) + Func, CultureInfo, Func>? toStringFactory = null) { var defEncoding = encoding ?? DefaultEncoding; var defCulture = culture ?? DefaultCulture ?? CultureInfo.CurrentCulture; @@ -71,29 +72,48 @@ public static void ToCsv(this IEnumerable collection, Stream stream, Encod } else { - var columns = ColumnInfoCache.Columns; - var members = columns.Select(c => c.MemberEntry).ToList(); - var toString = columns.Select(c => GetToString(defCulture, c, toStringFactory)).ToList(); + var members = CsvMemberCache.Members; + var toString = members.Select(c => GetToString(defCulture, c, toStringFactory)).ToList(); using (StreamWriter sw = new StreamWriter(stream, defEncoding) { AutoFlush = autoFlush }) { if (writeHeaders) - sw.WriteLine(members.ToString(m => HandleSpaces(m.Name), separator)); + sw.WriteLine(members.ToString(m => HandleSpaces(m.MemberInfo.Name), separator)); foreach (var item in collection) { for (int i = 0; i < members.Count; i++) { - var obj = members[i].Getter!(item); + var member = members[i]; + var toStr = toString[i]; + if (!member.IsCollection) + { + if (i != 0) + sw.Write(separator); - var str = EncodeCsv(toString[i](obj), defCulture); + var obj = member.MemberEntry.Getter!(item); - sw.Write(str); - if (i < members.Count - 1) - sw.Write(separator); + var str = EncodeCsv(toStr(obj), defCulture); + + sw.Write(str); + } else - sw.WriteLine(); + { + var list = (IList?)member.MemberEntry.Getter!(item); + + for (int j = 0; j < list!.Count; j++) + { + if (!(i == 0 && j == 0)) + sw.Write(separator); + + var str = EncodeCsv(toStr(list[j]), defCulture); + + sw.Write(str); + } + } } + + sw.WriteLine(); } } } @@ -114,7 +134,7 @@ public static void ToCsv(this IEnumerable collection, Stream stream, Encod return p; } - private static Func GetToString(CultureInfo culture, CsvColumnInfo column, Func, CultureInfo, Func>? toStringFactory) + private static Func GetToString(CultureInfo culture, CsvMemberInfo column, Func, CultureInfo, Func>? toStringFactory) { if (toStringFactory != null) { @@ -164,9 +184,8 @@ static string HandleSpaces(string p) var defCulture = culture ?? DefaultCulture ?? CultureInfo.CurrentCulture; var defOptions = options ?? new CsvReadOptions(); - var columns = ColumnInfoCache.Columns; - var members = columns.Select(c => c.MemberEntry).ToList(); - var parsers = columns.Select(c => GetParser(defCulture, c, defOptions.ParserFactory)).ToList(); + var members = CsvMemberCache.Members; + var parsers = members.Select(m => GetParser(defCulture, m, defOptions.ParserFactory)).ToList(); Regex regex = GetRegex(defCulture, defOptions.RegexTimeout); @@ -256,14 +275,14 @@ public static T ReadLine(string csvLine, CultureInfo? culture = null, CsvRead Match m = regex.Match(csvLine); - var columns = ColumnInfoCache.Columns; + var members = CsvMemberCache.Members; return ReadObject(m, - columns.Select(c => c.MemberEntry).ToList(), - columns.Select(c => GetParser(defCulture, c, defOptions.ParserFactory)).ToList()); + members, + members.Select(c => GetParser(defCulture, c, defOptions.ParserFactory)).ToList()); } - private static Func GetParser(CultureInfo culture, CsvColumnInfo column, Func, CultureInfo, Func>? parserFactory) + private static Func GetParser(CultureInfo culture, CsvMemberInfo column, Func, CultureInfo, Func?>? parserFactory) { if (parserFactory != null) { @@ -273,10 +292,12 @@ public static T ReadLine(string csvLine, CultureInfo? culture = null, CsvRead return result; } - return str => ConvertTo(str, column.MemberInfo.ReturningType(), culture, column.Format); + var type = column.IsCollection ? column.MemberInfo.ReturningType().ElementType()! : column.MemberInfo.ReturningType(); + + return str => ConvertTo(str, type, culture, column.Format); } - static T ReadObject(Match m, List> members, List> parsers) where T : new() + static T ReadObject(Match m, List> members, List> parsers) where T : new() { var vals = m.Groups["val"].Captures; @@ -284,16 +305,37 @@ public static T ReadLine(string csvLine, CultureInfo? culture = null, CsvRead throw new FormatException("Only {0} columns found (instead of {1}) in line: {2}".FormatWith(vals.Count, members.Count, m.Value)); T t = new T(); + for (int i = 0; i < members.Count; i++) { - string? str = null; + var member = members[i]; + var parser = parsers[i]; + string? str = null; try { - str = DecodeCsv(vals[i].Value); + if (!member.IsCollection) + { + str = DecodeCsv(vals[i].Value); - object? val = parsers[i](str); + object? val = parser(str); - members[i].Setter!(t, val); + member.MemberEntry.Setter!(t, val); + } + else + { + var list = (IList)Activator.CreateInstance(member.MemberInfo.ReturningType()); + + for (int j = i; j < vals.Count; j++) + { + str = DecodeCsv(vals[j].Value); + + object? val = parser(str); + + list.Add(val); + } + + member.MemberEntry.Setter!(t, list); + } } catch (Exception e) { @@ -317,10 +359,28 @@ static Regex GetRegex(CultureInfo culture, TimeSpan timeout) new Regex(BaseRegex.Replace('\'', '"').Replace(';', s), RegexOptions.Multiline | RegexOptions.ExplicitCapture, timeout)); } - static class ColumnInfoCache + static class CsvMemberCache { - public static List> Columns = MemberEntryFactory.GenerateList(MemberOptions.Fields | MemberOptions.Properties | MemberOptions.Typed | MemberOptions.Setters | MemberOptions.Getter) - .Select((me, i) => new CsvColumnInfo(i, me, me.MemberInfo.GetCustomAttribute()?.Format)).ToList(); + static CsvMemberCache() + { + var memberEntries = MemberEntryFactory.GenerateList(MemberOptions.Fields | MemberOptions.Properties | MemberOptions.Typed | MemberOptions.Setters | MemberOptions.Getter); + Members = memberEntries.Select((me, i) => + { + var type = me.MemberInfo.ReturningType(); + var isCollection = typeof(IList).IsAssignableFrom(type); + if (isCollection) + { + if (type.IsArray) + throw new InvalidOperationException($"{me.MemberInfo.Name} is an array, use a List instead"); + + if (i != memberEntries.Count - 1) + throw new InvalidOperationException($"{me.MemberInfo.Name} is of {type} but is not the last member"); + } + return new CsvMemberInfo(i, me, me.MemberInfo.GetCustomAttribute()?.Format, isCollection); + }).ToList(); + } + + public static List> Members; } static string DecodeCsv(string s) @@ -361,29 +421,31 @@ static string DecodeCsv(string s) public class CsvReadOptions where T: class { - public Func, CultureInfo, Func>? ParserFactory; + public Func, CultureInfo, Func?>? ParserFactory; public bool AsumeSingleLine = false; public Func? SkipError; public TimeSpan RegexTimeout = Regex.InfiniteMatchTimeout; } - public class CsvColumnInfo + public class CsvMemberInfo { public readonly int Index; public readonly MemberEntry MemberEntry; public readonly string? Format; + public readonly bool IsCollection; public MemberInfo MemberInfo { get { return this.MemberEntry.MemberInfo; } } - internal CsvColumnInfo(int index, MemberEntry memberEntry, string? format) + internal CsvMemberInfo(int index, MemberEntry memberEntry, string? format, bool isCollection) { this.Index = index; this.MemberEntry = memberEntry; this.Format = format; + this.IsCollection = isCollection; } } diff --git a/Signum.Utilities/DataStructures/MemComparer.cs b/Signum.Utilities/DataStructures/MemComparer.cs new file mode 100644 index 0000000000..0553fe3ed5 --- /dev/null +++ b/Signum.Utilities/DataStructures/MemComparer.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Signum.Utilities.DataStructures +{ + public static class MemComparer + { + [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] +#pragma warning disable IDE1006 // Naming Styles + static extern int memcmp(byte[] b1, byte[] b2, long count); +#pragma warning restore IDE1006 // Naming Styles + + public static bool Equals(byte[] b1, byte[] b2) + { + // Validate buffers are the same length. + // This also ensures that the count does not exceed the length of either buffer. + return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0; + } + } +} diff --git a/Signum.Utilities/ExpressionExpanderAttributes.cs b/Signum.Utilities/ExpressionExpanderAttributes.cs index 8d0d56757d..0a325cbf12 100644 --- a/Signum.Utilities/ExpressionExpanderAttributes.cs +++ b/Signum.Utilities/ExpressionExpanderAttributes.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -104,5 +105,32 @@ public static T Expression(Expression> body) { throw new InvalidOperationException("This method is not meant to be called. Missing reference to Signum.MSBuildTask in this assembly?"); } + + + public static void ReplaceExpressionUntyped(MemberInfo methodOrProperty, LambdaExpression newExpression) + { + var attr = methodOrProperty.GetCustomAttribute(); + + if (attr == null) + throw new InvalidOperationException($"The member {methodOrProperty.Name} has not {nameof(ExpressionFieldAttribute)}"); + + var fi = methodOrProperty.DeclaringType.GetField(attr.Name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + + fi.SetValue(null, newExpression); + } + + public static void ReplaceExpression(Expression> methodOrProperty, Expression> newExpression) + { + var body = methodOrProperty.Body; + + if (body is UnaryExpression u && u.NodeType == ExpressionType.Convert) + body = u.Operand; + + var member = body is MemberExpression m ? m.Expression.Type.GetProperty(((PropertyInfo)m.Member).Name) ?? m.Member: + body is MethodCallExpression mc ? mc.Object?.Type.GetMethod(mc.Method.Name, mc.Method.GetParameters().Select(p=> p.ParameterType).ToArray()) ?? mc.Method : + throw new InvalidOperationException($"Unexpected expression of type {body.NodeType}"); + + ReplaceExpressionUntyped(member, newExpression); + } } } diff --git a/Signum.Utilities/Extensions/ArgsExtensions.cs b/Signum.Utilities/Extensions/ArgsExtensions.cs index 135dc7dbf4..9c821f4cd1 100644 --- a/Signum.Utilities/Extensions/ArgsExtensions.cs +++ b/Signum.Utilities/Extensions/ArgsExtensions.cs @@ -4,6 +4,7 @@ using System.Linq; using Signum.Utilities.Reflection; using Signum.Utilities.ExpressionTrees; +using System.Diagnostics.CodeAnalysis; namespace Signum.Utilities { @@ -14,10 +15,10 @@ public static T GetArg(this IEnumerable? args) return args.SmartConvertTo().SingleEx(() => "{0} in the argument list".FormatWith(typeof(T))); ; } + [return: MaybeNull] public static T TryGetArgC(this IEnumerable? args) where T : class { - return args.SmartConvertTo().SingleOrDefaultEx( - () => "There are more than one {0} in the argument list".FormatWith(typeof(T))); + return args.SmartConvertTo().SingleOrDefaultEx(() => "There are more than one {0} in the argument list".FormatWith(typeof(T)))!; } public static T? TryGetArgS(this IEnumerable? args) where T : struct diff --git a/Signum.Utilities/Extensions/EnumExtensions.cs b/Signum.Utilities/Extensions/EnumExtensions.cs index fc5dbced72..75922e5f95 100644 --- a/Signum.Utilities/Extensions/EnumExtensions.cs +++ b/Signum.Utilities/Extensions/EnumExtensions.cs @@ -108,13 +108,13 @@ public static IEnumerable UntypedGetValues(Type type) public static T? GetByCode(string code) where T: struct, Enum { - return (T?)(object)EnumFieldCache.Get(typeof(T)) + return (T?)(object?)EnumFieldCache.Get(typeof(T)) .Where(kvp => kvp.Value.GetCustomAttribute()!.Code == code) .Select(kvp => kvp.Key) .SingleOrDefaultEx(); } - public static string GetCode(string key) + public static string? GetCode(string key) { return (EnumFieldCache.Get(typeof(T)) .Where(kvp => kvp.Key.NiceToString() == key) diff --git a/Signum.Utilities/Extensions/EnumerableExtensions.cs b/Signum.Utilities/Extensions/EnumerableExtensions.cs index 1b25da88ba..f7eb62a881 100644 --- a/Signum.Utilities/Extensions/EnumerableExtensions.cs +++ b/Signum.Utilities/Extensions/EnumerableExtensions.cs @@ -158,6 +158,7 @@ static Exception NewException(bool forEndUser, string message) } [MethodExpander(typeof(UniqueExExpander))] + [return: MaybeNull] public static T SingleOrDefaultEx(this IEnumerable collection, Func predicate) { if (collection == null) @@ -184,11 +185,13 @@ public static T SingleOrDefaultEx(this IEnumerable collection, Func(this IQueryable query, Expression> predicate) { return query.Where(predicate).SingleOrDefaultEx(); } + [return: MaybeNull] public static T SingleOrDefaultEx(this IEnumerable collection) { if (collection == null) @@ -208,6 +211,7 @@ public static T SingleOrDefaultEx(this IEnumerable collection) throw new InvalidOperationException("Sequence contains more than one {0}".FormatWith(typeof(T).TypeName())); } + [return: MaybeNull] public static T SingleOrDefaultEx(this IEnumerable collection, Func errorMoreThanOne, bool forEndUser = false) { if (collection == null) @@ -280,17 +284,20 @@ public static T FirstEx(this IEnumerable collection, Func errorZer } [MethodExpander(typeof(UniqueExExpander))] + [return: MaybeNull] public static T SingleOrManyEx(this IEnumerable collection, Func predicate) { return collection.Where(predicate).FirstEx(); } [MethodExpander(typeof(UniqueExExpander))] + [return: MaybeNull] public static T SingleOrManyEx(this IQueryable query, Expression> predicate) { return query.Where(predicate).FirstEx(); } + [return: MaybeNull] public static T SingleOrManyEx(this IEnumerable collection) { if (collection == null) @@ -310,6 +317,7 @@ public static T SingleOrManyEx(this IEnumerable collection) } } + [return: MaybeNull] public static T SingleOrManyEx(this IEnumerable collection, Func errorZero, bool forEndUser = false) { if (collection == null) @@ -330,6 +338,7 @@ public static T SingleOrManyEx(this IEnumerable collection, Func e } //Throws exception if 0, returns if one, returns default if many + [return: MaybeNull] public static T SingleOrMany(this IEnumerable collection) { if (collection == null) diff --git a/Signum.Utilities/Extensions/StringExtensions.cs b/Signum.Utilities/Extensions/StringExtensions.cs index 21d0e15f57..56e4c8d28b 100644 --- a/Signum.Utilities/Extensions/StringExtensions.cs +++ b/Signum.Utilities/Extensions/StringExtensions.cs @@ -743,6 +743,11 @@ public static string RemoveDiacritics(this string s) if (string.IsNullOrEmpty(s)) return s; + var dr = NaturalLanguageTools.DiacriticsRemover.TryGetC(CultureInfo.CurrentCulture.TwoLetterISOLanguageName); + + if (dr != null) + s = dr.RemoveDiacritics(s); + string normalizedString = s.Normalize(NormalizationForm.FormD); StringBuilder stringBuilder = new StringBuilder(); diff --git a/Signum.Utilities/NaturalLanguage/German.cs b/Signum.Utilities/NaturalLanguage/German.cs index c67ea0eecd..a4eda2a31d 100644 --- a/Signum.Utilities/NaturalLanguage/German.cs +++ b/Signum.Utilities/NaturalLanguage/German.cs @@ -302,4 +302,17 @@ private static string GetUpToHundred(int number) } + public class GermanDiacriticsRemover : IDiacriticsRemover + { + public string RemoveDiacritics(string str) + { + return str + .Replace("ü", "ue") + .Replace("ä", "ae") + .Replace("ö", "oe") + .Replace("Ü", "Ue") + .Replace("Ä", "Ae") + .Replace("Ö", "Oe"); + } + } } diff --git a/Signum.Utilities/NaturalLanguage/NaturalLanguageTools.cs b/Signum.Utilities/NaturalLanguage/NaturalLanguageTools.cs index 22f8d96bf3..cbaf3dcec8 100644 --- a/Signum.Utilities/NaturalLanguage/NaturalLanguageTools.cs +++ b/Signum.Utilities/NaturalLanguage/NaturalLanguageTools.cs @@ -29,6 +29,11 @@ public static class NaturalLanguageTools {"de", new GermanNumberWriter()}, }; + public static Dictionary DiacriticsRemover = new Dictionary + { + {"de", new GermanDiacriticsRemover()}, + }; + public static char? GetGender(string name, CultureInfo? culture = null) { var defCulture = culture ?? CultureInfo.CurrentUICulture; @@ -287,6 +292,11 @@ public interface INumberWriter string ToNumber(decimal number, NumberWriterSettings settings); } + public interface IDiacriticsRemover + { + string RemoveDiacritics(string str); + } + #pragma warning disable CS8618 // Non-nullable field is uninitialized. public class NumberWriterSettings { diff --git a/Signum.Utilities/Reflection/ReflectionTools.cs b/Signum.Utilities/Reflection/ReflectionTools.cs index ad4c832130..8ebb3cd56e 100644 --- a/Signum.Utilities/Reflection/ReflectionTools.cs +++ b/Signum.Utilities/Reflection/ReflectionTools.cs @@ -580,7 +580,7 @@ public static bool TryParse(string? value, Type type, CultureInfo ci, out object result = null; - if (string.IsNullOrEmpty(value)) + if (!value.HasText()) { if (Nullable.GetUnderlyingType(type) == null && type.IsValueType) {