diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Default/Curd/OdbcSelectTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Default/Curd/OdbcSelectTest.cs index a5d26f8c..348a17d7 100644 --- a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Default/Curd/OdbcSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Default/Curd/OdbcSelectTest.cs @@ -849,7 +849,7 @@ FROM [tb_topic22] a", subquery); }); Assert.Equal(@"SELECT a.[Id] as1, a.[Clicks] as2, a.[TypeGuid] as3, a.[Title] as4, a.[CreateTime] as5, isnull((SELECT min(b.[Id]) FROM [tb_topic22] b), 0) as6, isnull((SELECT min(b.[CreateTime]) - FROM [tb_topic22] b), '1970-01-01 00:00:00.000') as7 + FROM [tb_topic22] b), '1970-01-01 00:00:00') as7 FROM [tb_topic22] a", subquery); var subqueryList = select.ToList(a => new { @@ -869,7 +869,7 @@ FROM [tb_topic22] a", subquery); }); Assert.Equal(@"SELECT a.[Id] as1, a.[Clicks] as2, a.[TypeGuid] as3, a.[Title] as4, a.[CreateTime] as5, isnull((SELECT max(b.[Id]) FROM [tb_topic22] b), 0) as6, isnull((SELECT max(b.[CreateTime]) - FROM [tb_topic22] b), '1970-01-01 00:00:00.000') as7 + FROM [tb_topic22] b), '1970-01-01 00:00:00') as7 FROM [tb_topic22] a", subquery); var subqueryList = select.ToList(a => new { diff --git a/FreeSql.Tests/FreeSql.Tests/DataAnnotations/MySqlFluentTest.cs b/FreeSql.Tests/FreeSql.Tests/DataAnnotations/MySqlFluentTest.cs index bf551099..2940864c 100644 --- a/FreeSql.Tests/FreeSql.Tests/DataAnnotations/MySqlFluentTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/DataAnnotations/MySqlFluentTest.cs @@ -1,4 +1,6 @@ -using FreeSql.DataAnnotations; +using FreeSql.DataAnnotations; +using FreeSql.Internal; +using FreeSql.Internal.Model; using MySql.Data.MySqlClient; using System; using System.Linq; @@ -54,6 +56,37 @@ namespace FreeSql.Tests.DataAnnotations }; var tsql1 = g.mysql.Select().WhereDynamic(1).ToSql(); + + using (var fsql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.Sqlite, "Data Source=:memory:") + .UseAutoSyncStructure(true) + .UseMappingPriority(MappingPriorityType.Attribute, MappingPriorityType.Aop, MappingPriorityType.FluentApi) + .Build()) + { + ColumnInfo localFunc1() => fsql.CodeFirst.GetTableByEntity(typeof(ModelAopConfigEntity01)).Columns["CreatedTime"]; + ColumnInfo localFunc2() => fsql.CodeFirst.GetTableByEntity(typeof(ModelAopConfigEntity02)).Columns["CreatedTime"]; + + Assert.Equal(DateTimeKind.Local, localFunc1().Attribute.ServerTime); + Assert.Equal(DateTimeKind.Local, localFunc2().Attribute.ServerTime); + + fsql.CodeFirst.ConfigEntity(a => a.Property(b => b.CreatedTime).ServerTime(DateTimeKind.Utc)); + Assert.Equal(DateTimeKind.Utc, localFunc1().Attribute.ServerTime); + Assert.Equal(DateTimeKind.Utc, localFunc2().Attribute.ServerTime); + + fsql.CodeFirst.ConfigEntity(a => a.Property(b => b.CreatedTime).ServerTime(DateTimeKind.Local)); + Assert.Equal(DateTimeKind.Local, localFunc1().Attribute.ServerTime); + Assert.Equal(DateTimeKind.Local, localFunc2().Attribute.ServerTime); + + fsql.CodeFirst.ConfigEntity(a => a.Property(b => b.CreatedTime).ServerTime(DateTimeKind.Utc)); + Assert.Equal(DateTimeKind.Utc, localFunc1().Attribute.ServerTime); + + fsql.CodeFirst.ConfigEntity(a => a.Property(b => b.CreatedTime).ServerTime(DateTimeKind.Utc)); + Assert.Equal(DateTimeKind.Utc, localFunc2().Attribute.ServerTime); + + fsql.CodeFirst.ConfigEntity(a => a.Property(b => b.CreatedTime).ServerTime(DateTimeKind.Local)); + Assert.Equal(DateTimeKind.Utc, localFunc1().Attribute.ServerTime); + Assert.Equal(DateTimeKind.Utc, localFunc2().Attribute.ServerTime); + } } [System.ComponentModel.DataAnnotations.Schema.Table("xxx")] class ModelAopConfigEntity @@ -62,6 +95,17 @@ namespace FreeSql.Tests.DataAnnotations [Column(IsPrimary = false)] public int pkid { get; set; } } + class ModelAopConfigEntityBase + { + [Column(CanUpdate = false, ServerTime = DateTimeKind.Local)] + public virtual DateTime CreatedTime { get; set; } + } + class ModelAopConfigEntity01 : ModelAopConfigEntityBase + { + } + class ModelAopConfigEntity02 : ModelAopConfigEntityBase + { + } [Fact] public void Fluent() diff --git a/FreeSql.Tests/FreeSql.Tests/DataAnnotations/SqlServerFluentTest.cs b/FreeSql.Tests/FreeSql.Tests/DataAnnotations/SqlServerFluentTest.cs index 0ee5d8d8..360c527d 100644 --- a/FreeSql.Tests/FreeSql.Tests/DataAnnotations/SqlServerFluentTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/DataAnnotations/SqlServerFluentTest.cs @@ -1,4 +1,4 @@ -using FreeSql.DataAnnotations; +using FreeSql.DataAnnotations; using FreeSql.Tests.DataContext.SqlServer; using System; using System.Data.SqlClient; @@ -50,7 +50,7 @@ namespace FreeSql.Tests.DataAnnotations { a.Name("xxdkdkdk1222"); a.Property("Id").Name("Id22dd").IsIdentity(true); - a.Property("Name").DbType("varchar(101)").IsNullable(true); + a.Property("Name").Name("Name").DbType("varchar(101)").IsNullable(true); }) .ConfigEntity(a => diff --git a/FreeSql/DataAnnotations/TableFluent.cs b/FreeSql/DataAnnotations/TableFluent.cs index 5ae94172..6d2666e1 100644 --- a/FreeSql/DataAnnotations/TableFluent.cs +++ b/FreeSql/DataAnnotations/TableFluent.cs @@ -49,7 +49,7 @@ namespace FreeSql.DataAnnotations public ColumnFluent Property(string proto) { if (_properties.TryGetValue(proto, out var tryProto) == false) throw new KeyNotFoundException(CoreStrings.NotFound_PropertyName(proto)); - var col = _table._columns.GetOrAdd(tryProto.Name, name => new ColumnAttribute { Name = proto }); + var col = _table._columns.GetOrAdd(tryProto.Name, name => new ColumnAttribute { }); return new ColumnFluent(col, tryProto, _entityType); } @@ -136,7 +136,7 @@ namespace FreeSql.DataAnnotations public ColumnFluent Property(string proto) { if (_properties.TryGetValue(proto, out var tryProto) == false) throw new KeyNotFoundException(CoreStrings.NotFound_PropertyName(proto)); - var col = _table._columns.GetOrAdd(tryProto.Name, name => new ColumnAttribute { Name = proto }); + var col = _table._columns.GetOrAdd(tryProto.Name, name => new ColumnAttribute { }); return new ColumnFluent(col, tryProto, typeof(T)); } diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 2db205b7..7f90becd 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -3724,192 +3724,205 @@ 状态数据,可与 CommandAfter 共享 - - - 发生的错误 - - - - - 执行SQL命令,返回的结果 - - - - - 耗时(单位:Ticks) - - - - - 耗时(单位:毫秒) - - - - - 标识符,可将 TraceBeforeEventArgs 与 TraceAfterEventArgs 进行匹配 - - - - - 状态数据,可与 TraceAfter 共享 - - - - - 备注 - - - - - 发生的错误 - - - - - 耗时(单位:Ticks) - - - - - 耗时(单位:毫秒) - - - - - 【开发环境必备】自动同步实体结构到数据库,程序运行中检查实体表是否存在,然后创建或修改 - - - - - 转小写同步结构,适用 PostgreSQL - - - - - 转大写同步结构,适用 Oracle/达梦/人大金仓 - - - - - 将数据库的主键、自增、索引设置导入,适用 DbFirst 模式,无须在实体类型上设置 [Column(IsPrimary)] 或者 ConfigEntity。此功能目前可用于 mysql/sqlserver/postgresql/oracle。 - 本功能会影响 IFreeSql 首次访问的速度。 - 若使用 CodeFirst 创建索引后,又直接在数据库上建了索引,若无本功能下一次 CodeFirst 迁移时数据库上创建的索引将被删除 - - - - - 不使用命令参数化执行,针对 Insert/Update - - - - - 是否生成命令参数化执行,针对 lambda 表达式解析 - 注意:常量不会参数化,变量才会做参数化 - var id = 100; - fsql.Select<T>().Where(a => a.id == id) 会参数化 - fsql.Select<T>().Where(a => a.id == 100) 不会参数化 - - - - - 延时加载导航属性对象,导航属性需要声明 virtual - - - - - 将实体类型与数据库对比,返回DDL语句 - - + - + - 将实体类型集合与数据库对比,返回DDL语句 + 获取c#类型,int、long - 实体类型 + - + - 将实体类型与数据库对比,返回DDL语句(指定表名) + 获取c#类型对象 - 实体类型 - 指定表名对比 + - + - 同步实体类型到数据库 - 注意:生产环境中谨慎使用 + 获取ado.net读取方法, GetBoolean、GetInt64 - - - - - 同步实体类型集合到数据库 - 注意:生产环境中谨慎使用 - - - - - - 同步实体类型到数据库(指定表名) - 注意:生产环境中谨慎使用 - - 实体类型 - 指定表名对比 - 强制同步结构,无视缓存每次都同步 - - - - 根据 System.Type 获取数据库信息 - - + - + - FreeSql FluentApi 配置实体,方法名与特性相同 + 序列化 - - + - + - FreeSql FluentApi 配置实体,方法名与特性相同 + 反序列化 - - + - + - 获取 FreeSql FluentApi 配置实体的元数据 - - - 未使用ConfigEntity配置时,返回null - - - - 获取实体类核心配置 - - - - - - - 获取所有数据库 - - - - - - 获取指定数据库的表信息,包括表、列详情、主键、唯一键、索引、外键、备注 + 获取数据库枚举类型,适用 PostgreSQL + + + 临时 LambdaExpression.Parameter + + + + + 如果实体类有自增属性,分成两个 List,有值的Item1 merge,无值的Item2 insert + + + + + + + AsType, Ctor, ClearData 三处地方需要重新加载 + + + + + AsType, Ctor, ClearData 三处地方需要重新加载 + + + + + 动态读取 DescriptionAttribute 注释文本 + + + + + + + 通过属性的注释文本,通过 xml 读取 + + + Dict:key=属性名,value=注释 + + + + 更新实体的元数据 + + + + + 执行更新的 SQL + + + + + 执行更新命令的参数 + + + + + 执行更新命令影响的行 + + + + + 更新的实体数量 + + + + + 更新的实体 + + + + + 映射优先级,默认: Attribute > FluentApi > Aop + + + + + 实体特性 + [Table(Name = "tabname")] + [Column(Name = "table_id")] + + + + + 流式接口 + fsql.CodeFirst.ConfigEntity(a => a.Name("tabname")) + fsql.CodeFirst.ConfigEntity(a => a.Property(b => b.Id).Name("table_id")) + + + + + AOP 特性 https://github.com/dotnetcore/FreeSql/wiki/AOP + fsql.Aop.ConfigEntity += (_, e) => e.ModifyResult.Name = "public.tabname"; + fsql.Aop.ConfigEntityProperty += (_, e) => e.ModifyResult.Name = "table_id"; + + + + + 不进行任何处理 + + + + + 将帕斯卡命名字符串转换为下划线分隔字符串 + + BigApple -> Big_Apple + + + + + 将帕斯卡命名字符串转换为下划线分隔字符串,且转换为全大写 + + BigApple -> BIG_APPLE + + + + + 将帕斯卡命名字符串转换为下划线分隔字符串,且转换为全小写 + + BigApple -> big_apple + + + + + 将字符串转换为大写 + + BigApple -> BIGAPPLE + + + + + 将字符串转换为小写 + + BigApple -> bigapple + + + + + 不进行任何处理 + + + + + 将帕斯卡命名字符串转换为下划线分隔字符串 + + BigApple -> Big_Apple + + + + + 将帕斯卡命名字符串转换为下划线分隔字符串,且转换为全大写 + + BigApple -> BIG_APPLE + + + 获取指定单表信息,包括列详情、主键、唯一键、索引、备注 diff --git a/FreeSql/Internal/CommonUtils.cs b/FreeSql/Internal/CommonUtils.cs index 87147ac3..d72cd338 100644 --- a/FreeSql/Internal/CommonUtils.cs +++ b/FreeSql/Internal/CommonUtils.cs @@ -196,27 +196,38 @@ namespace FreeSql.Internal } break; case MappingPriorityType.FluentApi: - if (dicConfigEntity.TryGetValue(type, out var trytb) && trytb._columns.TryGetValue(proto.Name, out var trycol)) + var baseTypes = new List(); + var baseType = type; + while (baseType != typeof(object) && baseType != null) { - if (!string.IsNullOrEmpty(trycol.Name)) attr.Name = trycol.Name; - if (!string.IsNullOrEmpty(trycol.OldName)) attr.OldName = trycol.OldName; - if (!string.IsNullOrEmpty(trycol.DbType)) attr.DbType = trycol.DbType; - if (trycol._IsPrimary != null) attr._IsPrimary = trycol.IsPrimary; - if (trycol._IsIdentity != null) attr._IsIdentity = trycol.IsIdentity; - if (trycol._IsNullable != null) attr._IsNullable = trycol.IsNullable; - if (trycol._IsIgnore != null) attr._IsIgnore = trycol.IsIgnore; - if (trycol._IsVersion != null) attr._IsVersion = trycol.IsVersion; - if (trycol.MapType != null) attr.MapType = trycol.MapType; - if (trycol._Position != null) attr._Position = trycol.Position; - if (trycol._CanInsert != null) attr._CanInsert = trycol.CanInsert; - if (trycol._CanUpdate != null) attr._CanUpdate = trycol.CanUpdate; - if (trycol.ServerTime != DateTimeKind.Unspecified) attr.ServerTime = trycol.ServerTime; - if (trycol._StringLength != null) attr.StringLength = trycol.StringLength; - if (!string.IsNullOrEmpty(trycol.InsertValueSql)) attr.InsertValueSql = trycol.InsertValueSql; - if (trycol._Precision != null) attr.Precision = trycol.Precision; - if (trycol._Scale != null) attr.Scale = trycol.Scale; - if (!string.IsNullOrEmpty(trycol.RewriteSql)) attr.RewriteSql = trycol.RewriteSql; - if (!string.IsNullOrEmpty(trycol.RereadSql)) attr.RereadSql = trycol.RereadSql; + baseTypes.Add(baseType); + baseType = baseType.BaseType; + } + for (var a = baseTypes.Count - 1; a >= 0; a--) + { + var entityType = baseTypes[a]; + if (dicConfigEntity.TryGetValue(entityType, out var trytb) && trytb._columns.TryGetValue(proto.Name, out var trycol)) + { + if (!string.IsNullOrEmpty(trycol.Name)) attr.Name = trycol.Name; + if (!string.IsNullOrEmpty(trycol.OldName)) attr.OldName = trycol.OldName; + if (!string.IsNullOrEmpty(trycol.DbType)) attr.DbType = trycol.DbType; + if (trycol._IsPrimary != null) attr._IsPrimary = trycol.IsPrimary; + if (trycol._IsIdentity != null) attr._IsIdentity = trycol.IsIdentity; + if (trycol._IsNullable != null) attr._IsNullable = trycol.IsNullable; + if (trycol._IsIgnore != null) attr._IsIgnore = trycol.IsIgnore; + if (trycol._IsVersion != null) attr._IsVersion = trycol.IsVersion; + if (trycol.MapType != null) attr.MapType = trycol.MapType; + if (trycol._Position != null) attr._Position = trycol.Position; + if (trycol._CanInsert != null) attr._CanInsert = trycol.CanInsert; + if (trycol._CanUpdate != null) attr._CanUpdate = trycol.CanUpdate; + if (trycol.ServerTime != DateTimeKind.Unspecified) attr.ServerTime = trycol.ServerTime; + if (trycol._StringLength != null) attr.StringLength = trycol.StringLength; + if (!string.IsNullOrEmpty(trycol.InsertValueSql)) attr.InsertValueSql = trycol.InsertValueSql; + if (trycol._Precision != null) attr.Precision = trycol.Precision; + if (trycol._Scale != null) attr.Scale = trycol.Scale; + if (!string.IsNullOrEmpty(trycol.RewriteSql)) attr.RewriteSql = trycol.RewriteSql; + if (!string.IsNullOrEmpty(trycol.RereadSql)) attr.RereadSql = trycol.RereadSql; + } } break; case MappingPriorityType.Attribute: diff --git a/FreeSql/Internal/UtilsExpressionTree.cs b/FreeSql/Internal/UtilsExpressionTree.cs index ef597d07..a6e0b71d 100644 --- a/FreeSql/Internal/UtilsExpressionTree.cs +++ b/FreeSql/Internal/UtilsExpressionTree.cs @@ -28,6 +28,9 @@ namespace FreeSql.Internal ) return; var tbc = _cacheGetTableByEntity.GetOrAdd(common._orm.Ado.DataType, k1 => new ConcurrentDictionary()); //区分数据库类型缓存 if (tbc.TryRemove(entity, out var trytb) && trytb?.TypeLazy != null) tbc.TryRemove(trytb.TypeLazy, out var trylz); + var reltypes = tbc.Keys.Where(a => entity.IsAssignableFrom(a)).ToArray(); + foreach (var reltype in reltypes) + if (tbc.TryRemove(reltype, out trytb) && trytb?.TypeLazy != null) tbc.TryRemove(trytb.TypeLazy, out var trylz); } internal static TableInfo GetTableByEntity(Type entity, CommonUtils common) {