SqlSugarStartupExtensions.cs 18 KB

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