From acac7ec995765ae77de005f992fea2caca70f7f3 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Wed, 24 Jan 2024 12:17:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7net8=20=E5=87=86=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Program.cs | 126 -------- IRaCIS.Core.API/Progranm.cs | 306 ++++++++++++++++++ IRaCIS.Core.API/Startup.cs | 262 --------------- .../IRaCIS.Core.Application.xml | 26 ++ .../Repository/DynamicRelationalExtensions.cs | 120 +++++++ .../Repository/IRaCISContextExtension.cs | 64 +++- 6 files changed, 499 insertions(+), 405 deletions(-) delete mode 100644 IRaCIS.Core.API/Program.cs create mode 100644 IRaCIS.Core.API/Progranm.cs delete mode 100644 IRaCIS.Core.API/Startup.cs create mode 100644 IRaCIS.Core.Infra.EFCore/Repository/DynamicRelationalExtensions.cs diff --git a/IRaCIS.Core.API/Program.cs b/IRaCIS.Core.API/Program.cs deleted file mode 100644 index 38f1a2ee5..000000000 --- a/IRaCIS.Core.API/Program.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using Autofac.Extensions.DependencyInjection; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Configuration; -using Serilog; -using MediatR; -using IRaCIS.Core.Application.MediatR.Handlers; -using System.Threading.Tasks; -using MassTransit; -using MassTransit.NewIdProviders; -using System.IO; -using IRaCIS.Core.Domain.Share; -using IRaCIS.Core.Infra.EFCore; -using IRaCIS.Core.Application.Helper; -using System.Runtime.InteropServices; - -namespace IRaCIS.Core.API -{ - public class Program - { - public readonly string environment; - public static async Task Main(string[] args) - { - try - { - //以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数) - var config = new ConfigurationBuilder() - .AddEnvironmentVariables() - .Build(); - - var enviromentName = config["ASPNETCORE_ENVIRONMENT"]; - - if (string.IsNullOrWhiteSpace(enviromentName)) - { - - var index = Array.IndexOf(args, "--env"); - enviromentName = index > -1 - ? args[index + 1] - : "Development"; - } - - //Dicom 浏览 - //ImageManager.SetImplementation(WinFormsImageManager.Instance); - - var host = CreateHostBuilder(args) - .UseEnvironment(enviromentName) //命令行传入环境 - .ConfigureAppConfiguration((hostContext, config) => - { - - //Console.WriteLine(hostContext.HostingEnvironment.EnvironmentName); - config.AddJsonFile("appsettings.json", false, true) - .AddJsonFile($"appsettings.{enviromentName}.json", false, true); - }) - .Build(); - - - //// Serilog - SerilogExtension.AddSerilogSetup(enviromentName, host.Services); - - Log.Logger.Warning($"当前环境:{enviromentName}"); - - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Log.Logger.Warning($"当前部署平台环境:windows"); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - Log.Logger.Warning($"当前部署平台环境:linux"); - } - else - { - Log.Logger.Warning($"当前部署平台环境:OSX or FreeBSD"); - } - - NewId.SetProcessIdProvider(new CurrentProcessIdProvider()); - - - host.Run(); - - - - } - catch (Exception e) - { - - Log.Logger.Error(e.InnerException is null ? e.Message + e.StackTrace : e.InnerException?.Message + e.InnerException?.StackTrace); - } - - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .UseWindowsService() - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.ConfigureKestrel((context, options) => - { - //设置应用服务器Kestrel请求体最大为1GB // if don't set default value is: 30 MB - options.Limits.MaxRequestBodySize = long.MaxValue; - options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(30); - options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(20); - - }); - //webBuilder.UseSerilog();//在宿主机启动的时候配置serilog,与微软ILogger进行整合 - webBuilder.UseStartup(); - }).UseSerilog() - .UseServiceProviderFactory(new AutofacServiceProviderFactory()); - - - private static async Task InitCache(IHost host) - { - var _repository = host.Services.GetService(typeof(IRepository)) as IRepository; - - //初始化 国际化数据,并且监测国际化文件变更 - //await InternationalizationHelper.InitInternationlizationDataAndWatchJsonFileAsync(_repository); - - } - - - - - - } -} diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs new file mode 100644 index 000000000..8a3e0d66e --- /dev/null +++ b/IRaCIS.Core.API/Progranm.cs @@ -0,0 +1,306 @@ +using System; +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Configuration; +using Serilog; +using MediatR; +using IRaCIS.Core.Application.MediatR.Handlers; +using System.Threading.Tasks; +using MassTransit; +using MassTransit.NewIdProviders; +using System.IO; +using IRaCIS.Core.Domain.Share; +using IRaCIS.Core.Infra.EFCore; +using IRaCIS.Core.Application.Helper; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Builder; +using IRaCIS.Core.API; +using Autofac; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; +using IRaCIS.Core.Application.Filter; +using Microsoft.AspNetCore.HttpOverrides; +using IRaCIS.Application.Services.BackGroundJob; +using LogDashboard; +using OfficeOpenXml.Utils; + + + +#region 获取环境变量 +//以配置文件为准,否则 从url中取环境值(服务以命令行传递参数启动,配置文件配置了就不需要传递环境参数) +var config = new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + +var enviromentName = config["ASPNETCORE_ENVIRONMENT"]; + +if (string.IsNullOrWhiteSpace(enviromentName)) +{ + + var index = Array.IndexOf(args, "--env"); + enviromentName = index > -1 + ? args[index + 1] + : "Development"; +} +#endregion + +var builder = WebApplication.CreateBuilder(new WebApplicationOptions +{ + EnvironmentName = enviromentName +}); + +#region 兼容windows 服务命令行的方式 + +//foreach (var arg in args) +//{ +// Console.WriteLine(arg); +//} + +int urlsIndex = Array.FindIndex(args, arg => arg != null && arg.StartsWith("--urls")); + +if (urlsIndex > -1) +{ + var url = args[urlsIndex].Substring("--urls=".Length); + Console.WriteLine(url); + builder.WebHost.UseUrls(url); +} + +#endregion + +#region 主机配置 + + +NewId.SetProcessIdProvider(new CurrentProcessIdProvider()); + +builder.Configuration.AddJsonFile("appsettings.json", false, true) + .AddJsonFile($"appsettings.{enviromentName}.json", false, true); + +builder.Host + .UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .ConfigureContainer(containerBuilder => + { + containerBuilder.RegisterModule(); + }) + .UseWindowsService().UseSerilog(); +#endregion + + +#region 配置服务 +var _configuration = builder.Configuration; + +//健康检查 +builder.Services.AddHealthChecks(); +//本地化 +builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resources"); + +// 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim() +builder.Services.AddControllers(options => +{ + //options.Filters.Add(); + options.Filters.Add(); + options.Filters.Add(); + options.Filters.Add(); + + if (_configuration.GetSection("BasicSystemConfig").GetValue("OpenLoginLimit")) + { + options.Filters.Add(); + } + +}) + .AddNewtonsoftJsonSetup(); // NewtonsoftJson 序列化 处理 + +builder.Services.AddOptions().Configure(_configuration.GetSection("SystemEmailSendConfig")); +builder.Services.AddOptions().Configure(_configuration.GetSection("BasicSystemConfig")); +builder.Services.AddOptions().Configure(_configuration.GetSection("AliyunOSS")); +builder.Services.AddOptions().Configure(_configuration.GetSection("ObjectStoreService")); + + +//动态WebApi + UnifiedApiResultFilter 省掉控制器代码 +builder.Services.AddDynamicWebApiSetup(); +//AutoMapper +builder.Services.AddAutoMapperSetup(); +//EF ORM QueryWithNoLock +builder.Services.AddEFSetup(_configuration); +//Http 响应压缩 +builder.Services.AddResponseCompressionSetup(); +//Swagger Api 文档 +builder.Services.AddSwaggerSetup(); +//JWT Token 验证 +builder.Services.AddJWTAuthSetup(_configuration); + +// MediatR 进程内消息 事件解耦 从程序集中 注册命令和handler对应关系 +builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining()); +// EasyCaching 缓存 +builder.Services.AddEasyCachingSetup(_configuration); + +// hangfire 定时任务框架 有界面,更友好~ +builder.Services.AddhangfireSetup(_configuration); + +// +builder.Services.AddQuartZSetup(_configuration); + +//Serilog 日志可视化 LogDashboard日志 +builder.Services.AddLogDashboardSetup(); + + +builder.Services.AddJsonConfigSetup(_configuration); +//转发头设置 获取真实IP +builder.Services.Configure(options => +{ + options.ForwardedHeaders = + ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; +}); +//Dicom影像渲染图片 跨平台 +builder.Services.AddDicomSetup(); + +// 实时应用 +builder.Services.AddSignalR(); + +builder.Services.AddSingleton(); + + + + + +#region 历史废弃配置 +//builder.Services.AddMemoryCache(); +////上传限制 配置 +//builder.Services.Configure(options => +//{ +// options.MultipartBodyLengthLimit = int.MaxValue; +// options.ValueCountLimit = int.MaxValue; +// options.ValueLengthLimit = int.MaxValue; +//}); +//IP 限流 可设置白名单 或者黑名单 +//services.AddIpPolicyRateLimitSetup(_configuration); +// 用户类型 策略授权 +//services.AddAuthorizationPolicySetup(_configuration); +#endregion + +#endregion + +var app = builder.Build(); +var env = app.Environment; + +#region 配置中间件 + + +// Configure the HTTP request pipeline. + +//本地化 +app.UseLocalization(); + +app.UseForwardedHeaders(); + +//响应压缩 +app.UseResponseCompression(); + +//app.UseCors(t => t.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); + +//不需要 token 访问的静态文件 wwwroot css, JavaScript, and images don't require authentication. +app.UseStaticFiles(); + +//app.UseMiddleware(); + +//LogDashboard +app.UseLogDashboard("/LogDashboard"); + +//hangfire +app.UseHangfireConfig(env); + + +////限流 中间件 +//app.UseIpRateLimiting(); + + +if (env.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} +else +{ + //app.UseHsts(); +} + +// 特殊异常处理 比如 404 +app.UseStatusCodePagesWithReExecute("/Error/{0}"); + +SwaggerSetup.Configure(app, env); + + +////serilog 记录请求的用户信息 +app.UseSerilogConfig(env); + +app.UseRouting(); + +app.UseCors(t => t.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); + +//app.UseIRacisHostStaticFileStore(env); + + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +app.MapHub("/UploadHub"); +app.MapHealthChecks("/health"); + + +// Serilog +SerilogExtension.AddSerilogSetup(enviromentName, app.Services); + + +var hangfireJobService = app.Services.GetRequiredService(); + +await hangfireJobService.InitHangfireJobTaskAsync(); + + +#endregion + +try +{ + #region 运行环境 部署平台 + + Log.Logger.Warning($"当前环境:{enviromentName}"); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Log.Logger.Warning($"当前部署平台环境:windows"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Log.Logger.Warning($"当前部署平台环境:linux"); + } + else + { + Log.Logger.Warning($"当前部署平台环境:OSX or FreeBSD"); + } + + #endregion + + + Log.Logger.Warning($"ContentRootPath:{env.ContentRootPath}"); + + + string parentDirectory = Path.GetFullPath(Path.Combine(env.ContentRootPath, "..")); + + + Log.Logger.Warning($"ContentRootPath——parentDirectory:{parentDirectory}"); + + //Log.Logger.Warning($"ContentRootPath——GetParent:{Directory.GetParent(env.ContentRootPath).Parent.FullName}"); + //Log.Logger.Warning($"ContentRootPath——xx:{Path.GetDirectoryName(Path.GetDirectoryName(env.ContentRootPath))}"); + + app.Run(); + +} +catch (Exception e) +{ + + Log.Logger.Error(e.InnerException is null ? e.Message + e.StackTrace : e.InnerException?.Message + e.InnerException?.StackTrace); +} + + + diff --git a/IRaCIS.Core.API/Startup.cs b/IRaCIS.Core.API/Startup.cs deleted file mode 100644 index 9d7a01577..000000000 --- a/IRaCIS.Core.API/Startup.cs +++ /dev/null @@ -1,262 +0,0 @@ -using Autofac; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; -using IRaCIS.Core.Application.Filter; -using LogDashboard; -using MediatR; -using IRaCIS.Core.Application.MediatR.Handlers; -using Microsoft.Extensions.Logging; -using AspNetCoreRateLimit; -using Microsoft.AspNetCore.HttpOverrides; -using IRaCIS.Core.Infra.EFCore; -using System.Globalization; -using Microsoft.AspNetCore.Localization; -using Localization; -using Magicodes.ExporterAndImporter.Core.Filters; -using IRaCIS.Core.Application.MediatR.CommandAndQueries; -using IRaCIS.Core.Infra.EFCore.Common; -using Invio.Extensions.Authentication.JwtBearer; -using Microsoft.AspNetCore.SignalR; -using IRaCIS.Core.Domain.Share; -using Microsoft.AspNetCore.StaticFiles; -using IRaCIS.Application.Services.BackGroundJob; -using IRaCIS.Core.Application.Helper; -using Microsoft.AspNetCore.Http; -using Autofac.Core; -using DocumentFormat.OpenXml.InkML; -using EasyCaching.Core; - -namespace IRaCIS.Core.API -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - public ILogger _logger { get; } - - public IConfiguration _configuration { get; } - - //// ConfigureContainer is where you can register things directly - //// with Autofac. This runs after ConfigureServices so the things - //// here will override registrations made in ConfigureServices. - //// Don't build the container; that gets done for you by the factory. - // for castle - public void ConfigureContainer(ContainerBuilder containerBuilder) - { - containerBuilder.RegisterModule(); - - #region Test - //containerBuilder.RegisterType().PropertiesAutowired().InstancePerLifetimeScope();//注册仓储 - - //var container = containerBuilder.Build(); - - //// Now you can resolve services using Autofac. For example, - //// this line will execute the lambda expression registered - //// to the IConfigReader service. - //using (var scope = container.BeginLifetimeScope()) - //{ - // var reader = scope.Resolve(); - - // var test = scope.Resolve(); - // var test2 = scope.Resolve(); - - // var test3 = scope.Resolve>(); - //} - #endregion - } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - //健康检查 - services.AddHealthChecks(); - //本地化 - services.AddJsonLocalization(options => options.ResourcesPath = "Resources"); - - // 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim() - services.AddControllers(options => - { - //options.Filters.Add(); - options.Filters.Add(); - options.Filters.Add(); - options.Filters.Add(); - - if (_configuration.GetSection("BasicSystemConfig").GetValue("OpenLoginLimit")) - { - options.Filters.Add(); - } - - - }) - .AddNewtonsoftJsonSetup(); // NewtonsoftJson 序列化 处理 - - services.AddOptions().Configure(_configuration.GetSection("SystemEmailSendConfig")); - services.AddOptions().Configure(_configuration.GetSection("BasicSystemConfig")); - services.AddOptions().Configure(_configuration.GetSection("AliyunOSS")); - services.AddOptions().Configure(_configuration.GetSection("ObjectStoreService")); - - - //动态WebApi + UnifiedApiResultFilter 省掉控制器代码 - services.AddDynamicWebApiSetup(); - //AutoMapper - services.AddAutoMapperSetup(); - //EF ORM QueryWithNoLock - services.AddEFSetup(_configuration); - //Http 响应压缩 - services.AddResponseCompressionSetup(); - //Swagger Api 文档 - services.AddSwaggerSetup(); - //JWT Token 验证 - services.AddJWTAuthSetup(_configuration); - // MediatR 进程内消息 事件解耦 从程序集中 注册命令和handler对应关系 - services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining()); - // EasyCaching 缓存 - services.AddEasyCachingSetup(_configuration); - - //services.AddDistributedMemoryCache(); - - // hangfire 定时任务框架 有界面,更友好~ - services.AddhangfireSetup(_configuration); - - // QuartZ 定时任务框架 使用了hangfire 暂时不用,后续需要可以打开,已经配好 - services.AddQuartZSetup(_configuration); - - // 保护上传文件 - //services.AddStaticFileAuthorizationSetup(); - - - ////HttpReports 暂时废弃 - //services.AddHttpReports().AddHttpTransport(); - //Serilog 日志可视化 LogDashboard日志 - services.AddLogDashboardSetup(); - //上传限制 配置 - services.Configure(options => - { - options.MultipartBodyLengthLimit = int.MaxValue; - options.ValueCountLimit = int.MaxValue; - options.ValueLengthLimit = int.MaxValue; - }); - //IP 限流 可设置白名单 或者黑名单 - //services.AddIpPolicyRateLimitSetup(_configuration); - // 用户类型 策略授权 - //services.AddAuthorizationPolicySetup(_configuration); - - services.AddJsonConfigSetup(_configuration); - //转发头设置 获取真实IP - services.Configure(options => - { - options.ForwardedHeaders = - ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; - }); - //Dicom影像渲染图片 跨平台 - services.AddDicomSetup(); - - // 实时应用 - services.AddSignalR(); - - - services.AddSingleton(); - - //services.AddSingleton(); - - services.AddMemoryCache(); - - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public async void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - //app.UsePathBase(PathString.FromUriComponent("/api")); - - //本地化 - app.UseLocalization(); - - app.UseForwardedHeaders(); - - //响应压缩 - app.UseResponseCompression(); - - //app.UseCors(t => t.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); - - //不需要 token 访问的静态文件 wwwroot css, JavaScript, and images don't require authentication. - app.UseStaticFiles(); - - - //LogDashboard - app.UseLogDashboard("/back/logs"); - - //hangfire - app.UseHangfireConfig(env); - - ////暂时废弃 - //app.UseHttpReports(); - - ////限流 中间件 - //app.UseIpRateLimiting(); - - - - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - //app.UseHsts(); - } - Console.WriteLine("当前环境: " + env.EnvironmentName); - - //app.UseMiddleware(); - - // 特殊异常处理 比如 404 - app.UseStatusCodePagesWithReExecute("/Error/{0}"); - - SwaggerSetup.Configure(app, env); - - - - ////serilog 记录请求的用户信息 - app.UseSerilogConfig(env); - - app.UseRouting(); - - app.UseCors(t => t.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); - - //app.UseIRacisHostStaticFileStore(env); - app.UseMiddleware(); - - - app.UseAuthentication(); - //app.UseJwtBearerQueryString(); - app.UseAuthorization(); - - ////文件伺服 必须带Token 访问 - ////app.UseIRacisHostStaticFileStore(env); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - - endpoints.MapHub("/UploadHub"); - - endpoints.MapHealthChecks("/health"); - }); - - - var hangfireJobService = app.ApplicationServices.GetRequiredService(); - - await hangfireJobService.InitHangfireJobTaskAsync(); - //有的时候每调用 - //HangfireJobHelper.NotImmediatelyOnceOnlyJob(t => t.InitHangfireJobTaskAsync(),TimeSpan.FromSeconds(1)); - - - } - } -} diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index e48c8d5d2..16d18b105 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -547,6 +547,32 @@ InternationalizationService + + + 前端国际化内容接口 + + + + + + 前端批量提交,后端判断不存在就添加,存在就更新 + + + + + + 后端之前批量添加接口 + + + + + + + 前后端添加的时候,区分了,前端判断重复多了多了一个路由 路由+标识唯一 + + + + PublishLogService diff --git a/IRaCIS.Core.Infra.EFCore/Repository/DynamicRelationalExtensions.cs b/IRaCIS.Core.Infra.EFCore/Repository/DynamicRelationalExtensions.cs new file mode 100644 index 000000000..b2ebb10f0 --- /dev/null +++ b/IRaCIS.Core.Infra.EFCore/Repository/DynamicRelationalExtensions.cs @@ -0,0 +1,120 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace IRaCIS.Core.Infra.EFCore +{ + + public static class DynamicRelationalExtensions + { + static MethodInfo UpdateMethodInfo = + typeof(RelationalQueryableExtensions).GetMethod(nameof(RelationalQueryableExtensions.ExecuteUpdate)); + + static MethodInfo UpdateAsyncMethodInfo = + typeof(RelationalQueryableExtensions).GetMethod(nameof(RelationalQueryableExtensions.ExecuteUpdateAsync)); + + #region 避免使用 + + public static int ExecuteUpdate(this IQueryable query, string fieldName, object? fieldValue) + { + var updateBody = BuildUpdateBody(query.ElementType, + new Dictionary { { fieldName, fieldValue } }); + + return (int)UpdateMethodInfo.MakeGenericMethod(query.ElementType).Invoke(null, new object?[] { query, updateBody }); + } + + public static int ExecuteUpdate(this IQueryable query, IReadOnlyDictionary fieldValues) + { + var updateBody = BuildUpdateBody(query.ElementType, fieldValues); + + return (int)UpdateMethodInfo.MakeGenericMethod(query.ElementType).Invoke(null, new object?[] { query, updateBody }); + } + public static Task ExecuteUpdateAsync(this IQueryable query, string fieldName, object? fieldValue, CancellationToken cancellationToken = default) + { + var updateBody = BuildUpdateBody(query.ElementType, + new Dictionary { { fieldName, fieldValue } }); + + return (Task)UpdateAsyncMethodInfo.MakeGenericMethod(query.ElementType).Invoke(null, new object?[] { query, updateBody, cancellationToken })!; + } + #endregion + + public static Dictionary ExtractFieldValues(this Expression> updateFactory) + { + var dic = new Dictionary(); + var obj = (TSource)Activator.CreateInstance(typeof(TSource)); + + Func func = updateFactory.Compile(); + + TSource applyObj = func(obj); + + var propList = ((MemberInitExpression)updateFactory.Body).Bindings.Select(mb => mb.Member.Name) + .Select(propName => typeof(TSource).GetProperty(propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)).ToList(); + + + foreach (PropertyInfo prop in propList) + { + object value = prop.GetValue(applyObj); + dic.Add(prop.Name, value); + } + + return dic; + } + + + + public static Task ExecuteUpdateAsync(this IQueryable query, IReadOnlyDictionary fieldValues, CancellationToken cancellationToken = default) + { + var updateBody = BuildUpdateBody(query.ElementType, fieldValues); + + return (Task)UpdateAsyncMethodInfo.MakeGenericMethod(query.ElementType).Invoke(null, new object?[] { query, updateBody, cancellationToken })!; + } + + + static LambdaExpression BuildUpdateBody(Type entityType, IReadOnlyDictionary fieldValues) + { + var setParam = Expression.Parameter(typeof(SetPropertyCalls<>).MakeGenericType(entityType), "s"); + var objParam = Expression.Parameter(entityType, "e"); + + Expression setBody = setParam; + + foreach (var pair in fieldValues) + { + var propExpression = Expression.PropertyOrField(objParam, pair.Key); + var valueExpression = ValueForType(propExpression.Type, pair.Value); + + // s.SetProperty(e => e.SomeField, value) + setBody = Expression.Call(setBody, nameof(SetPropertyCalls.SetProperty), + new[] { propExpression.Type }, Expression.Lambda(propExpression, objParam), valueExpression); + + } + + // s => s.SetProperty(e => e.SomeField, value) + var updateBody = Expression.Lambda(setBody, setParam); + + return updateBody; + } + + static Expression ValueForType(Type desiredType, object? value) + { + if (value == null) + { + return Expression.Default(desiredType); + } + + if (value.GetType() != desiredType) + { + return Expression.Convert(Expression.Constant(value), desiredType); + } + + return Expression.Constant(value); + } + } + +} diff --git a/IRaCIS.Core.Infra.EFCore/Repository/IRaCISContextExtension.cs b/IRaCIS.Core.Infra.EFCore/Repository/IRaCISContextExtension.cs index 5428578f6..c20c0704c 100644 --- a/IRaCIS.Core.Infra.EFCore/Repository/IRaCISContextExtension.cs +++ b/IRaCIS.Core.Infra.EFCore/Repository/IRaCISContextExtension.cs @@ -157,7 +157,7 @@ namespace IRaCIS.Core.Infra.EFCore { if (deleteFilter == null) throw new ArgumentNullException(nameof(deleteFilter)); - return await _dbContext.Set().IgnoreQueryFilters().Where(deleteFilter).BatchDeleteAsync() > 0; + return await _dbContext.Set().IgnoreQueryFilters().Where(deleteFilter).ExecuteDeleteAsync() > 0; } @@ -166,34 +166,64 @@ namespace IRaCIS.Core.Infra.EFCore { if (where == null) throw new ArgumentNullException(nameof(where)); - var bindings = ((MemberInitExpression)updateFactory.Body).Bindings.ToList(); - var hasPropNameList = bindings.Select(t => t.Member.Name).ToList(); + #region history 使用扩展删除包,同时自动赋值更新人 更新时间 + + //var bindings = ((MemberInitExpression)updateFactory.Body).Bindings.ToList(); + + //var hasPropNameList = bindings.Select(t => t.Member.Name).ToList(); - if (typeof(IAuditUpdate).IsAssignableFrom(typeof(T))) + //if (typeof(IAuditUpdate).IsAssignableFrom(typeof(T))) + //{ + + // if (!hasPropNameList.Contains(nameof(IAuditUpdate.UpdateTime))) + // { + // bindings.Add(Expression.Bind(typeof(T).GetMember(nameof(IAuditUpdate.UpdateTime))[0], Expression.Constant(DateTime.Now))); + + // } + + // if (!hasPropNameList.Contains(nameof(IAuditUpdate.UpdateUserId))) + // { + // bindings.Add(Expression.Bind(typeof(T).GetMember(nameof(IAuditUpdate.UpdateUserId))[0], Expression.Constant(updateUserId))); + + // } + //} + + //var member = Expression.MemberInit(Expression.New(typeof(T)), bindings); + + //var factory = Expression.Lambda>(member, Expression.Parameter(typeof(T), "x")); + + //return await _dbContext.Set().IgnoreQueryFilters().Where(where).BatchUpdateAsync(factory).ConfigureAwait(false) > 0; + + #endregion + + + #region efcore 7 & 8 { + var fieldValues = updateFactory.ExtractFieldValues(); - if (!hasPropNameList.Contains(nameof(IAuditUpdate.UpdateTime))) + var hasPropNameList = ((MemberInitExpression)updateFactory.Body).Bindings.Select(t => t.Member.Name).ToList(); + + if (typeof(IAuditUpdate).IsAssignableFrom(typeof(T))) { - bindings.Add(Expression.Bind(typeof(T).GetMember(nameof(IAuditUpdate.UpdateTime))[0], Expression.Constant(DateTime.Now))); + if (!hasPropNameList.Contains(nameof(IAuditUpdate.UpdateTime))) + { + fieldValues.Add(nameof(IAuditUpdate.UpdateTime), DateTime.Now); + } + + if (!hasPropNameList.Contains(nameof(IAuditUpdate.UpdateUserId))) + { + fieldValues.Add(nameof(IAuditUpdate.UpdateUserId), updateUserId); + } } - if (!hasPropNameList.Contains(nameof(IAuditUpdate.UpdateUserId))) - { - bindings.Add(Expression.Bind(typeof(T).GetMember(nameof(IAuditUpdate.UpdateUserId))[0], Expression.Constant(updateUserId))); - - } + return await _dbContext.Set().IgnoreQueryFilters().Where(where).ExecuteUpdateAsync(fieldValues).ConfigureAwait(false) > 0; } - var member = Expression.MemberInit(Expression.New(typeof(T)), bindings); - - var factory = Expression.Lambda>(member, Expression.Parameter(typeof(T), "x")); - - - return await _dbContext.Set().IgnoreQueryFilters().Where(where).BatchUpdateAsync(factory).ConfigureAwait(false) > 0; + #endregion