将SignalR用户映射到使用SQL Server的连接

4
我阅读了有关映射SignalR用户到连接的 主题。简单地说,该主题介绍了四种映射方法,而我想使用第四种方法(永久、外部存储)。该方法使用SQL Server数据库来存储客户端连接时的 ConnectionId (当触发 OnConnected 方法时),并在客户端关闭浏览器(当触发 OnDisconnected 方法时)时仅使 ConnectionId 无效。
以下是数据库的代码:
public class UserContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Connection> Connections { get; set; }
}

public class User
{
    [Key]
    public string UserName { get; set; }
    public ICollection<Connection> Connections { get; set; }
}

public class Connection
{
    public string ConnectionID { get; set; }
    public string UserAgent { get; set; }
    public bool Connected { get; set; }
}

以下是中间件类的代码:

[Authorize]
public class ChatHub : Hub
{
    public void SendChatMessage(string who, string message)
    {
        var name = Context.User.Identity.Name;
        using (var db = new UserContext())
        {
            var user = db.Users.Find(who);
            if (user == null)
            {
                Clients.Caller.showErrorMessage("Could not find that user.");
            }
            else
            {
                db.Entry(user)
                    .Collection(u => u.Connections)
                    .Query()
                    .Where(c => c.Connected == true)
                    .Load();

                if (user.Connections == null)
                {
                    Clients.Caller.showErrorMessage("The user is no longer connected.");
                }
                else
                {
                    foreach (var connection in user.Connections)
                    {
                        Clients.Client(connection.ConnectionID)
                            .addChatMessage(name + ": " + message);
                    }
                }
            }
        }
    }

    public override Task OnConnected()
    {
        var name = Context.User.Identity.Name;
        using (var db = new UserContext())
        {
            var user = db.Users
                .Include(u => u.Connections)
                .SingleOrDefault(u => u.UserName == name);

            if (user == null)
            {
                user = new User
                {
                    UserName = name,
                    Connections = new List<Connection>()
                };
                db.Users.Add(user);
            }

            user.Connections.Add(new Connection
            {
                ConnectionID = Context.ConnectionId,
                UserAgent = Context.Request.Headers["User-Agent"],
                Connected = true
            });
            db.SaveChanges();
        }
        return base.OnConnected();
    }

    public override Task OnDisconnected(bool stopCalled)
    {
        using (var db = new UserContext())
        {
            var connection = db.Connections.Find(Context.ConnectionId);
            connection.Connected = false;
            db.SaveChanges();
        }
        return base.OnDisconnected(stopCalled);
    }
}

我希望改进这个方法,因为使用这个方法会创建许多不需要的ConnectionIds。此外,使用这个方法将导致Connections表随着时间的推移变得越来越大而没有任何用处。


你需要维护多个连接ID吗?你是否需要支持“一个用户同时在两台或更多机器上连接”的要求? - Viktor Bahtev
在我的应用程序中,用户可能会使用多个浏览器或多个设备,因此我有一个要求,即支持同时使用两台或更多机器。 - Ahmed Shamel
1个回答

2
清理连接ID而不是将Connected属性设置为false怎么样?您的OnDisconnected()方法可能看起来像这样:
public override Task OnDisconnected(bool stopCalled)
{
    using (var db = new UserContext())
    {
        var connection = db.Connections.Find(Context.ConnectionId);
        db.Connections.Remove(connection);
        db.SaveChanges();
    }
    return base.OnDisconnected(stopCalled);
}

这将停止Connections表无限增长。然后,您可以完全删除Connected属性,并让Connection行的存在表示连接处于活动状态。

更新

如果您不想删除记录而是要重新使用已经存在于数据库中的记录,则另一种方法可能如下:

public override Task OnConnected()
{
    var name = Context.User.Identity.Name;
    using (var db = new UserContext())
    {
        var user = db.Users
            .Include(u => u.Connections)
            .SingleOrDefault(u => u.UserName == name);

        if (user == null)
        {
            user = new User
            {
                UserName = name,
                Connections = new List<Connection>()
            };
            db.Users.Add(user);
        }

        var connection = user.Connections.Where(c => c.Connected == false && UserAgent == Context.Request.Headers["User-Agent"]).FirstOrDefault();
        if (connection == null) 
        {
             connection = new Connection();
             connection.UserAgent = Context.Request.Headers["User-Agent"];
             user.Connections.Add(connection);
        }

        connection.ConnectionID = Context.ConnectionId;
        connection.Connected = true;
        db.SaveChanges();
    }
    return base.OnConnected();
}

这符合您的想法吗?

这与您想要的类似吗?


谢谢你的回答,我给你点赞。但是为什么你要删除记录呢?如果在onConnect触发时记录已经存在,那么更新记录怎么样? - Ahmed Shamel
谢谢Ahmed。我建议删除记录,以避免表格无限增长,因为我认为这是您的要求。如果仅更新记录,则表格将继续增长,因为每次浏览器通过SignalR连接时都会生成一个新的唯一connectionId。 OnConnected()每个连接只会触发一次,因此我认为没有必要在那里更新任何内容-唯一需要做的就是添加有关新连接的信息。 - lennartk
我的意思是在OnConnected事件中有两种情况。第一种情况是用户在数据库中没有连接,这种情况下我们将在数据库中创建新记录。第二种情况是用户在数据库中已经连接,但连接的值为false,在这种情况下,我们将更新ConnectionID和Connected的值。我希望这解释清楚了我在前面的评论中所说的内容。 - Ahmed Shamel
我想我明白你的意思。我不确定重复使用现有记录而不是删除并创建新记录的收益是否如此巨大,但如果您有高流量场景,那么可能是这样。我已经更新了我的答案,其中包括对连接进行重新使用的实现,这些连接的Connected属性为false。你觉得怎么样? - lennartk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接