SqlSugarStartupExtensions.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. using System.Collections;
  2. using System.ComponentModel;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.Linq.Dynamic.Core;
  5. using System.Linq.Expressions;
  6. using System.Reflection;
  7. using Hotline.FlowEngine.Workflows;
  8. using Hotline.Orders;
  9. using Hotline.SeedData.Codes;
  10. using Hotline.Users;
  11. using Microsoft.Extensions.Configuration;
  12. using Microsoft.Extensions.DependencyInjection;
  13. using Serilog;
  14. using SqlSugar;
  15. using XF.Domain.Entities;
  16. using XF.Domain.Extensions;
  17. using XF.Domain.Options;
  18. using XF.Domain.Repository;
  19. using XF.Utility.SequentialId;
  20. namespace Hotline.Repository.SqlSugar.Extensions
  21. {
  22. public static class SqlSugarStartupExtensions
  23. {
  24. public static void AddSqlSugar(this IServiceCollection services, IConfiguration configuration, string dbName = "Hotline")
  25. {
  26. //多租户 new SqlSugarScope(List<ConnectionConfig>,db=>{});
  27. SqlSugarScope sqlSugar = new SqlSugarScope(new ConnectionConfig()
  28. {
  29. DbType = DbType.PostgreSQL,
  30. ConnectionString = configuration.GetConnectionString(dbName),
  31. IsAutoCloseConnection = true,
  32. ConfigureExternalServices = new ConfigureExternalServices
  33. {
  34. EntityService = (property, column) =>
  35. {
  36. var attributes = property.GetCustomAttributes(true); //get all attributes
  37. //if (attributes.Any(it => it is KeyAttribute))// by attribute set primarykey
  38. //{
  39. // column.IsPrimarykey = true; //有哪些特性可以看 1.2 特性明细
  40. //}
  41. ////可以写多个,这边可以断点调试
  42. //// if (attributes.Any(it => it is NotMappedAttribute))
  43. ////{
  44. //// column.IsIgnore= true;
  45. ////}
  46. //if (attributes.Any(it => it is DbNullableAttribute))
  47. //{
  48. // column.IsNullable = true;
  49. //}
  50. //if (attributes.Any(it => it is DbJsonAttribute))
  51. //{
  52. // column.DataType = "varchar(3000)";
  53. // column.IsJson = true;
  54. //}
  55. //if (attributes.Any(it => it is DbLengthAttribute))
  56. //{
  57. // column.Length = (attributes.First(d => d is DbLengthAttribute) as DbLengthAttribute)?.MaxLength ?? 255;
  58. //}
  59. //if (attributes.Any(it => it is DbIgnoreAttribute))
  60. //{
  61. // column.IsIgnore = true;
  62. //}
  63. //if (property.PropertyType.IsGenericType &&
  64. // property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
  65. //{
  66. // column.IsNullable = true;
  67. //}
  68. if (column.PropertyName.ToLower() == "id" ||
  69. attributes.Any(it => it is KeyAttribute)) //是id的设为主键
  70. {
  71. column.IsPrimarykey = true;
  72. column.Length = 36;
  73. }
  74. //if (!column.DbColumnName.Contains("_"))
  75. // column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName);//ToUnderLine驼峰转下划线
  76. //column.ColumnDescription = (attributes.FirstOrDefault(d => d is DescriptionAttribute) as DescriptionAttribute)?.Description ?? string.Empty;
  77. //统一设置 nullable等于isnullable=true
  78. if (!column.IsPrimarykey && new NullabilityInfoContext().Create(property).WriteState is NullabilityState.Nullable)
  79. {
  80. column.IsNullable = true;
  81. }
  82. },
  83. EntityNameService = (type, entity) =>
  84. {
  85. var attributes = type.GetCustomAttributes(true);
  86. //if (attributes.Any(it => it is TableAttribute))
  87. //{
  88. // entity.DbTableName = (attributes.First(it => it is TableAttribute) as TableAttribute).UserName;
  89. //}
  90. entity.DbTableName = entity.DbTableName.ToSnakeCase();
  91. //if (!entity.DbTableName.Contains("_"))
  92. // entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);//ToUnderLine驼峰转下划线方法
  93. if (attributes.Any(d => d is DescriptionAttribute))
  94. {
  95. entity.TableDescription =
  96. (attributes.First(d => d is DescriptionAttribute) as DescriptionAttribute)
  97. .Description;
  98. }
  99. }
  100. },
  101. MoreSettings = new ConnMoreSettings
  102. {
  103. PgSqlIsAutoToLower = false,//增删查改支持驼峰表
  104. PgSqlIsAutoToLowerCodeFirst = false, // 建表建驼峰表。5.1.3.30
  105. }
  106. },
  107. SetDbAop
  108. );
  109. ISugarUnitOfWork<HotlineDbContext> context = new SugarUnitOfWork<HotlineDbContext>(sqlSugar);
  110. services.AddSingleton(context);
  111. InitDatabase(context, configuration);
  112. }
  113. private static void InitDatabase(ISugarUnitOfWork<HotlineDbContext> context, IConfiguration configuration)
  114. {
  115. var dbOptions = configuration.GetSection("DatabaseConfiguration").Get<DatabaseOptions>() ?? new DatabaseOptions();
  116. if (dbOptions.ApplyDbMigrations)
  117. {
  118. context.Db.DbMaintenance.CreateDatabase();
  119. var types = typeof(User).Assembly.GetTypes()
  120. .Where(d => d.GetInterfaces().Any(x => x == typeof(ITable) && !d.IsAbstract))
  121. .Distinct()
  122. .ToArray();
  123. context.Db.CodeFirst.InitTables(types);//根据types创建表
  124. }
  125. if (dbOptions.ApplySeed)
  126. {
  127. var allTypes = AppDomain.CurrentDomain.GetAssemblies()
  128. .SelectMany(d => d.GetTypes());
  129. var seedDataTypes = allTypes.Where(d => !d.IsInterface && !d.IsAbstract && d.IsClass
  130. && d.HasImplementedOf(typeof(ISeedData<>)));
  131. foreach (var seedType in seedDataTypes)
  132. {
  133. var instance = Activator.CreateInstance(seedType);
  134. var hasDataMethod = seedType.GetMethod("HasData");
  135. var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
  136. if (seedData == null) continue;
  137. var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
  138. var entityInfo = context.Db.EntityMaintenance.GetEntityInfo(entityType);
  139. if (entityInfo.Columns.Any(d => d.IsPrimarykey))
  140. {
  141. var storage = context.Db.StorageableByObject(seedData.ToList()).ToStorage();
  142. storage.AsInsertable.ExecuteCommand();
  143. }
  144. else
  145. {
  146. // 无主键则只进行插入
  147. if (!context.Db.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
  148. context.Db.InsertableByObject(seedData.ToList()).ExecuteCommand();
  149. }
  150. }
  151. ////持久化需要持久化的runtime常量表
  152. //var constTypes = allTypes.Where(d=> !d.IsInterface && !d.IsAbstract && d.IsClass
  153. // && d.HasImplementedOf(typeof(IConstTable<>)));
  154. //foreach (var constType in constTypes)
  155. //{
  156. // var instance = Activator.CreateInstance(constType);
  157. // var hasDataMethod = constType.GetMethod("GetData");
  158. // var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
  159. // if (seedData == null) continue;
  160. // var entityType = constType.GetInterfaces().First().GetGenericArguments().First();
  161. // var entityInfo = context.Db.EntityMaintenance.GetEntityInfo(entityType);
  162. // if (entityInfo.Columns.Any(d => d.IsPrimarykey))
  163. // {
  164. // var storage = context.Db.StorageableByObject(seedData.ToList()).ToStorage();
  165. // storage.AsInsertable.ExecuteCommand();
  166. // }
  167. // else
  168. // {
  169. // // 无主键则只进行插入
  170. // if (!context.Db.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
  171. // context.Db.InsertableByObject(seedData.ToList()).ExecuteCommand();
  172. // }
  173. //}
  174. }
  175. }
  176. #region private
  177. private static void SetDbAop(SqlSugarClient db)
  178. {
  179. /***写AOP等方法***/
  180. db.Aop.OnLogExecuting = (sql, pars) =>
  181. {
  182. Log.Information("Sql: {0}", sql);
  183. Log.Information("SqlParameters: {0}", string.Join(',', pars.Select(d => d.Value)));
  184. };
  185. db.Aop.OnError = (exp) =>//SQL报错
  186. {
  187. //exp.sql 这样可以拿到错误SQL,性能无影响拿到ORM带参数使用的SQL
  188. Log.Error("SqlError: {0}", exp.Sql);
  189. //5.0.8.2 获取无参数化 SQL 对性能有影响,特别大的SQL参数多的,调试使用
  190. //UtilMethods.GetSqlString(DbType.SqlServer,exp.sql,exp.parameters)
  191. };
  192. //db.Aop.OnExecutingChangeSql = (sql, pars) => //可以修改SQL和参数的值
  193. //{
  194. // //sql=newsql
  195. // //foreach (var p in pars) //修改
  196. // //{
  197. // //}
  198. // return new KeyValuePair<string, SugarParameter[]>(sql, pars);
  199. //};
  200. db.Aop.OnLogExecuted = (sql, p) =>
  201. {
  202. //执行时间超过1秒
  203. if (db.Ado.SqlExecutionTime.TotalSeconds > 1)
  204. {
  205. //代码CS文件名
  206. var fileName = db.Ado.SqlStackTrace.FirstFileName;
  207. //代码行数
  208. var fileLine = db.Ado.SqlStackTrace.FirstLine;
  209. //方法名
  210. var FirstMethodName = db.Ado.SqlStackTrace.FirstMethodName;
  211. //db.Ado.SqlStackTrace.MyStackTraceList[1].xxx 获取上层方法的信息
  212. Log.Warning("slow query ==> fileName: {fileName}, fileLine: {fileLine}, FirstMethodName: {FirstMethodName}",
  213. fileName, fileLine, FirstMethodName);
  214. Log.Warning(UtilMethods.GetNativeSql(sql, p));
  215. Log.Warning("slow query totalSeconds: {sec}", db.Ado.SqlExecutionTime.TotalSeconds);
  216. }
  217. //相当于EF的 PrintToMiniProfiler
  218. };
  219. db.Aop.DataExecuting = (oldValue, entityInfo) =>
  220. {
  221. //inset生效
  222. if (entityInfo.PropertyName == "CreationTime" && entityInfo.OperationType == DataFilterType.InsertByObject)
  223. {
  224. if (oldValue is DateTime createTime)
  225. {
  226. if (createTime == DateTime.MinValue)
  227. {
  228. entityInfo.SetValue(DateTime.Now);//修改CreateTime字段
  229. //entityInfo有字段所有参数
  230. }
  231. }
  232. }
  233. //update生效
  234. else if (entityInfo.PropertyName == "LastModificationTime" && entityInfo.OperationType == DataFilterType.UpdateByObject)
  235. {
  236. entityInfo.SetValue(DateTime.Now);//修改UpdateTime字段
  237. }
  238. //根据当前列修改另一列 可以么写
  239. //if(当前列逻辑==XXX)
  240. //var properyDate = entityInfo.EntityValue.GetType().GetProperty("Date");
  241. //if(properyDate!=null)
  242. //properyDate.SetValue(entityInfo.EntityValue,1);
  243. else if (entityInfo.EntityColumnInfo.IsPrimarykey
  244. && entityInfo.EntityColumnInfo.PropertyName.ToLower() == "id"
  245. && entityInfo.OperationType == DataFilterType.InsertByObject
  246. && oldValue is null) //通过主键保证只进一次事件
  247. {
  248. //var propertyId = entityInfo.EntityValue.GetType().GetProperty("Id");
  249. //if (propertyId is not null)
  250. //{
  251. // var idValue = propertyId.GetValue(entityInfo.EntityValue);
  252. // if (idValue is null)
  253. // //这样每条记录就只执行一次
  254. // entityInfo.SetValue(SequentialStringGenerator.Create());
  255. //}
  256. entityInfo.SetValue(SequentialStringGenerator.Create());
  257. }
  258. };
  259. SetDeletedEntityFilter(db);
  260. }
  261. private static void SetDeletedEntityFilter(SqlSugarClient db)
  262. {
  263. var cacheKey = $"DbFilter:{db.CurrentConnectionConfig.ConfigId}:IsDeleted";
  264. var tableFilterItemList = db.DataCache.Get<List<TableFilterItem<object>>>(cacheKey);
  265. if (tableFilterItemList == null)
  266. {
  267. // 获取基类实体数据表
  268. var entityTypes = AppDomain.CurrentDomain.GetAssemblies()
  269. .SelectMany(d => d.GetTypes())
  270. .Where(d => !d.IsInterface
  271. && !d.IsAbstract
  272. && d.IsClass
  273. && d.GetInterfaces().Any(x => x == typeof(ISoftDelete)));
  274. if (!entityTypes.Any()) return;
  275. var tableFilterItems = new List<TableFilterItem<object>>();
  276. foreach (var entityType in entityTypes)
  277. {
  278. if (entityType.GetProperty("IsDeleted") is null) continue;
  279. //// 排除非当前数据库实体
  280. //var tAtt = entityType.GetCustomAttribute<TenantAttribute>();
  281. //if ((tAtt != null && (string)db.CurrentConnectionConfig.ConfigId != tAtt.configId.ToString()) ||
  282. // (tAtt == null && (string)db.CurrentConnectionConfig.ConfigId != SqlSugarConst.ConfigId))
  283. // continue;
  284. var lambda = DynamicExpressionParser.ParseLambda(new[] {
  285. Expression.Parameter(entityType, "d") },
  286. typeof(bool),
  287. $"{nameof(SoftDeleteEntity.IsDeleted)} == @0", false);
  288. var tableFilterItem = new TableFilterItem<object>(entityType, lambda);
  289. tableFilterItems.Add(tableFilterItem);
  290. db.QueryFilter.Add(tableFilterItem);
  291. }
  292. db.DataCache.Add(cacheKey, tableFilterItems);
  293. }
  294. else
  295. {
  296. tableFilterItemList.ForEach(u =>
  297. {
  298. db.QueryFilter.Add(u);
  299. });
  300. }
  301. }
  302. #endregion
  303. }
  304. }