Blazor Service calls returns connection errors occasionally

Viewed 234

I am getting this error occasionally from a Blazor server side .NET Core project.

Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host..

After reload the page or retry to execute same function call one more time there will be no errors and results will be returned

The DbContext definition looks like below:

public class ADPortalDbContext:DbContext
{
   public DbSet<Company> Companies { get; set; }

   public ADPortalDbContext(DbContextOptions<ADPortalDbContext> options)
      : base(options) { }
 }

The Service for returning the results from database looks like below:

public class CompanyService : ICompanyService { private readonly ADPortalDbContext context;

public CompanyService(ADPortalDbContext context)
{
    this.context = context;
}

public async Task<IEnumerable<Company>> GetCompaniesSearchText(string searchText)
{
    try
    {
        return await context.Companies
            .Where(i => EF.Functions.Like(i.Name.ToLower(), $"%{searchText.ToLower()}%"))
            .ToListAsync()
            .ConfigureAwait(false);
    }
    catch(Exception ex)
    {
        throw new InvalidOperationException("Unable to return results " + ex.Message);
    }
} 

Startup.cs of Blazor app is like below:

 services.AddTransient<ICompanyService, CompanyService>();
 services.AddDbContext<ADPortalDbContext>(options =>
                    options.UseMySql(connStr, srvVersion, x =>
                    {
                        x.MigrationsAssembly("DCPortal.Infrastructre");
                    }),
                    contextLifetime: ServiceLifetime.Transient);

The Blazor page calling the service is like below:

@inject ICompanyService  CompanyService

private async Task<IEnumerable<Company>> SearchCompanies(string searchText)
{
try
{
    IEnumerable<Company> companies_ListDb = await CompanyService.GetCompaniesSearchText(searchText);
}
catch(Exception ex)
{
     //Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host..
}
}
1 Answers

The general crux of the issue is the lifetime of the service - a DB context should be pretty much single-use; so in the background the connection is being closed at some point (resulting in your error).

The only real way I've found to get around it is to implement a context factory.

So, in your case, you'll want to create a file in the same area as your context:

public class ADPortalDbContextFactory : IDbContextFactory<ADPortalDbContext>
{
    private readonly DbContextOptions<ADPortalDbContext> options;

    public ADPortalDbContextFactory(DbContextOptions<ADPortalDbContext> contextOptions)
    {
        options = contextOptions;
    }

    public ADPortalDbContext CreateDbContext()
    {
        return new ADPortalDbContext(options);
    }
}

From there, you can put a simple tweak into your startup:

services.AddDbContext<ADPortalDbContext>(options =>
                    options.UseMySql(connStr, srvVersion, x =>
                    {
                        x.MigrationsAssembly("DCPortal.Infrastructre");
                    }));
services.AddDbContextFactory<ADPortalDbContext, ADPortalDbContextFactory>(options =>
                    options.UseMySql(connStr, srvVersion, x =>
                    {
                        x.MigrationsAssembly("DCPortal.Infrastructre");
                    }), ServiceLifetime.Scoped);
services.AddScoped<ICompanyService, CompanyService>();

Your service then becomes:

public class CompanyService : ICompanyService 
{ 
    private readonly IDbContextFactory<ADPortalDbContext> contextFactory;
    
    public CompanyService(IDbContextFactory<ADPortalDbContext> context)
    {
        this.contextFactory = context;
    }

    public async Task<IEnumerable<Company>> GetCompaniesSearchText(string searchText)
    {
        try
        {
            using (var context = contextFactory.CreateDbContext())
            {
                return await context.Companies
                    .Where(i => EF.Functions.Like(i.Name.ToLower(), $"%{searchText.ToLower()}%"))
                    .ToListAsync()
                    .ConfigureAwait(false);
            }
        }
        catch(Exception ex)
        {
            throw new InvalidOperationException("Unable to return results " + ex.Message);
        }
    } 
}
Related