51 changed files with 1293 additions and 67 deletions
@ -0,0 +1,13 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.HangFire" Version="2.9.0" /> |
|||
<PackageReference Include="Volo.Abp.BackgroundJobs" Version="2.9.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,34 @@ |
|||
using Hangfire; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using Volo.Abp; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.Hangfire; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.BackgroundJobs.Hangfire |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpBackgroundJobsAbstractionsModule), |
|||
typeof(AbpHangfireModule) |
|||
)] |
|||
public class AbpBackgroundJobsHangfireModule : AbpModule |
|||
{ |
|||
public override void OnPreApplicationInitialization(ApplicationInitializationContext context) |
|||
{ |
|||
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().Value; |
|||
if (!options.IsJobExecutionEnabled) |
|||
{ |
|||
var hangfireOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpHangfireOptions>>().Value; |
|||
hangfireOptions.BackgroundJobServerFactory = CreateOnlyEnqueueJobServer; |
|||
} |
|||
} |
|||
|
|||
private BackgroundJobServer CreateOnlyEnqueueJobServer(IServiceProvider serviceProvider) |
|||
{ |
|||
serviceProvider.GetRequiredService<JobStorage>(); |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
using Hangfire; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.BackgroundJobs.Hangfire |
|||
{ |
|||
[Dependency(ReplaceServices = true)] |
|||
public class HangfireBackgroundJobManager : IBackgroundJobManager, ITransientDependency |
|||
{ |
|||
public virtual Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, |
|||
TimeSpan? delay = null) |
|||
{ |
|||
if (!delay.HasValue) |
|||
{ |
|||
return Task.FromResult( |
|||
BackgroundJob.Enqueue<HangfireJobExecutionAdapter<TArgs>>( |
|||
adapter => adapter.Execute(args) |
|||
) |
|||
); |
|||
} |
|||
else |
|||
{ |
|||
return Task.FromResult( |
|||
BackgroundJob.Schedule<HangfireJobExecutionAdapter<TArgs>>( |
|||
adapter => adapter.Execute(args), |
|||
delay.Value |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace LINGYUN.Abp.BackgroundJobs.Hangfire |
|||
{ |
|||
public class HangfireJobExecutionAdapter<TArgs> |
|||
{ |
|||
protected AbpBackgroundJobOptions Options { get; } |
|||
protected IServiceScopeFactory ServiceScopeFactory { get; } |
|||
protected IBackgroundJobExecuter JobExecuter { get; } |
|||
|
|||
public HangfireJobExecutionAdapter( |
|||
IOptions<AbpBackgroundJobOptions> options, |
|||
IBackgroundJobExecuter jobExecuter, |
|||
IServiceScopeFactory serviceScopeFactory) |
|||
{ |
|||
JobExecuter = jobExecuter; |
|||
ServiceScopeFactory = serviceScopeFactory; |
|||
Options = options.Value; |
|||
} |
|||
|
|||
public void Execute(TArgs args) |
|||
{ |
|||
if (!Options.IsJobExecutionEnabled) |
|||
{ |
|||
throw new AbpException( |
|||
"Background job execution is disabled. " + |
|||
"This method should not be called! " + |
|||
"If you want to enable the background job execution, " + |
|||
$"set {nameof(AbpBackgroundJobOptions)}.{nameof(AbpBackgroundJobOptions.IsJobExecutionEnabled)} to true! " + |
|||
"If you've intentionally disabled job execution and this seems a bug, please report it." |
|||
); |
|||
} |
|||
|
|||
using (var scope = ServiceScopeFactory.CreateScope()) |
|||
{ |
|||
var jobType = Options.GetJob(typeof(TArgs)).JobType; |
|||
var context = new JobExecutionContext(scope.ServiceProvider, jobType, args); |
|||
AsyncHelper.RunSync(() => JobExecuter.ExecuteAsync(context)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
using Hangfire; |
|||
using JetBrains.Annotations; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.BackgroundJobs; |
|||
|
|||
namespace LINGYUN.Abp.BackgroundJobs.Hangfire |
|||
{ |
|||
public static class IBackgroundJobManagerExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// 后台作业进入周期性队列
|
|||
/// </summary>
|
|||
/// <typeparam name="TArgs">作业参数类型</typeparam>
|
|||
/// <param name="backgroundJobManager">后台作业管理器</param>
|
|||
/// <param name="cron">Cron表达式</param>
|
|||
/// <param name="args">作业参数</param>
|
|||
/// <returns></returns>
|
|||
public static Task EnqueueAsync<TArgs>( |
|||
this IBackgroundJobManager backgroundJobManager, |
|||
[NotNull] string cron, |
|||
TArgs args |
|||
) |
|||
{ |
|||
Check.NotNullOrWhiteSpace(cron, nameof(cron)); |
|||
Check.NotNull(args, nameof(args)); |
|||
|
|||
var jobName = BackgroundJobNameAttribute.GetName<TArgs>(); |
|||
|
|||
RecurringJob.AddOrUpdate<HangfireJobExecutionAdapter<TArgs>>(jobName, adapter => adapter.Execute(args), cron); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
using Hangfire; |
|||
using System; |
|||
|
|||
namespace Volo.Abp.BackgroundJobs |
|||
{ |
|||
public class CronGenerator |
|||
{ |
|||
/// <summary>
|
|||
/// 周期性为分钟的任务
|
|||
/// </summary>
|
|||
/// <param name="interval">执行周期的间隔,默认为每分钟一次</param>
|
|||
/// <returns></returns>
|
|||
public static string Minute(int interval = 1) |
|||
{ |
|||
return $"1 0/{interval} * * * ? "; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 周期性为小时的任务
|
|||
/// </summary>
|
|||
/// <param name="minute">第几分钟开始,默认为第一分钟</param>
|
|||
/// <param name="interval">执行周期的间隔,默认为每小时一次</param>
|
|||
/// <returns></returns>
|
|||
public static string Hour(int minute = 1, int interval = 1) |
|||
{ |
|||
return $"1 {minute} 0/ {interval} * * ? "; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 周期性为天的任务
|
|||
/// </summary>
|
|||
/// <param name="hour">第几小时开始,默认从1点开始</param>
|
|||
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
|
|||
/// <param name="interval">执行周期的间隔,默认为每天一次</param>
|
|||
/// <returns></returns>
|
|||
public static string Day(int hour = 1, int minute = 1, int interval = 1) |
|||
{ |
|||
return $"1 {minute} {hour} 1/ {interval} * ? "; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 周期性为周的任务
|
|||
/// </summary>
|
|||
/// <param name="dayOfWeek">星期几开始,默认从星期一点开始</param>
|
|||
/// <param name="hour">第几小时开始,默认从1点开始</param>
|
|||
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
|
|||
/// <returns></returns>
|
|||
public static string Week(DayOfWeek dayOfWeek = DayOfWeek.Monday, int hour = 1, int minute = 1) |
|||
{ |
|||
return Cron.Weekly(dayOfWeek, hour, minute); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 周期性为月的任务
|
|||
/// </summary>
|
|||
/// <param name="day">几号开始,默认从一号开始</param>
|
|||
/// <param name="hour">第几小时开始,默认从1点开始</param>
|
|||
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
|
|||
/// <returns></returns>
|
|||
public static string Month(int day = 1, int hour = 1, int minute = 1) |
|||
{ |
|||
return Cron.Monthly(day, hour, minute); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 周期性为年的任务
|
|||
/// </summary>
|
|||
/// <param name="month">几月开始,默认从一月开始</param>
|
|||
/// <param name="day">几号开始,默认从一号开始</param>
|
|||
/// <param name="hour">第几小时开始,默认从1点开始</param>
|
|||
/// <param name="minute">第几分钟开始,默认从第1分钟开始</param>
|
|||
/// <returns></returns>
|
|||
public static string Year(int month = 1, int day = 1, int hour = 1, int minute = 1) |
|||
{ |
|||
return Cron.Yearly(month, day, hour, minute); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> |
|||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
|||
<Version>2.9.0</Version> |
|||
<Company>LINGYUN</Company> |
|||
<Authors>LINGYUN</Authors> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
|||
<OutputPath>D:\LocalNuget</OutputPath> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Hangfire.MySql.Core" Version="2.2.5" /> |
|||
<PackageReference Include="Volo.Abp.HangFire" Version="2.9.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,38 @@ |
|||
using Hangfire; |
|||
using Hangfire.MySql.Core; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Hangfire; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Hangfire.Storage.MySql |
|||
{ |
|||
[DependsOn(typeof(AbpHangfireModule))] |
|||
public class AbpHangfireMySqlStorageModule : AbpModule |
|||
{ |
|||
private MySqlStorage _jobStorage; |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
var mysqlStorageOptions = new MySqlStorageOptions(); |
|||
configuration.GetSection("Hangfire:MySql").Bind(mysqlStorageOptions); |
|||
|
|||
var hangfireMySqlConfiguration = configuration.GetSection("Hangfire:MySql:Connection"); |
|||
var hangfireMySqlCon = hangfireMySqlConfiguration.Exists() |
|||
? hangfireMySqlConfiguration.Value : configuration.GetConnectionString("Default"); |
|||
|
|||
_jobStorage = new MySqlStorage(hangfireMySqlCon, mysqlStorageOptions); |
|||
context.Services.AddSingleton<JobStorage, MySqlStorage>(fac => |
|||
{ |
|||
return _jobStorage; |
|||
}); |
|||
|
|||
context.Services.AddHangfire(config => |
|||
{ |
|||
config.UseStorage(_jobStorage); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> |
|||
<Version>2.9.0</Version> |
|||
<Authors>LINGYUN</Authors> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
|||
<OutputPath>D:\LocalNuget</OutputPath> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="HangFire.SqlServer" Version="1.7.11" /> |
|||
<PackageReference Include="Volo.Abp.HangFire" Version="2.9.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,38 @@ |
|||
using Hangfire; |
|||
using Hangfire.SqlServer; |
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Hangfire; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Hangfire.Storage.SqlServer |
|||
{ |
|||
[DependsOn(typeof(AbpHangfireModule))] |
|||
public class AbpHangfireSqlServerStorageModule : AbpModule |
|||
{ |
|||
private SqlServerStorage _jobStorage; |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
var sqlserverStorageOptions = new SqlServerStorageOptions(); |
|||
configuration.GetSection("Hangfire:SqlServer").Bind(sqlserverStorageOptions); |
|||
|
|||
var hangfireSqlServerConfiguration = configuration.GetSection("Hangfire:SqlServer:Connection"); |
|||
var hangfireSqlServerCon = hangfireSqlServerConfiguration.Exists() |
|||
? hangfireSqlServerConfiguration.Value : configuration.GetConnectionString("Default"); |
|||
|
|||
_jobStorage = new SqlServerStorage(hangfireSqlServerCon, sqlserverStorageOptions); |
|||
context.Services.AddSingleton<JobStorage, SqlServerStorage>(fac => |
|||
{ |
|||
return _jobStorage; |
|||
}); |
|||
|
|||
context.Services.AddHangfire(config => |
|||
{ |
|||
config.UseStorage(_jobStorage); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.Notifications |
|||
{ |
|||
public class NotificationEventData |
|||
{ |
|||
public Guid? TenantId { get; set; } |
|||
public string CateGory { get; set; } |
|||
public string Name { get; set; } |
|||
public string Id { get; set; } |
|||
public NotificationData Data { get; set; } |
|||
public DateTime CreationTime { get; set; } |
|||
public NotificationType NotificationType { get; set; } |
|||
public NotificationSeverity NotificationSeverity { get; set; } |
|||
|
|||
public NotificationEventData() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public NotificationInfo ToNotificationInfo() |
|||
{ |
|||
return new NotificationInfo |
|||
{ |
|||
NotificationSeverity = NotificationSeverity, |
|||
CreationTime = CreationTime, |
|||
Data = Data, |
|||
Id = Id, |
|||
Name = Name, |
|||
CateGory = CateGory, |
|||
NotificationType = NotificationType, |
|||
TenantId = TenantId |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Notifications |
|||
{ |
|||
public class CleanupNotificationJobArgs |
|||
{ |
|||
/// <summary>
|
|||
/// 清理大小
|
|||
/// </summary>
|
|||
public int Count { get; set; } |
|||
/// <summary>
|
|||
/// 清理租户
|
|||
/// </summary>
|
|||
public Guid? TenantId { get; set; } |
|||
} |
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
using LINGYUN.Abp.MessageService.Notifications; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.BackgroundJobs |
|||
{ |
|||
public class NotificationExpritionCleanupJob : AsyncBackgroundJob<CleanupNotificationJobArgs> |
|||
{ |
|||
private readonly ICurrentTenant _currentTenant; |
|||
private readonly IUnitOfWorkManager _unitOfWorkManager; |
|||
private readonly INotificationRepository _notificationRepository; |
|||
public NotificationExpritionCleanupJob( |
|||
ICurrentTenant currentTenant, |
|||
IUnitOfWorkManager unitOfWorkManager, |
|||
INotificationRepository notificationRepository) |
|||
{ |
|||
_currentTenant = currentTenant; |
|||
_unitOfWorkManager = unitOfWorkManager; |
|||
_notificationRepository = notificationRepository; |
|||
} |
|||
|
|||
public override async Task ExecuteAsync(CleanupNotificationJobArgs args) |
|||
{ |
|||
using (var unitOfWork = _unitOfWorkManager.Begin()) |
|||
{ |
|||
using (_currentTenant.Change(args.TenantId)) |
|||
{ |
|||
await _notificationRepository.DeleteExpritionAsync(args.Count); |
|||
|
|||
await unitOfWork.SaveChangesAsync(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
using LINGYUN.Abp.Notifications; |
|||
using Microsoft.Extensions.Logging; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.BackgroundJobs |
|||
{ |
|||
internal class NotificationCleanupExpritionJob : AsyncBackgroundJob<NotificationCleanupExpritionJobArgs>, ITransientDependency |
|||
{ |
|||
protected INotificationStore Store { get; } |
|||
protected IServiceProvider ServiceProvider { get; } |
|||
|
|||
public NotificationCleanupExpritionJob( |
|||
INotificationStore store, |
|||
IServiceProvider serviceProvider) |
|||
{ |
|||
Store = store; |
|||
ServiceProvider = serviceProvider; |
|||
} |
|||
|
|||
public override async Task ExecuteAsync(NotificationCleanupExpritionJobArgs args) |
|||
{ |
|||
try |
|||
{ |
|||
Logger.LogDebug("Before cleanup exprition jobs..."); |
|||
await Store.DeleteNotificationAsync(args.Count); |
|||
Logger.LogDebug("Exprition jobs cleanup job was successful..."); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.LogWarning("Exprition jobs cleanup job was failed..."); |
|||
Logger.LogWarning("Error:{0}", ex.Message); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using Volo.Abp.BackgroundJobs; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.BackgroundJobs |
|||
{ |
|||
[BackgroundJobName("定时清理过期通知消息任务")] |
|||
internal class NotificationCleanupExpritionJobArgs |
|||
{ |
|||
/// <summary>
|
|||
/// 清理大小
|
|||
/// </summary>
|
|||
public int Count { get; set; } |
|||
|
|||
public NotificationCleanupExpritionJobArgs() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public NotificationCleanupExpritionJobArgs(int count = 200) |
|||
{ |
|||
Count = count; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
using LINGYUN.Abp.Notifications; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BackgroundJobs; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
using Volo.Abp.Uow; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.EventBus.Distributed |
|||
{ |
|||
/// <summary>
|
|||
/// 订阅通知发布事件,统一发布消息
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 作用在于SignalR客户端只会与一台服务器建立连接,
|
|||
/// 只有启用了SignlR服务端的才能真正将消息发布到客户端
|
|||
/// </remarks>
|
|||
public class NotificationEventHandler : IDistributedEventHandler<NotificationEventData>, ITransientDependency |
|||
{ |
|||
/// <summary>
|
|||
/// Reference to <see cref="ILogger<DefaultNotificationDispatcher>"/>.
|
|||
/// </summary>
|
|||
public ILogger<NotificationEventHandler> Logger { get; set; } |
|||
/// <summary>
|
|||
/// Reference to <see cref="IBackgroundJobManager"/>.
|
|||
/// </summary>
|
|||
protected IBackgroundJobManager BackgroundJobManager; |
|||
/// <summary>
|
|||
/// Reference to <see cref="INotificationStore"/>.
|
|||
/// </summary>
|
|||
protected INotificationStore NotificationStore { get; } |
|||
/// <summary>
|
|||
/// Reference to <see cref="INotificationPublishProviderManager"/>.
|
|||
/// </summary>
|
|||
protected INotificationPublishProviderManager NotificationPublishProviderManager { get; } |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="NotificationEventHandler"/> class.
|
|||
/// </summary>
|
|||
public NotificationEventHandler( |
|||
IBackgroundJobManager backgroundJobManager, |
|||
|
|||
INotificationStore notificationStore, |
|||
INotificationPublishProviderManager notificationPublishProviderManager) |
|||
{ |
|||
BackgroundJobManager = backgroundJobManager; |
|||
|
|||
NotificationStore = notificationStore; |
|||
NotificationPublishProviderManager = notificationPublishProviderManager; |
|||
|
|||
Logger = NullLogger<NotificationEventHandler>.Instance; |
|||
} |
|||
|
|||
[UnitOfWork] |
|||
public virtual async Task HandleEventAsync(NotificationEventData eventData) |
|||
{ |
|||
var notificationInfo = eventData.ToNotificationInfo(); |
|||
|
|||
var providers = Enumerable |
|||
.Reverse(NotificationPublishProviderManager.Providers); |
|||
|
|||
await PublishFromProvidersAsync(providers, notificationInfo); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 指定提供者发布通知
|
|||
/// </summary>
|
|||
/// <param name="providers">提供者列表</param>
|
|||
/// <param name="notificationInfo">通知信息</param>
|
|||
/// <returns></returns>
|
|||
protected async Task PublishFromProvidersAsync(IEnumerable<INotificationPublishProvider> providers, |
|||
NotificationInfo notificationInfo) |
|||
{ |
|||
Logger.LogDebug($"Persistent notification {notificationInfo.Name}"); |
|||
|
|||
// 持久化通知
|
|||
await NotificationStore.InsertNotificationAsync(notificationInfo); |
|||
|
|||
Logger.LogDebug($"Gets a list of user subscriptions {notificationInfo.Name}"); |
|||
// 获取用户订阅列表
|
|||
var userSubscriptions = await NotificationStore.GetSubscriptionsAsync(notificationInfo.TenantId, notificationInfo.Name); |
|||
|
|||
Logger.LogDebug($"Persistent user notifications {notificationInfo.Name}"); |
|||
// 持久化用户通知
|
|||
var subscriptionUserIdentifiers = userSubscriptions.Select(us => new UserIdentifier(us.UserId, us.UserName)); |
|||
|
|||
await NotificationStore.InsertUserNotificationsAsync(notificationInfo, |
|||
subscriptionUserIdentifiers.Select(u => u.UserId)); |
|||
|
|||
// 发布通知
|
|||
foreach (var provider in providers) |
|||
{ |
|||
await PublishAsync(provider, notificationInfo, subscriptionUserIdentifiers); |
|||
} |
|||
} |
|||
/// <summary>
|
|||
/// 发布通知
|
|||
/// </summary>
|
|||
/// <param name="provider">通知发布者</param>
|
|||
/// <param name="notificationInfo">通知信息</param>
|
|||
/// <param name="subscriptionUserIdentifiers">订阅用户列表</param>
|
|||
/// <returns></returns>
|
|||
protected async Task PublishAsync(INotificationPublishProvider provider, NotificationInfo notificationInfo, |
|||
IEnumerable<UserIdentifier> subscriptionUserIdentifiers) |
|||
{ |
|||
try |
|||
{ |
|||
Logger.LogDebug($"Sending notification with provider {provider.Name}"); |
|||
|
|||
// 发布
|
|||
await provider.PublishAsync(notificationInfo, subscriptionUserIdentifiers); |
|||
|
|||
Logger.LogDebug($"Send notification {notificationInfo.Name} with provider {provider.Name} was successful"); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Logger.LogWarning($"Send notification error with provider {provider.Name}"); |
|||
Logger.LogWarning($"Error message:{ex.Message}"); |
|||
|
|||
Logger.LogTrace(ex, $"Send notification error with provider { provider.Name}"); |
|||
|
|||
Logger.LogDebug($"Send notification error, notification {notificationInfo.Name} entry queue"); |
|||
// 发送失败的消息进入后台队列
|
|||
await BackgroundJobManager.EnqueueAsync( |
|||
new NotificationPublishJobArgs(notificationInfo.GetId(), |
|||
provider.GetType().AssemblyQualifiedName, |
|||
subscriptionUserIdentifiers.ToList(), |
|||
notificationInfo.TenantId)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,508 @@ |
|||
// <auto-generated />
|
|||
using System; |
|||
using LINGYUN.Abp.MessageService.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.EntityFrameworkCore.Infrastructure; |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Migrations |
|||
{ |
|||
[DbContext(typeof(MessageServiceHostMigrationsDbContext))] |
|||
[Migration("20200617010925_Add-Notification-Column-CateGory")] |
|||
partial class AddNotificationColumnCateGory |
|||
{ |
|||
protected override void BuildTargetModel(ModelBuilder modelBuilder) |
|||
{ |
|||
#pragma warning disable 612, 618
|
|||
modelBuilder |
|||
.HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.MySql) |
|||
.HasAnnotation("ProductVersion", "3.1.4") |
|||
.HasAnnotation("Relational:MaxIdentifierLength", 64); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Messages.ChatGroup", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<string>("Address") |
|||
.HasColumnType("varchar(256) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(256); |
|||
|
|||
b.Property<bool>("AllowAnonymous") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<bool>("AllowSendMessage") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnName("CreationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<Guid?>("CreatorId") |
|||
.HasColumnName("CreatorId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<string>("Description") |
|||
.HasColumnType("varchar(128) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(128); |
|||
|
|||
b.Property<long>("GroupId") |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<DateTime?>("LastModificationTime") |
|||
.HasColumnName("LastModificationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<Guid?>("LastModifierId") |
|||
.HasColumnName("LastModifierId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<int>("MaxUserCount") |
|||
.HasColumnType("int"); |
|||
|
|||
b.Property<string>("Name") |
|||
.IsRequired() |
|||
.HasColumnType("varchar(20) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(20); |
|||
|
|||
b.Property<string>("Notice") |
|||
.HasColumnType("varchar(64) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<string>("Tag") |
|||
.HasColumnType("varchar(512) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(512); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "Name"); |
|||
|
|||
b.ToTable("AppChatGroups"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Messages.ChatGroupAdmin", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<bool>("AllowAddPeople") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<bool>("AllowDissolveGroup") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<bool>("AllowKickPeople") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<bool>("AllowSendNotice") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<bool>("AllowSilence") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<Guid?>("CreatorId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<long>("GroupId") |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<bool>("IsSuperAdmin") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<DateTime?>("LastModificationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<Guid?>("LastModifierId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "GroupId"); |
|||
|
|||
b.ToTable("AppChatGroupAdmins"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Messages.GroupChatBlack", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<Guid?>("CreatorId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<long>("GroupId") |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<Guid>("ShieldUserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "GroupId"); |
|||
|
|||
b.ToTable("AppGroupChatBlacks"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Messages.GroupMessage", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<string>("Content") |
|||
.IsRequired() |
|||
.HasColumnType("longtext CHARACTER SET utf8mb4") |
|||
.HasMaxLength(1048576); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnName("CreationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<Guid?>("CreatorId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<long>("GroupId") |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<long>("MessageId") |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<sbyte>("SendState") |
|||
.HasColumnType("tinyint"); |
|||
|
|||
b.Property<string>("SendUserName") |
|||
.IsRequired() |
|||
.HasColumnType("varchar(64) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<int>("Type") |
|||
.HasColumnType("int"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "GroupId"); |
|||
|
|||
b.ToTable("AppGroupMessages"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Messages.UserChatBlack", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<Guid?>("CreatorId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid>("ShieldUserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "UserId"); |
|||
|
|||
b.ToTable("AppUserChatBlacks"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Messages.UserChatGroup", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnName("CreationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<Guid?>("CreatorId") |
|||
.HasColumnName("CreatorId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<long>("GroupId") |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "GroupId", "UserId"); |
|||
|
|||
b.ToTable("AppUserChatGroups"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Messages.UserChatSetting", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<bool>("AllowAddFriend") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<bool>("AllowAnonymous") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<bool>("AllowReceiveMessage") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<bool>("AllowSendMessage") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<bool>("RequireAddFriendValition") |
|||
.HasColumnType("tinyint(1)"); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "UserId"); |
|||
|
|||
b.ToTable("AppUserChatSettings"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Messages.UserMessage", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<string>("Content") |
|||
.IsRequired() |
|||
.HasColumnType("longtext CHARACTER SET utf8mb4") |
|||
.HasMaxLength(1048576); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnName("CreationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<Guid?>("CreatorId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<long>("MessageId") |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<Guid>("ReceiveUserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<sbyte>("SendState") |
|||
.HasColumnType("tinyint"); |
|||
|
|||
b.Property<string>("SendUserName") |
|||
.IsRequired() |
|||
.HasColumnType("varchar(64) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(64); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<int>("Type") |
|||
.HasColumnType("int"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "ReceiveUserId"); |
|||
|
|||
b.ToTable("AppUserMessages"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Messages.UserSpecialFocus", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<Guid?>("CreatorId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid>("FocusUserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "UserId"); |
|||
|
|||
b.ToTable("AppUserSpecialFocuss"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Notifications.Notification", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnName("CreationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<DateTime?>("ExpirationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<string>("NotificationCateGory") |
|||
.IsRequired() |
|||
.HasColumnType("varchar(50) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(50); |
|||
|
|||
b.Property<string>("NotificationData") |
|||
.IsRequired() |
|||
.HasColumnType("longtext CHARACTER SET utf8mb4") |
|||
.HasMaxLength(1048576); |
|||
|
|||
b.Property<long>("NotificationId") |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<string>("NotificationName") |
|||
.IsRequired() |
|||
.HasColumnType("varchar(100) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(100); |
|||
|
|||
b.Property<string>("NotificationTypeName") |
|||
.IsRequired() |
|||
.HasColumnType("varchar(512) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(512); |
|||
|
|||
b.Property<sbyte>("Severity") |
|||
.HasColumnType("tinyint"); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<int>("Type") |
|||
.HasColumnType("int"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "NotificationName"); |
|||
|
|||
b.ToTable("AppNotifications"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Notifications.UserNotification", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<long>("NotificationId") |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<int>("ReadStatus") |
|||
.HasColumnType("int"); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "UserId", "NotificationId") |
|||
.HasName("IX_Tenant_User_Notification_Id"); |
|||
|
|||
b.ToTable("AppUserNotifications"); |
|||
}); |
|||
|
|||
modelBuilder.Entity("LINGYUN.Abp.MessageService.Subscriptions.UserSubscribe", b => |
|||
{ |
|||
b.Property<long>("Id") |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("bigint"); |
|||
|
|||
b.Property<DateTime>("CreationTime") |
|||
.HasColumnName("CreationTime") |
|||
.HasColumnType("datetime(6)"); |
|||
|
|||
b.Property<string>("NotificationName") |
|||
.IsRequired() |
|||
.HasColumnType("varchar(100) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(100); |
|||
|
|||
b.Property<Guid?>("TenantId") |
|||
.HasColumnName("TenantId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<Guid>("UserId") |
|||
.HasColumnType("char(36)"); |
|||
|
|||
b.Property<string>("UserName") |
|||
.IsRequired() |
|||
.ValueGeneratedOnAdd() |
|||
.HasColumnType("varchar(128) CHARACTER SET utf8mb4") |
|||
.HasMaxLength(128) |
|||
.HasDefaultValue("/"); |
|||
|
|||
b.HasKey("Id"); |
|||
|
|||
b.HasIndex("TenantId", "UserId", "NotificationName") |
|||
.IsUnique() |
|||
.HasName("IX_Tenant_User_Notification_Name"); |
|||
|
|||
b.ToTable("AppUserSubscribes"); |
|||
}); |
|||
#pragma warning restore 612, 618
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
using Microsoft.EntityFrameworkCore.Migrations; |
|||
|
|||
namespace LINGYUN.Abp.MessageService.Migrations |
|||
{ |
|||
public partial class AddNotificationColumnCateGory : Migration |
|||
{ |
|||
protected override void Up(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.DropIndex( |
|||
name: "IX_AppNotifications_NotificationName", |
|||
table: "AppNotifications"); |
|||
|
|||
migrationBuilder.AddColumn<string>( |
|||
name: "NotificationCateGory", |
|||
table: "AppNotifications", |
|||
maxLength: 50, |
|||
nullable: false, |
|||
defaultValue: ""); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_AppNotifications_TenantId_NotificationName", |
|||
table: "AppNotifications", |
|||
columns: new[] { "TenantId", "NotificationName" }); |
|||
} |
|||
|
|||
protected override void Down(MigrationBuilder migrationBuilder) |
|||
{ |
|||
migrationBuilder.DropIndex( |
|||
name: "IX_AppNotifications_TenantId_NotificationName", |
|||
table: "AppNotifications"); |
|||
|
|||
migrationBuilder.DropColumn( |
|||
name: "NotificationCateGory", |
|||
table: "AppNotifications"); |
|||
|
|||
migrationBuilder.CreateIndex( |
|||
name: "IX_AppNotifications_NotificationName", |
|||
table: "AppNotifications", |
|||
column: "NotificationName"); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue