Blazor + SQLTableDependency + SignalR:从OnChange事件通知特定组

问题描述

我有一个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集线器

 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();
    }

因此流程应如下所示。

  1. 用户登录-通过SignalR建立连接
  2. 连接信息存储在数据库中。
  3. 根据用户从哪个页面和OrganizationId将连接添加到SignalR组。
  4. 用户用户界面创建新资产。
  5. 在资产服务中调用AddAsset方法
  6. 资产被插入到数据库中。
  7. sqlTableDependency检测到更改,然后调用AssetChanged处理程序方法
  8. AssetChanged处理程序方法调用OnAssetChanged事件。
  9. AssetHub需要预订OnAssetChanged事件。
  10. 触发OnAssetChanged事件时,AssetHub中的处理程序方法需要调用SendAssetToGroup方法
  11. 用户从“资产”页面导航到另一个页面时,SignalR连接将从数据库删除,并且该连接也从组中删除

直到第9步和第10步,我的所有工作都可以进行。由于sqlTableDependency不在乎谁进行了更改,因此是否有其他方法可以使之成为可能,所以我无法查找需要更新的连接组。也被推。有什么想法吗?

解决方法

当用户界面正在使用一个类时,例如:Student

UI组件加入了一个名为“ Student”或“ BlahNamespace.Student”的组。 如果其列表仅是名称,则如果其实体将名称和另一个ID作为字符串连接的组连接在一起 “ BlahNamespace.Student:201”如果您的数据库知道该实体的信息,那么您也可以附加组织的名称以获取更好的信息。

服务器可以根据操作通知组。

我将集线器注入API控制器以实现这一目标。

我个人不会使用信号发送器服务来传输数据,保持它的重量轻,而只是“通知”更改。然后,客户可以决定如何处理。这样一来,只有通过所有已配置的安全性,才能通过API以一种方式访问​​数据。