首页 > 解决方案 > SignalR and Database Context Disposals

问题描述

I have been trying to get SignalR to start a database query and then depending on the result of said query respond to the Client that initiated it.

But I have been running into Cannot access a disposed object. from both the DatabaseContext and HubContext. I have tried several different methods from creating a Database controller singleton amoung others, none of which worked.

I have finally managed to create a solution but am now wondering if I am creating too many instances/contexts of the Database context.

Here is the method I have come up with. (Simplified)

[Authorize(JwtBearerDefaults.AuthenticationScheme)]
public class Game : Hub
{
    private GameContext _dbContext;
    private IHubContext<Game> _hubContext;

    public Game(IHubContext<Game> hubContext)
    {
        _hubContext = hubContext;
    }

    public override async Task OnConnectedAsync()
    {
        await Clients.Client(Context.ConnectionId).SendAsync("DebugMessage", "Connected to Hub");
        await base.OnConnectedAsync();
    }

    public async void NewUser(string username, string userIdentifier)
    {
        _dbContext = new GameContext { };
        string connectionID = Context.ConnectionId;

        bool error = false;
        bool usernameExists = false;
        bool createdUser = false;

        try
        {
            var rows = await _dbContext.Users.Where(x => x.Username == username).ToListAsync();
            if (rows.Count > 0)
            {
                usernameExists = true;
                goto SendResponse;
            }
        }
        catch (Exception e)
        {
            error = true;
            goto SendResponse;
        }

        Users user = new Users { Username = username, UserIdentifier = userIdentifier, Joined = DateTime.UtcNow };
        try
        {
            _dbContext.Add(user);
            await _dbContext.SaveChangesAsync();
            createdUser = true;
            goto SendResponse;
        }
        catch (Exception e)
        {
            error = true;
            goto SendResponse;
        }

        SendResponse:
            await _hubContext.Clients.Client(connectionID).SendAsync("CreatedUser", username, usernameExists, createdUser, error);
    }

}

As you can see in the NewUser function I am creating a new instance/context to get it too work.

It feels like it's wasteful so have made a Singleton pattern for it in the Game Hub to check if its null and create a new one if needed as follows.

private Object padlock = new Object { };
private GameContext _dbContextInstance;
private GameContext _dbContext
{
    get
    {
        if (_dbContextInstance == null)
        {
            lock (padlock)
            {
                if (_dbContextInstance == null)
                {
                    _dbContextInstance = new GameContext { };
                }
            }
        }
        return _dbContextInstance;
    }
}

But it still feels wasteful as I am continually making the same thing over and over when a User is making many calls it will add up so before I continue would like to know if I am going down the wrong route with this and if so what route i should be taking.

Thanks

标签: c#databasesignalr

解决方案


您的代码应如下所示

// this has to be a task, otherwise the framework
// cannot know when it has finished 
public async Task NewUser(string username, string userIdentifier)
{
    using (var dbContext = new GameContext())
    {
        string connectionID = Context.ConnectionId;

        bool error = false;
        bool usernameExists = false;
        bool createdUser = false;

        try
        {
            usernameExists  = await dbContext.Users.AnyAsync(x=> x.Username == username);
        }
        catch (Exception e)
        {
            error = true;
        }

        if (!usernameExists && !error)
        {
            Users user = new Users { Username = username, UserIdentifier = userIdentifier, Joined = DateTime.UtcNow };
            try
            {
                dbContext.Add(user);
                await dbContext.SaveChangesAsync();
                createdUser = true;
            }
            catch (Exception e)
            {
                error = true;
            }
        }

        await _hubContext.Clients.Client(connectionID).SendAsync("CreatedUser", username, usernameExists, createdUser, error);
    }
}

在 C# 中使用标签并不常见。您应该让框架负责如何管理连接(唯一重要的事情),而这可以通过正确使用using语句来完成。


推荐阅读