I'm trying to use DBContext in Hosted services but getting this error.
I tried to follow this accepted answer but somehow its not working, I'm not sure what I'm doing wrong.
I'm new to .net please guide me into right direction.
Unhandled Exception: System.InvalidOperationException: Cannot consume
scoped service 'StatusApp.Context.DBContext' from singleton
'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'.
public class TokenService : IHostedService
{
public IConfiguration _Configuration { get; }
protected IMemoryCache _cache;
private Timer _timer;
public IHttpClientFactory _clientFactory;
public DBContext _DBcontext;
private readonly IServiceScopeFactory _scopeFactory;
public TokenService(IConfiguration configuration, IMemoryCache memoryCache, IHttpClientFactory clientFactory, DBContext DBcontext, IServiceScopeFactory scopeFactory)
{
_Configuration = configuration;
_cache = memoryCache;
_clientFactory = clientFactory;
_scopeFactory = scopeFactory;
_DBcontext = _scopeFactory.CreateScope().ServiceProvider.GetRequiredService<DBcontext>();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(getOrg, null, 0, 1000);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
//Timer does not have a stop.
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public async Task getOrg()
{
var request = new HttpRequestMessage(HttpMethod.Get, "organizations");
var response = await _client_NP.SendAsync(request);
var json = await response.Content.ReadAsStringAsync();
OrganizationsClass.OrgsRootObject model = JsonConvert.DeserializeObject<OrganizationsClass.OrgsRootObject>(json);
foreach (var item in model.resources)
{
var g = Guid.Parse(item.guid);
var x = _DBcontext.Organizations.FirstOrDefault(o => o.OrgGuid == g);
if (x == null)
{
_DBcontext.Organizations.Add(new Organizations
{
OrgGuid = g,
Name = item.name,
CreatedAt = item.created_at,
UpdatedAt = item.updated_at,
Timestamp = DateTime.Now,
Foundation = 3
});
}
else if (x.UpdatedAt != item.updated_at)
{
x.CreatedAt = item.created_at;
x.UpdatedAt = item.updated_at;
x.Timestamp = DateTime.Now;
}
}
await _DBcontext.SaveChangesAsync();
}
}
You're almost there, but you've left DBContext as a dependency in TokenService's constructor. Remove that and you'll no longer receive the error.
public TokenService(
IConfiguration configuration,
IMemoryCache memoryCache,
IHttpClientFactory clientFactory,
DBContext DBcontext,
IServiceScopeFactory scopeFactory)
However, you're not quite following the recommendation for dealing with DbContext's in a singleton service. Instead of creating a single instance of DBContext in the constructor and storing it as a field, create a scope and a corresponding DBContext whenever you need it. In your case, that's in the getOrg method.
Follow these steps to achieve that:
Remove the _DBcontext field from your TokenService class:
public DBContext _DBcontext;
Remove the associated assignment from the TokenService constructor:
_DBcontext = _scopeFactory.CreateScope().ServiceProvider.GetRequiredService<DBcontext>();
In getOrg, create a scope, resolve an instance of DBContext and, lastly, dispose of the scope:
public async Task getOrg()
{
var request = new HttpRequestMessage(HttpMethod.Get, "organizations");
var response = await _client_NP.SendAsync(request);
var json = await response.Content.ReadAsStringAsync();
OrganizationsClass.OrgsRootObject model = JsonConvert.DeserializeObject<OrganizationsClass.OrgsRootObject>(json);
using (var scope = _scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<DBcontext>();
foreach (var item in model.resources)
{
var g = Guid.Parse(item.guid);
var x = dbContext.Organizations.FirstOrDefault(o => o.OrgGuid == g);
if (x == null)
{
dbContext.Organizations.Add(new Organizations
{
OrgGuid = g,
Name = item.name,
CreatedAt = item.created_at,
UpdatedAt = item.updated_at,
Timestamp = DateTime.Now,
Foundation = 3
});
}
else if (x.UpdatedAt != item.updated_at)
{
x.CreatedAt = item.created_at;
x.UpdatedAt = item.updated_at;
x.Timestamp = DateTime.Now;
}
}
await dbContext.SaveChangesAsync();
}
}
Instead of using the _DBContext field, the code above creates a local, properly-scoped variable dbContext and uses that instead.
Related
I have an odata query builder class that I am using to build my odata string that is desterilising the result based on the object that called it.
public class UosOdataQueryBuilder<T>
{
private readonly Dictionary<string, string> _queryOptions;
private readonly IHttpClientFactory _clientFactory;
private readonly ILogger _logger;
public UosOdataQueryBuilder([FromServices] IHttpClientFactory clientFactory, [FromServices] ILogger logger)
{
_queryOptions = new Dictionary<string, string>();
_clientFactory = clientFactory;
_logger = logger;
}
public UosOdataQueryBuilder<T> WithFilter(string filter)
{
_queryOptions.Add("$filter", filter);
return this;
}
public UosOdataQueryBuilder<T> Skip(int skip)
{
_queryOptions.Add("$skip", skip.ToString());
return this;
}
public UosOdataQueryBuilder<T> Top(int top)
{
_queryOptions.Add("$top", top.ToString());
return this;
}
public UosOdataQueryBuilder<T> WithNoInlineCount()
{
_queryOptions.Add("$inlinecount", "none");
return this;
}
public UosOdataQueryBuilder<T> OrderBy(string orderBy)
{
_queryOptions.Add("$orderby", orderBy);
return this;
}
public async Task<UosOdataReponse<T>> ExecuteQueryAsync(string elementName = "")
{
var result = new UosOdataReponse<T>();
try
{
var authToken = AppSettings.PlatformBearerToken;
var queryParameters = new List<string>();
foreach (var option in _queryOptions)
queryParameters.Add($"{option.Key}={option.Value}");
var queryParametersCombined = string.Join("&", queryParameters);
var oDataElementName = (elementName == "") ? typeof(T).Name : elementName;
var baseUrl = AppSettings.PlatformBaseUri;
var client = _clientFactory.CreateClient("UOS");
var request = new HttpRequestMessage(
HttpMethod.Get,
new Uri(baseUrl + $"/uos/v4/odata/{oDataElementName}" + queryParametersCombined));
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync();
result = JsonConvert.DeserializeObject<UosOdataReponse<T>>(data);
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
return result;
}
}
I have setup the client in startup
services.AddHttpClient("UOS", c =>
{
c.BaseAddress = new Uri(Configuration.GetValue<string>("PlatformBaseUri") + "uos/v4/");
c.DefaultRequestHeaders.Add("Accept", "application/json");
c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Configuration.GetValue<string>("PlatformBearerToken"));
//c.Timeout = new TimeSpan(0, 0, 30);
});
When I create a new instance of this from another method it is requiring that I pass in the clientFactory and logger.
protected async Task<int> GetUosOdataCount(string filter)
{
var result = new List<T>();
try
{
var countCheck = await new UosOdataQueryBuilder<T>()
.WithFilter(filter)
.Top(1)
.ExecuteQueryAsync();
return countCheck.Count;
}
catch (Exception ex)
{
//CustomLogger.LogError(GetType().FullName, "GetUosOdata", ex.Message);
}
}
In .NET Framework I would remove the parameters from the constructor of the UosOdataQueryBuilder and resolve the dependencies within it. For Example:
_uosUserAttributeRepository = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IUosUserAttributeRepository)) as IUosUserAttributeRepository;
But I am not sure how to achieve in .NET Core. Any suggestions?
You can create an interface for UosOdataQueryBuilder<T> and register it into DI generically. some thing like this:
public interface IUosOdataQueryBuilder<T>
{
Task<T> SomeMethod();
}
public class UosOdataQueryBuilder<T> : IUosOdataQueryBuilder<T>
{
private readonly Dictionary<string, string> _queryOptions;
private readonly IHttpClientFactory _clientFactory;
private readonly ILogger<UosOdataQueryBuilder<T>> _logger;
public UosOdataQueryBuilder(IHttpClientFactory clientFactory, ILogger<UosOdataQueryBuilder<T>> logger)
{
_queryOptions = new Dictionary<string, string>();
_clientFactory = clientFactory;
_logger = logger;
}
public Task<T> SomeMethod()
{
return default;
}
}
And in ConfigureServices in startup write this:
services.AddScoped(typeof(IUosOdataQueryBuilder<>), typeof(UosOdataQueryBuilder<>));
And in your controller inject the IUosOdataQueryBuilder:
private readonly IUosOdataQueryBuilder<YourClass> _uosOdataQueryBuilder;
public YourController( IUosOdataQueryBuilder<YourClass> uosOdataQueryBuilder)
{
_uosOdataQueryBuilder = uosOdataQueryBuilder;
}
you can register IUosOdataQueryBuilder as Singleton but for prevent memory leak you should inject IServiceScopeFactory in concrete class to get registered service in your methods not in constructor.
I'd agree with comment to stick with injection but you can retrieve the dependencies within the class using IServiceProvider
e.g
MyMethod(IServiceProvider serviceProvider)
{
MyService svc = (MyService)serviceProvider.GetService(typeof(MyService));
...
I am having an issue where while looping through a data set and looking in the db to see if it already exists, it works the first run but the second item causes an error.
The following method builds up a List from a raw data file
private async Task<List<Vehicle>> CreateListOfVehiclesFromAuctionDataFile(IEnumerable<string> rows)
{
var cars = new List<Vehicle>();
var vinList = new List<string>();
foreach (var dataRow in rows)
{
var data = dataRow.Split(",");
var dto = GetCarModelInfoFromAuctionData(dataRow);
if (vinList.Contains(data[14]))
{
continue;
}
vinList.Add(data[14]);
var car = new Vehicle
{
Vin = data[14],
InteriorColor = data[15],
ExteriorColor = data[16],
VehicleImageUrl = data[17],
Notes = data[18],
ModelId = await GetModelIdFromCarModelDto(dto)
};
cars.Add(car);
}
return cars;
}
That method calls this method within it
private async Task<int> GetModelIdFromCarModelDto(CarModelDto dto)
{
var modelId =0;
try
{
modelId = await _context.Models.Where(u => u.ModelYear == dto.ModelYear)
.Where(u => u.ModelType == dto.ModelType)
.Where(u => u.BodyStyle.Contains(dto.BodyStyle))
.Select(u => u.ModelId)
.FirstOrDefaultAsync();
}
catch (Exception ex)
{
//log error here
var errorMessage = $"Model id not found: {ex.Message}- Exception: {ex.InnerException}";
}
return modelId;
}
Those private method calls come from this public method
public async Task<int> AddVehicleDataFromAuctionFileAsync()
{
var currentRecords = _context.AuctionDatum.Count();
var data = await AzureDataRetrieval.GetDataAsStreamAsync(AzureService.AzureContainer,
AzureFilePathsFromMain.VehicleAuctionData);
var rows = ConvertAuctionDataStreamToDataArray(data);
**var cars = await CreateListOfVehiclesFromAuctionDataFile(rows);**
var datum = CreateListOfVehicleAuctionData(rows);
await _context.AuctionDatum.AddRangeAsync(datum);
await _context.Vehicles.AddRangeAsync(cars);
await _context.SaveChangesAsync();
return await _context.AuctionDatum.CountAsync() - currentRecords;
}
The main method call comes from the controller
public VehiclesController(CarCollectionDataContext context, IConfiguration configuration)
{
_context = context;
_configuration = configuration;
}
public async Task<IActionResult> Test()
{
var vm = new VehicleDataViewModel(VehicleView.Models);
var mgr = new VehicleDataManager(_context, _configuration);
**var datum = mgr.AddVehicleDataFromAuctionFileAsync();**
vm.Models = await mgr.GetModelListAsync();
return View(vm);
}
The first time through this method it works fine and retrieve's the ModelId. The second time through it fails with the following error message. The where clause parameters, when checked manually, show there is an item that should be retrieved.
Model id not found: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection
and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context,
or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of
disposing context instances.
Object name: 'CarCollectionDataContext'.- Exception:
I am not disposing the context anywhere in the code. The context IS brought in via DI
private readonly CarCollectionDataContext _context;
private readonly IConfiguration _configuration;
public VehicleDataManager(CarCollectionDataContext context, IConfiguration configuration)
{
_context = context;
_configuration = configuration;
AzureService = new AzureService.AzureService(_configuration);
}
I am at the end of my knowledge base on how to resolve this, any help appreciated.
Net core repository pattern. I have below piece of code
public class MapFileUploadBusinessLogic : IMapFileUploadBusinessLogic
{
public MapFileUploadBusinessLogic(IServiceScopeFactory scopeFactory, IOptions<AuthenticationConfig> authenticationConfig, IOptions<ADFClient> AdfClient, IUnitOfWork unitOfWork, IConfiguration configuration, IMapper mapper, ILogger<MapFileUploadBusinessLogic> logger)
{
UnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
CoordinateReferenceSystemRepository = UnitOfWork.GetRepository<CoordinateReferenceSystem>();
this.Logger = logger ?? throw new ArgumentNullException(nameof(logger));
MapUploadRepository = UnitOfWork.GetRepository<MapUpload>();
azureDataFactoryRepository = unitOfWork.GetAzureRepository();
this._authenticationConfig = authenticationConfig.Value;
this._ADFClient = AdfClient.Value;
this.scopeFactory = scopeFactory;
}
public AuthenticationConfig _authenticationConfig { get; set; }
public ADFClient _ADFClient { get; set; }
public IConfiguration Configuration { get; }
private IUnitOfWork UnitOfWork { get; }
private IDbRepository<MapUpload> MapUploadRepository { get; set; }
public async Task UploadMapFile(List<MapFileUploadEntity> mapFileUploadEntities)
{
Dictionary<string, object> parameters = new Dictionary<string, object>
{
{"Parameters", mapFileUploadEntities }
};
DataFactoryManagementClient dataFactoryManagementClient = azureDataFactoryRepository.InitiateConnection(_authenticationConfig);
var result = await azureDataFactoryRepository.RunADFPipeline(dataFactoryManagementClient, parameters, _ADFClient, ApplicationConstants.SciHubPipeline);
await Task.Delay(15000);
ADFPipeLineStatus aDFPipeLineStatus = await azureDataFactoryRepository.GetPipelineInfoAsync(dataFactoryManagementClient, _ADFClient, result);
if(aDFPipeLineStatus.Status == "Succeeded")
{
var mapUploadData = await this.MapUploadRepository.GetAsync(x => mapFileUploadEntities.Any(m => m.MapName == x.MapName)).ConfigureAwait(false);
//push notification
}
else if(aDFPipeLineStatus.Status == "Failed")
{
MapUpload mapUpload = await MapUploadRepository.GetFirstOrDefaultAsync(x => x.MapId == 9);
var mapUploadData = await this.MapUploadRepository.GetAsync(x => mapFileUploadEntities.Any(m => m.MapName == x.MapName)).ConfigureAwait(false);
//push notification
}
}
In the above code when I call await this.MapUploadRepository.GetAsync(x => mapFileUploadEntities.Any(m => m.MapName == x.MapName)).ConfigureAwait(false); it throws
Cannot access a disposed object. A common cause of this error is
disposing a context that was resolved from dependency injection and
then later trying to use the same context instance elsewhere in your
application. This may occur if you are calling Dispose() on the
context, or wrapping the context in a using statement. If you are
using dependency injection, you should let the dependency injection
container take care of disposing context instances.\r\nObject name:
'MyContext'."
Is the Task.Delay making this problem? I have below registration in my startup.cs
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(this.Configuration["AzureSQLConnectionString"]));
services.AddScoped<IUnitOfWork, UnitOfWork>();
Can someone help me to understand what I am doing wrong here? Any help would be appreciated. Thanks
I'm writing a test with xunit on .NET Core 3.0 and I have a problem with the in-memory database. I need a separate database for each test but now I create a single database that causes problems, but I have no idea how to create a new database for each test.
public class AccountAdminTest : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private IServiceScopeFactory scopeFactory;
private readonly CustomWebApplicationFactory<Startup> _factory;
private ApplicationDbContext _context;
public AccountAdminTest(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
var scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<ApplicationDbContext>();
}
}
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<ApplicationDbContext>));
if (descriptor != null)
{
services.Remove(descriptor);
}
services.AddDbContext<ApplicationDbContext>((options, context) =>
{
context.UseInMemoryDatabase("IdentityDatabase");
});
});
}
}
Now it's look like this but still dosen't work. When i change lifetime on AddDbContext it doesn't change anything.
public class AccountAdminTest : IDisposable
{
public AccountAdminTest(ITestOutputHelper output)
{
this.output = output;
_factory = new CustomWebApplicationFactory<Startup>();
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
_context = _scope.ServiceProvider.GetService<ApplicationDbContext>();
var _user = User.getAppAdmin();
_context.Add(_user);
_context.SaveChanges(); //Here i got error on secound test. It says "An item with the same key has already been added"
}
public void Dispose()
{
_scope.Dispose();
_factory.Dispose();
_context.Dispose();
_client.Dispose();
}
I can't get token when use Guid as db name. It says that username/password is not valid. I use IdentityServer for authentication
public async Task<string> GetAccessToken(string userName, string password, string clientId, string scope)
{
var disco = await _client.GetDiscoveryDocumentAsync("https://localhost:44444");
if (!String.IsNullOrEmpty(disco.Error))
{
throw new Exception(disco.Error);
}
var response = await _client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = clientId,
Scope = scope,
UserName = userName,
Password = password,
});
return response.AccessToken;
}
All you need to change is this code here:
services.AddDbContext<ApplicationDbContext>((options, context) =>
{
context.UseInMemoryDatabase("IdentityDatabase");
});
Instead of the constant value "IdentityDatabase", use something like Guid.NewGuid().ToString():
context.UseInMemoryDatabase(Guid.NewGuid().ToString());
Then, every time the context is fetched, it will be using a new in-memory database.
Edit: You must specify an unique name for every test run to avoid sharing the same InMemory database as slightly mentioned here and already answered here and here.
Nonetheless the suggestion below to switch to "Constructor and Dispose" still applies.
IClassFixture is not the right tool to use, the documentation says:
When to use: when you want to create a single test context and share it among all the tests in the class, and have it cleaned up after all the tests in the class have finished.
In your case you have to use "Constructor and Dispose", the documentation says:
When to use: when you want a clean test context for every test (sharing the setup and cleanup code, without sharing the object instance).
So your test will be:
public class AccountAdminTest
{
private readonly HttpClient _client;
private IServiceScopeFactory scopeFactory;
private readonly CustomWebApplicationFactory<Startup> _factory;
private ApplicationDbContext _context;
public AccountAdminTest(CustomWebApplicationFactory<Startup> factory)
{
//
_factory = new CustomWebApplicationFactory<Startup>();
_client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = new Uri("https://localhost:44444")
});
scopeFactory = _factory.Services.GetService<IServiceScopeFactory>();
_scope = scopeFactory.CreateScope();
_context = scope.ServiceProvider.GetService<ApplicationDbContext>();
}
//Tests...
public void Dispose() {
_scope.Dispose();
_factory.Dispose();
//Dispose and cleanup anything else...
}
}
Alternatively you can specify a lifetime of ServiceLifetime.Transient for your DbContext using this overload of .AddDbContext
This question already has answers here:
The best overloaded method match for System.Threading.Timer.Timer() has some invalid arguments
(3 answers)
Closed 3 years ago.
I'm trying to run function getOrg though hosted services but some how its not working I'm not sure what I'm doing wrong.
Error:
Argument 1: cannot convert from 'method group' to 'TimerCallback'
(CS1503)
public class TokenService : IHostedService
{
public IConfiguration _Configuration { get; }
protected IMemoryCache _cache;
private Timer _timer;
public IHttpClientFactory _clientFactory;
private readonly IServiceScopeFactory _scopeFactory;
public TokenService(IConfiguration configuration, IMemoryCache memoryCache, IHttpClientFactory clientFactory, IServiceScopeFactory scopeFactory)
{
_Configuration = configuration;
_cache = memoryCache;
_clientFactory = clientFactory;
_scopeFactory = scopeFactory;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(getOrg, null, 0, 1000); // getting error here
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
//Timer does not have a stop.
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public async Task getOrg()
{
var request = new HttpRequestMessage(HttpMethod.Get, "organizations");
var response = await _client_NP.SendAsync(request);
var json = await response.Content.ReadAsStringAsync();
OrganizationsClass.OrgsRootObject model = JsonConvert.DeserializeObject<OrganizationsClass.OrgsRootObject>(json);
using (var scope = _scopeFactory.CreateScope())
{
var _DBcontext = scope.ServiceProvider.GetRequiredService<DBContext>();
foreach (var item in model.resources)
{
var g = Guid.Parse(item.guid);
var x = _DBcontext.Organizations.FirstOrDefault(o => o.OrgGuid == g);
if (x == null)
{
_DBcontext.Organizations.Add(new Organizations
{
OrgGuid = g,
Name = item.name,
CreatedAt = item.created_at,
UpdatedAt = item.updated_at,
Timestamp = DateTime.Now,
Foundation = 3
});
}
else if (x.UpdatedAt != item.updated_at)
{
x.CreatedAt = item.created_at;
x.UpdatedAt = item.updated_at;
x.Timestamp = DateTime.Now;
}
}
await getSpace();
await _DBcontext.SaveChangesAsync();
}
}
}
TimerCallback takes on object parameter for state. Try changing getOrg to:
public async void getOrg(object state)
You are providing wrong parameters to System.Threading.Timer constructor.
The first parameter should be a delegate type (instead of getOrg):
public delegate void TimerCallback(object state);
So add a delegate to your code:
private void TimerProc(object state)
{
}
Change the constructor:
_timer = new Timer(TimerProc, null, 0, 1000); // getting error here