我有一个Blazor应用程序,它使用SQLTableDependency来检测数据库更改,然后通过SignalR通知所有客户端更改。这是有效的,但我需要一种能够检测变化并只通知特定SignalR组的方法。因为SQLTableDependency不关心谁在数据库中插入、更改或删除了记录,所以我也不知道如何知道该向哪个组发送更新。有关我的应用程序以及我正在努力实现的目标的更多详细信息,请参阅下文。
我们为每个客户建立了一个新的组织。一个组织有自己的资产列表,并且可以有多个用户。
Organization.cs
public class Organization
{
public int OrganizationId { get; set; }
public string OrganizationName { get; set; }
public List<Asset> Assets { get; set; }
public List<ApplicationUser> Users { get; set; }
public bool IsDisabled { get; set; }
}
Asset.cs
public class Asset
{
public int AssetId { get; set; }
public string SerialNumber { get; set; }
public int OrganizationId { get; set; }
public virtual Organization Organization { get; set; }
public DateTime DateAdded { get; set; }
}
ApplicationUser.cs
public class ApplicationUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int OrganizationId { get; set; }
public virtual Organization Organization { get; set; }
public List<Connection> Connections { get; set; }
public string Timezone { get; set; }
}
Connection.cs-我将每个SignalR连接存储在数据库中。
public class Connection
{
public string ConnectionId { get; set; }
public string UserName { get; set; }
public bool Connected { get; set; }
public string Group { get; set; }
public DateTime ConnectionTime { get; set; }
}
AssetService.cs
public class AssetService : IAssetService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public AssetService(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public async Task<Asset> AddAssetAsync(Asset asset, string currentUserName)
{
try
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetService<DataContext>();
if (asset.Device != null)
{
db.Entry(asset.Device).State = EntityState.Modified;
}
asset.DateAdded = DateTime.UtcNow;
await db.Assets.AddAsync(asset);
await db.SaveChangesAsync();
return asset;
}
}
catch (System.Exception ex)
{
throw ex;
}
}
}
AssetHub.cs-SignalR Hub
public class ChatHub : Hub
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly IServiceScopeFactory _serviceScopeFactory;
public ChatHub(UserManager<ApplicationUser> userManager, IServiceScopeFactory serviceScopeFactory)
{
_userManager = userManager;
_serviceScopeFactory = serviceScopeFactory;
}
public async Task SendAssetToGroup(string userName, string location, Asset asset)
{
if (!string.IsNullOrWhiteSpace(userName))
{
var user = await _userManager.Users.Include(x => x.Connections).SingleAsync(x => x.UserName == userName);
if (user != null)
{
var group = $"{user.AccountId}-{location}";
await Clients.Group(group).SendAsync("AssetUpdate", user.Email, asset);
}
}
}
public override async Task OnConnectedAsync()
{
var httpContext = Context.GetHttpContext();
var location = httpContext.Request.Query["location"];
using (var scope = _serviceScopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetService<ApplicationDbContext>();
if (!string.IsNullOrWhiteSpace(userName))
{
var user = await db.Users.Include(x => x.Connections).SingleAsync(x => x.UserName == httpContext.User.Identity.Name);
if (user != null)
{
var group = $"{user.OrganizationId}-{location}";
var connection = new Connection { Connected = true, ConnectionId = Context.ConnectionId, Group = group, UserName = user.UserName };
await Groups.AddToGroupAsync(connection.ConnectionId, group);
user.Connections.Add(connection);
db.Users.Update(user);
}
}
await db.SaveChangesAsync();
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
if (!string.IsNullOrWhiteSpace(Context.ConnectionId))
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetService<ApplicationDbContext>();
var connection = await db.Connections.Where(x => x.ConnectionId ==
Context.ConnectionId).FirstOrDefaultAsync();
if (connection != null)
{
await Groups.RemoveFromGroupAsync(connection.ConnectionId, connection.Group);
db.Connections.Remove(connection);
await db.SaveChangesAsync();
}
}
}
await base.OnDisconnectedAsync(exception);
}
}
AssetTableChangeService.cs-这是我需要帮助的地方。当SQLTableDependency检测到Assets表发生更改时,我需要能够调用AssetHub中的SendAssetToGroup方法。由于用户是组织的成员,我不想将更新推送给所有组织,我只想将更新发送给特定组织组的成员。
public class AssetTableChangeService : IAssetTableChangeService
{
private const string TableName = "Assets";
private SqlTableDependency<Asset> _notifier;
private IConfiguration _configuration;
public event AssetChangeDelegate OnAssetChanged;
public StockTableChangeService(IConfiguration configuration)
{
_configuration = configuration;
// SqlTableDependency will trigger an event
// for any record change on monitored table
_notifier = new SqlTableDependency<Asset>(
_configuration.GetConnectionString("DefaultConnection"),
TableName);
_notifier.OnChanged += AssetChanged;
_notifier.Start();
}
private void AssetChanged(object sender, RecordChangedEventArgs<Asset> e)
{
OnAssetChanged.Invoke(this, new AssetChangeEventArgs(e.Entity, e.EntityOldValues));
}
public void Dispose()
{
_notifier.Stop();
_notifier.Dispose();
}
所以流量应该是这样的。
- 用户登录-通过SignalR建立连接
- 连接信息存储在数据库中
- 连接将根据用户连接的页面和OrganizationId添加到SignalR组
- 用户从UI创建一个新资产
- AddAsset方法在资产服务中调用
- 将资产插入数据库中
- SQLTableDependency检测到更改,然后调用AssetChanged处理程序方法
- AssetChanged处理程序方法调用OnAssetChanged事件
- AssetHub需要订阅OnAssetChanged事件
- 当OnAssetChanged事件被激发时,AssetHub中的处理程序方法需要调用SendAssetToGroup方法
- 当用户从"资产"页面导航到另一个页面时,SignalR连接将从数据库中删除,该连接也将从组中删除
在第9步和第10步之前,一切都在进行中。有没有办法做到这一点,因为SQLTableDependency不在乎是谁做了更改,所以我也无法查找需要推送更新的连接组。有什么想法吗?
当UI使用一个名为:Student 的类时
UI组件加入称为"UI"的组;学生;或";BlahNamespace.Student";。如果它只是一个名称列表,如果它是一个实体,则将名称和另一个ID连接为字符串的组连接起来"BlahNamespace.Student:201";在您的情况下,如果数据库从实体中知道这一点,您也可以附加组织的名称以获得更精细的粒度。
服务器可以根据操作情况根据需要通知组。
我将集线器注入API控制器中以实现这一点。
就我个人而言,我不会使用信号服务来传输数据,保持它的重量轻,只是";信号";改变。然后客户可以决定如何处理。这样,在所有配置的安全性下,只能通过API以一种方式访问数据。