1 异常产生原因:
由于未为Role实体定义相就的缓存强制销毁器类:Services.Customers.Caching.RoleCacheEventConsumer,从而导致Services.Events.EventPublisher.PublishAsync<TEvent>(TEvent @event)中的 consumers实例为0,如下图所示:
2 Core.Events.EntityInsertedEvent
using Core.Domain;
namespace Core.Events
{
/// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)。</typeparam>
/// <summary>
/// 【实体插入事件--类】
/// <remarks>
/// 摘要:
/// 通过该类的属性成员在指定实体的1个实例执行持久化插入操作后,为实体所有相关实例的分布式缓存的强制移除操作提供数据支撑,同时避免指定实体的列表渲染显示出现的异常。
/// </remarks>
/// </summary>
public class EntityInsertedEvent<T> where T : BaseEntity
{
#region 拷贝构造方法
/// <param name="entity">指定实体的1个指定实例。</param>
/// <summary>
/// 【拷贝构造方法】
/// <remarks>
/// 摘要:
/// 拷贝构造方法通过其参数实例,实例化该类中同一类型的属性成员。
/// </remarks>
/// </summary>
public EntityInsertedEvent(T entity)
{
Entity = entity;
}
#endregion
#region 属性
/// <summary>
/// 【实体】
/// <remarks>
/// 摘要:
/// 获取指定实体的1个指定实例。
/// </remarks>
/// </summary>
public T Entity { get; }
#endregion
}
}
3 Core.Events.EntityUpdatedEvent
using Core.Domain;
namespace Core.Events
{
/// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)。</typeparam>
/// <summary>
/// 【实体更新事件--类】
/// <remarks>
/// 摘要:
/// 通过该类的属性成员在指定实体的1个实例执行持久化更新操作后,为实体所有相关实例的分布式缓存的强制移除操作提供数据支撑,同时避免指定实体的列表渲染显示出现的异常。
/// </remarks>
/// </summary>
public class EntityUpdatedEvent<T> where T : BaseEntity
{
#region 拷贝构造方法
/// <param name="entity">指定实体的1个指定实例。</param>
/// <summary>
/// 【拷贝构造方法】
/// <remarks>
/// 摘要:
/// 拷贝构造方法通过其参数实例,实例化该类中同一类型的属性成员。
/// </remarks>
/// </summary>
public EntityUpdatedEvent(T entity)
{
Entity = entity;
}
#endregion
#region 属性
/// <summary>
/// 【实体】
/// <remarks>
/// 摘要:
/// 获取指定实体的1个指定实例。
/// </remarks>
/// </summary>
public T Entity { get; }
#endregion
}
}
4 Core.Events. EntityDeletedEvent
using Core.Domain;
namespace Core.Events
{
/// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)。</typeparam>
/// <summary>
/// 【实体持久化/逻辑删除事件--类】
/// <remarks>
/// 摘要:
/// 通过该类的属性成员在指定实体的1个实例执行持久化/逻辑删除操作后,为实体所有相关实例的分布式缓存的强制移除操作提供数据支撑,同时避免指定实体的列表渲染显示出现的异常。
/// </remarks>
/// </summary>
public class EntityDeletedEvent<T> where T : BaseEntity
{
#region 拷贝构造方法
/// <param name="entity">指定实体的1个指定实例。</param>
/// <summary>
/// 【拷贝构造方法】
/// <remarks>
/// 摘要:
/// 拷贝构造方法通过其参数实例,实例化该类中同一类型的属性成员。
/// </remarks>
/// </summary>
public EntityDeletedEvent(T entity)
{
Entity = entity;
}
#endregion
#region 属性
/// <summary>
/// 【实体】
/// <remarks>
/// 摘要:
/// 获取指定实体的1个指定实例。
/// </remarks>
/// </summary>
public T Entity { get; }
#endregion
}
}
5 Core.Events.IEventPublisher
namespace Core.Events
{
/// <summary>
/// 【缓存移除事件触发--接口】
/// <remarks>
/// 摘要:
/// 通过继承于该接口的具体实现类中的方法成员用于实例化缓存的强制移除句柄的实例,触发执行缓存的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
public interface IEventPublisher
{
#region 方法
/// <typeparam name="TEvent">泛型类型实例(这里特指:实体实例的变更事件类的类型实例,为缓存的强制移除提供触发句柄)。</typeparam>
/// <param name="event">实体实例的变更事件类的1个实例,为缓存的强制移除提供触发句柄。</param>
/// <summary>
/// 【异步触发】
/// <remarks>
/// 摘要:
/// 该方法用于实例化缓存的强制移除句柄的实例,触发执行缓存的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
Task PublishAsync<TEvent>(TEvent @event);
/// <typeparam name="TEvent">泛型类型实例(这里特指:实体实例的变更事件类的类型实例,为缓存的强制移除提供触发句柄)。</typeparam>
/// <param name="event">实体实例的变更事件类的1个实例,为缓存的强制移除提供触发句柄。</param>
/// <summary>
/// 【触发】
/// <remarks>
/// 摘要:
/// 该方法用于实例化缓存的强制移除句柄的实例,触发执行缓存的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
void Publish<TEvent>(TEvent @event);
#endregion
}
}
6 Core.Events.EventPublisherExtensions
using Core.Domain;
namespace Core.Events
{
/// <summary>
/// 【事件触发器扩展--类】
/// <remarks>
/// 摘要:
/// 通过该类中的成员方法以指定事件触发类的指定实例进行实例化,从而为执行缓存项的强制移除句柄,从分布式缓存数据库移除1/n个缓存项提供实例支撑。
/// </remarks>
/// </summary>
public static class EventPublisherExtensions
{
/// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)。</typeparam>
/// <param name="eventPublisher">事件触发类的1个指定实例。</param>
/// <param name="entity">指定实体的1/n实例。</param>
/// <summary>
/// 【异步实体插入】
/// <remarks>
/// 摘要:
/// 在指定实体的1个实例执行持久化插入新操作后,执行缓存项的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
public static async Task EntityInsertedAsync<T>(this IEventPublisher eventPublisher, T entity) where T : BaseEntity
{
await eventPublisher.PublishAsync(new EntityInsertedEvent<T>(entity));
}
/// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)。</typeparam>
/// <param name="eventPublisher">事件触发类的1个指定实例。</param>
/// <param name="entity">指定实体的1/n实例。</param>
/// <summary>
/// 【实体插入】
/// <remarks>
/// 摘要:
/// 在指定实体的1个实例执行持久化插入新操作后,执行缓存项的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
public static void EntityInserted<T>(this IEventPublisher eventPublisher, T entity) where T : BaseEntity
{
eventPublisher.Publish(new EntityInsertedEvent<T>(entity));
}
/// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)。</typeparam>
/// <param name="eventPublisher">事件触发类的1个指定实例。</param>
/// <param name="entity">指定实体的1/n实例。</param>
/// <summary>
/// 【异步实体更新】
/// <remarks>
/// 摘要:
/// 在指定实体的1个实例执行持久化更新操作后,执行缓存项的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
public static async Task EntityUpdatedAsync<T>(this IEventPublisher eventPublisher, T entity) where T : BaseEntity
{
await eventPublisher.PublishAsync(new EntityUpdatedEvent<T>(entity));
}
/// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)。</typeparam>
/// <param name="eventPublisher">事件触发类的1个指定实例。</param>
/// <param name="entity">指定实体的1/n实例。</param>
/// <summary>
/// 【异步实体更新】
/// <remarks>
/// 摘要:
/// 在指定实体的1个实例执行持久化更新操作后,执行缓存项的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
public static void EntityUpdated<T>(this IEventPublisher eventPublisher, T entity) where T : BaseEntity
{
eventPublisher.Publish(new EntityUpdatedEvent<T>(entity));
}
/// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)。</typeparam>
/// <param name="eventPublisher">事件触发类的1个指定实例。</param>
/// <param name="entity">指定实体的1/n实例。</param>
/// <summary>
/// 【异步实体删除】
/// <remarks>
/// 摘要:
/// 在指定实体的1个实例执行持久化删除新操作后,执行缓存项的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
public static async Task EntityDeletedAsync<T>(this IEventPublisher eventPublisher, T entity) where T : BaseEntity
{
await eventPublisher.PublishAsync(new EntityDeletedEvent<T>(entity));
}
/// <typeparam name="T">泛型类型实例(这里特指:1个指定实体的类型实例)。</typeparam>
/// <param name="eventPublisher">事件触发类的1个指定实例。</param>
/// <param name="entity">指定实体的1/n实例。</param>
/// <summary>
/// 【实体删除】
/// <remarks>
/// 摘要:
/// 在指定实体的1个实例执行持久化删除新操作后,执行缓存项的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
public static void EntityDeleted<T>(this IEventPublisher eventPublisher, T entity) where T : BaseEntity
{
eventPublisher.Publish(new EntityDeletedEvent<T>(entity));
}
}
}
7 Core.Events.IConsumer<T>
namespace Services.Events
{
/// <typeparam name="T">泛型类型实例(这里特指:实体插入事件类的类型实例、实体更新事件类的类型实例或实体持久化/逻辑删除事件类的类型实例)。</typeparam>
/// <summary>
/// 摘要:
/// 通过继承于该接口具体实现类中的方法成员,在指定实体的1个实例执行插入、更新或持久化/逻辑删除操作时,为实体所有相关实例的分布式缓存的强制移除操作提供数据支撑,同时避免指定实体的列表渲染显示出现的异常。
/// </summary>
public interface IConsumer<T>
{
/// <param name="eventMessage">实体插、更新或持久化/逻辑删除事件类的1个指定实例。</param>
/// <summary>
/// 【异步句柄事件】
/// <remarks>
/// 摘要:
/// 在指定实体的1个实例执行插入、更新或持久化/逻辑删除操作后,为该实体实例的分布式缓存的强制移除操作提供数据支撑,从而为新变更的实例缓存操作,预留出相应的内存空间。
/// </remarks>
/// </summary>
Task HandleEventAsync(T eventMessage);
}
}
8 Core.Events.EventPublisher
using Core.Events;
using Core.Infrastructure;
namespace Services.Events
{
/// <summary>
/// 【缓存移除事件触发--类】
/// <remarks>
/// 摘要:
/// 通过该类中的方法成员用于实例化缓存项的强制移除句柄的实例,触发执行缓存项的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
public class EventPublisher : IEventPublisher
{
#region 方法--接口实现
/// <typeparam name="TEvent">泛型类型实例(这里特指:实体实例的变更事件类的类型实例,为缓存项的强制移除提供触发句柄)。</typeparam>
/// <param name="event">实体实例的变更事件类的1个实例,为缓存项的强制移除提供触发句柄。</param>
/// <summary>
/// 【异步触发】
/// <remarks>
/// 摘要:
/// 该方法用于实例化缓存的强制移除句柄的实例,触发执行缓存项的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
public virtual async Task PublishAsync<TEvent>(TEvent @event)
{
//获取所有继承于“IConsumer”接口具体实现类的实例。
var consumers = EngineContext.Current.ResolveAll<IConsumer<TEvent>>().ToList();
foreach (var consumer in consumers)
{
try
{
//在指定实体的1个实例执行插入、更新或持久化/逻辑删除操作后,触发执行缓存项的强制移除句柄,从而移除缓存数据库中该实体所有相关的缓存项。
await consumer.HandleEventAsync(@event);
}
catch (Exception exception)
{
try
{
}
catch
{
// ignored
}
}
}
}
/// <typeparam name="TEvent">泛型类型实例(这里特指:实体实例的变更事件类的类型实例,为缓存项的强制移除提供触发句柄)。</typeparam>
/// <param name="event">实体实例的变更事件类的1个实例,为缓存项的强制移除提供触发句柄。</param>
/// <summary>
/// 【触发】
/// <remarks>
/// 摘要:
/// 该方法用于实例化缓存的强制移除句柄的实例,触发执行缓存项的强制移除句柄,从分布式缓存数据库移除1/n个缓存项。
/// </remarks>
/// </summary>
public virtual void Publish<TEvent>(TEvent @event)
{
//获取所有继承于“IConsumer”接口具体实现类的实例。
var consumers = EngineContext.Current.ResolveAll<IConsumer<TEvent>>().ToList();
foreach (var consumer in consumers)
try
{
//在指定实体的1个实例执行插入、更新或持久化/逻辑删除操作后,触发执行缓存项的强制移除句柄,从而移除缓存数据库中该实体所有相关的缓存项。
consumer.HandleEventAsync(@event).Wait();
}
catch (Exception exception)
{
try
{
}
catch
{
// ignored
}
}
}
#endregion
}
}
9 Services.Customers.Caching.CacheEventConsumer
using Core.Domain.Customers;
using Services.Caching;
namespace Services.Customers.Caching
{
/// <summary>
/// 摘要:
/// 通过该类中的方法成员,在角色实体的1个实例执行插入、更新或持久化/逻辑删除操作后,为该实体所有相关实例的分布式缓存的强制移除操作提供数据支撑,同时避免角色实体的列表渲染显示出现的异常。
/// </summary>
public class RoleCacheEventConsumer : CacheEventConsumer<Role>
{
}
}
注意:
RoleCacheEventConsumer类必须被定义,且注意其具体定义内容,否则会因角色实例的分布式缓存的未被强制移除,从而导致角色实体的列表渲染显示出现的异常。
10 重构Program.cs文件
using Core.Configuration;
using Core.Infrastructure;
using Data;
using Data.Configuration;
using Microsoft.EntityFrameworkCore;
using Framework.Infrastructure.Extensions;
using Core.Caching;
using LinqToDB.DataProvider.SqlServer;
using Core.Events;
using Services.Events;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
//如果启动项中不存在“appsettings.json”文件,则通过.Net(Core)的内置方法自动新建“appsettings.json”文件。
builder.Configuration.AddJsonFile("appsettings.json", true, true);
//把当前程序中所有继承了“IConfig”接口的具体实现类的实例,依赖注入到.Net(Core)内置依赖注入容器实例中,如果需要并把这些数据持久化存储到"appsettings.json"文件。
builder.Services.ConfigureApplicationSettings(builder);
builder.Services.AddScoped<INopFileProvider, NopFileProvider>();
builder.Services.AddSingleton<ILocker, MemoryCacheManager>();
builder.Services.AddSingleton<IStaticCacheManager, MemoryCacheManager>();
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddSingleton<IEventPublisher, EventPublisher>();
var typeFinder = Singleton<ITypeFinder>.Instance;
var consumers = typeFinder.FindClassesOfType(typeof(IConsumer<>)).ToList();
foreach (var consumer in consumers)
foreach (var findInterface in consumer.FindInterfaces((type, criteria) =>
{
var isMatch = type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition());
return isMatch;
}, typeof(IConsumer<>)))
builder.Services.AddScoped(findInterface, consumer);
//注意:在动态对EntityFrameworkCore中间件进行实例化时,必须使用“AddDbContextPool” 内置方法替换“AddDbContext”内置方法,
//否则就会出现异常:“System.InvalidOperationException: The service collection cannot be modified because it is read-only”。
builder.Services.AddDbContextPool<EFCoreContext>(options => {
//从单例实例的字典成员实例中获取当前程序所有配置相关数据。
AppSettings _appSettings = Singleton<AppSettings>.Instance;
//从应用配置类实例中获取数据库连接相关数据。
DataConfig _dataConfig = _appSettings.Get<DataConfig>();
//说明:如果想要“EntityFrameworkCore”中间件支持多数据库软件,则把选择条件中的所有中间件都注入到依赖注入到.Net(Core)框架内置容器即可,
//选择条件来限定当前程序只支持所设定的1个数据库软件,当然“DataConfig”类与“appsettings.json”文件也必须为支持多数据库软件进行重构。
if (_dataConfig.DataProvider.ToString().Equals("sqlserver", StringComparison.InvariantCultureIgnoreCase))
{
//通过“DbContextOptionsBuilder”实例中的参数实例,为“Microsoft.EntityFrameworkCore.SqlServer”中间件的实例化提供参数实例,
//最终把“Microsoft.EntityFrameworkCore.SqlServer”中间件实例,依赖注入到.Net(Core)框架内置容器中。
//IIS发布部署连接字符串必须使用“SQL Server身份认证”数据库连接方式,才能实现发布部署程序与数据库的CURD的操作。
options.UseSqlServer(_dataConfig.ConnectionString);
}
});
//通过AddRazorRuntimeCompilation依赖注入中间件实现页面修改热加载(Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation)。
builder.Services
.AddControllersWithViews()
.AddRazorRuntimeCompilation();
//把具体现类和中间件注入到内置依赖注入容器后,并把.NetCore框架内置依赖注入容器接口实例所存储的当前程序中的具体现类和中间件的实例通过“Engine”(引擎)单例实例存储到单例类的字典属性成员实例中。
//注意:从依赖注入到.Net(Core)框架内置容器中,获取“IServiceProvider”接口实例,必须定义在最后,
//否则“GetServices”/“GetRequiredService”方法将有可能不能获取取1个指定类的实例,因为该类的实例还没有依赖注入到.Net(Core)框架内置容器中。
builder.Services.ConfigureApplicationServices(builder);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
/*注意:
在.net7最好不要直接使用app.UseEndpoints或app.UseMvc来集成路由匹配模式,否则会出现:“ASP0014”警告信息,
为了避免该警告信息直接使用最小API:app.MapControllerRoute来集成路由匹配模式。
*/
app.MapControllerRoute(
name: "areaRoute",
pattern: $"{{area:exists}}/{{controller=Install}}/{{action=Index}}/{{id?}}");
//动态实例化EntityFrameworkCore中间件,必须把InstallRefactoring/Index设定为默认启动页面,
//否则依然会因为当前类的拷贝构造方法实例化EntityFrameworkCore中间件是在程序启动前就被实例化的,如果无数据库如果数据库连接字符串,
//那么就会因EntityFrameworkCore中间件实例中不包含数据库如果数据库连接字符串,从而造成上述异常。
app.MapControllerRoute(
name: "default",
pattern: "{controller=InstallRefactoring}/{action=Index}/{id?}");
// 通过.NetCore框架的内置管道接口实例,实例化“IServiceProvider”接口实例,同时把继承于“IStartup”所有具体现类中的中间件实例集成到.NetCore框架内置管道中。
app.ConfigureRequestPipeline();
app.Run();
对以上功能更为具体实现和注释见230509_008ShopRazor(未执行缓存的强制清理操作导致显示异常解决方案)。