AddPooledDbContextFactory with DbContext that uses custom DbConnection: service not registered - asp.net-core

I have a custom DbContext SnowflakeDbContext that I need to initialize with a SnowflakeDbConnection for it to work:
public class SnowflakeDbContext : DbContext
{
private readonly string connectionString = "";
public SnowflakeDbContext(DbContextOptions<SnowflakeDbContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
var dbConnection = new SnowflakeDbConnection()
{
ConnectionString = this.connectionString
};
optionsBuilder.UseSqlServer(dbConnection);
optionsBuilder.AddInterceptors(new SnowflakeCommandInterceptor());
}
public DbSet<Opportunity> Opportunities { get; set; } = default!;
public DbSet<Account> Accounts { get; set; } = default!;
}
This works well with EF Core 5, were in Startup.cs (I am using an ASP.NET Core 5 web application) I use
.AddDbContext<SnowflakeDbContext>(ServiceLifetime.Singleton)
I want to use the SnowflakeDbContext with HotChocolate where it is recommended that I use AddPooledDbContextFactory<> in order to support pooling of connections and allowing the system to make simultaneous calls (described here).
I have modified Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services
.AddPooledDbContextFactory<SnowflakeDbContext>(options =>
{
var dbConnection = new SnowflakeDbConnection()
{
ConnectionString = this.connectionString
};
options.UseSqlServer(dbConnection);
options.AddInterceptors(new SnowflakeCommandInterceptor());
})
.AddGraphQLServer()
.AddQueryType<Query>();
}
Using the following GraphQL query (which uses parallel queries):
query GetAccountsInParallel {
a: accounts {
id, name
}
b: accounts {
id, name
}
c: accounts {
id, name
}
}
I get the following error:
"No service for type 'SnowflakeGraphQL.Snowflake.SnowflakeDbContext' has been registered.",
I can add
.AddDbContext<SnowflakeDbContext>()
in Startup.cs after the call to .AddPooledDbContextFactory<>. Now I get a different error:
"A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext."
All of the examples I have seen on the web use .UseSqlServer(connectionString) where as I need to use the .UseSqlServer(dbConnection) version in order to be able to access our Snowflake database.
How do I configure my application in Startup.cs to use .AddPooledDbContextFactory()?
Update: Starting with the graphql-workshop code and replacing Sqlite with first SqlServer and then SqlServer using my SnowflakeDbContext I get it to work, so there must be a subtle difference somewhere in my code as described above that results in a failure in my case.

When retrieving the accounts records, we need to use the [ScopedService] rather than the [Service] like this:
[UseApplicationDbContext]
public async Task<List<Account>> GetAccounts([ScopedService] SnowflakeDbContext context) => await context.Accounts.ToListAsync();

Related

how to handle DbContext lifetime in API / backgroundworker

the past days I have been struggling with injecting a DbContext in MY background worker. On the one hand, I want to inject the dbContext in my backgorund worker, but on the other hand I also want to use it in my API.
The injecting in my API seems to work fine, but since my worker is a singleton, I can not follow the standard lifetime of scoped for my dbcontext, and I have to add it as transient.
I have already tried to create a unit of work, in which I can refresh the context myself in my worker, effectively creating some kind of scoped service. I would refresh the context every time the worker went through his loop once again. This worked, and the application was working as I wanted, but I was no longer able to properly test, since I would create a new DbContext myself in the code. I feel like there must be a better way for to handle this.
My project structure looks like the following:
API => contains controlers + models I use for post requests.
The API project needs to use my database, to get and post data. It uses the repositories for this
Core (class library) => contains some core models
Domain(class library) => Contains my domain models + repositories.
All database work goes through here
Worker => Contains some logic.
The worker needs to use my database, to get and post data. It uses the repositories for this
Services (class library) => Some services that contain some logic.
The worker uses my repositories to get to the database
Tests => Tests for all code.
I want to be able to to integrationTesting as well here.
I currently inject all repositories and services in both my API and worker:
Worker configureservices:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddDbContext<CatAPIDbContext>(ServiceLifetime.Transient);
services.AddTransient(typeof(IFeedingProfileRepository), typeof(FeedingProfileRepository));
services.AddTransient(typeof(IFeedingTimesRepository), typeof(FeedingTimesRepository));
services.AddTransient(typeof(IFeedHistoryRepository), typeof(FeedHistoryRepository));
services.AddTransient(typeof(IMotorController), typeof(MotorController));
services.AddTransient(typeof(IFoodDispenser), typeof(FoodDispenser));
services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
services.AddTransient(typeof(IFeedingTimeChecker), typeof(FeedingTimeChecker));
services.AddHostedService<Worker>();
});
(EDIT)Worker code:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public IFeedingTimeChecker _feedingTimeChecker { get; }
public Worker(ILogger<Worker> logger, IFeedingTimeChecker feedingTimeChecker)
{
_logger = logger;
_feedingTimeChecker = feedingTimeChecker;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
_feedingTimeChecker.ResetFeedingTimesGivenIfNeeded();
_feedingTimeChecker.FeedIfNeeded();
}
catch(Exception ex)
{
_logger.LogError(ex.ToString());
}
await Task.Delay(10000, stoppingToken);
}
}
}
(EDIT)FeedingTimeChecker (called by worker)
private FeedingProfile _currentProfile { get; set; }
public DateTime lastResetDataFeedingTimes;
public DateTime lastProfileRefresh;
private readonly ILogger<FeedingTimeChecker> _logger;
private IFeedingProfileRepository _feedingProfileRepository { get; set; }
private IFeedingTimesRepository _feedingTimesRepository { get; set; }
private IFoodDispenser _foodDispenser { get; }
public FeedingTimeChecker(IFeedingProfileRepository feedingProfileRepository, IFeedingTimesRepository feedingTimesRepository,IFoodDispenser foodDispenser, ILogger<FeedingTimeChecker> logger)
{
lastResetDataFeedingTimes = DateTime.MinValue.Date;
lastProfileRefresh = DateTime.MinValue.Date;
_foodDispenser = foodDispenser;
_logger = logger;
_feedingTimesRepository = feedingTimesRepository;
_feedingProfileRepository = feedingProfileRepository;
}
public void UpdateCurrentProfile()
{
if(Time.GetDateTimeNow - TimeSpan.FromSeconds(5) > lastProfileRefresh)
{
_logger.LogInformation("Refreshing current profile");
_currentProfile = _feedingProfileRepository.GetCurrentFeedingProfile();
lastProfileRefresh = Time.GetDateTimeNow;
}
}
API configureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddDbContext<CatAPIDbContext>();
services.AddTransient(typeof(IFeedingProfileRepository), typeof(FeedingProfileRepository));
services.AddTransient(typeof(IFeedingTimesRepository), typeof(FeedingTimesRepository));
services.AddTransient(typeof(IFeedHistoryRepository), typeof(FeedHistoryRepository));
services.AddTransient(typeof(IMotorController), typeof(MotorController));
services.AddTransient(typeof(IFoodDispenser), typeof(FoodDispenser));
services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
}
in my repositories I use the dbContext like the following:
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
public CatAPIDbContext _dbContext { get; set; }
public GenericRepository(CatAPIDbContext dbContext)
{
_dbContext = dbContext;
}
public T GetById(object id)
{
return _dbContext.Set<T>().Find(id);
}
}
The result I would expect, is for my worker and API to behave correctly, always getting the lastest data and disposing of the dbContext on every single request, since I use a transient lifetime for my dbContext.
However, in my worker, I always get the following error:
The instance of entity type 'FeedingTime' cannot be tracked because another instance another instance of this type with the same key is already being tracked.
This error occurs when I try to set a column in the FeedingTime table.
A feedingProfile has 0-many feedingTimes, and the feedingProfile constantly retrieved.
Any solution where I can keep a testable clean codebase, but yet not run into this problem would be very welcome.
Thanks in advance

No parameterless constructor defined for this object in asp.netcore migrations

I am new to ASP.NET Core. learning new version of .NET Core 2.0 using VS Code. I got stuck while doing creating database using migration. First, it gives an exception of implementation of IDesignTimeDbContextFactory. After solving this, it still gives an exception of
No parameterless constructor defined for this object
Here's my code for DbContextClass:
public VegaDbContext CreateDbContext(string[] args)
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var builder = new DbContextOptionsBuilder<VegaDbContext>();
var connectionString =
configuration.GetConnectionString("DefaultConnection");
builder.UseSqlServer(connectionString);
return new VegaDbContext(builder.Options);
}
I had tried a couple of ways when I was experimenting with ef core. I faced similar issues too. Finally I found services working great. First you will need to create your DBContext with the following override constructor:
public VegaDbContext(DbContextOptions<VegaDbContext> options) : base(options)
{
}
In your start up, you can add your context as a service like this:
services.AddDbContext<ApplicationDBContext>(config => {
config.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
You can read in full detail about how dependency injection works here:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection
This part should help you with the migration. You can perform your migrations using the dotnet ef commands https://learn.microsoft.com/en-us/ef/core/miscellaneous/cli/dotnet.
When using your db context, do ensure that you are using dependency injection so you make full use of the AddDbContext function and keep it DRY.
https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro
If I were your in your shoes, I look at this document.
Here is the simple DbContext that you can find on this webSite
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
I just got the same error. If you are careful the error description is actually giving you the solution of the problem.
  DesignTimeFactoryObject's constructor function should not take parameters.
public class ExampleDesignTimeFactory : IDesignTimeDbContextFactory<YourDBContext>{
public ExampleDesignTimeFactory(){ no constructor or no parameter constructor }
}
I use ASP.NET CORE 3.1 to create the project and it solved

Creating a database context using the database first approach with entityframework core.

I want to be able to create a database context with entityframework core in my webapi project using the database first approach.
When I create like this it works very well
public class TestingContext : DbContext
{
public TestingContext(DbContextOptions<TestingContext> options)
: base(options)
{
}
public TestingContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=xxxxxx;Initial Catalog=xxxxxx;Integrated Security=False;User Id=xxxxx;Password=xxxxx;MultipleActiveResultSets=True");
}
public DbSet<Information> Information { get; set; }
public DbSet<ArticleUser> ArticleUser { get; set; }
}
I had to add the line services.AddDbContext to make it work.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddCors();
//using Dependency Injection
services.AddSingleton<Ixxx, xxx>();
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddDbContext<TestingContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
// Register the Swagger generator, defining one or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Articles API", Version = "v1" });
});
}
If I remove this method from my TestingContext
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=xxxxxx;Initial Catalog=xxxxxx;Integrated Security=False;User Id=xxxxx;Password=xxxxx;MultipleActiveResultSets=True");
}
I get the error below.
No database provider has been configured for this DbContext.
A provider can be configured by overriding the DbContext.OnConfiguring method or
by using AddDbContext on the application service provider. If AddDbContext is used,
then also ensure that your DbContext type accepts a DbContextOptions object in its
constructor and passes it to the base constructor for DbContext.
Why do I need to pass my connection string to the database in two places before it can pull my data. Please assist. I am new to the core. The two places are configure services method and the context itself.
Option 1: Remove parameterized constructor and OnConfiguring. Result:
public class TestingContext : DbContext
{
public DbSet<Information> Information { get; set; }
public DbSet<ArticleUser> ArticleUser { get; set; }
}
Option 2: Remove parameterized constructor and options in ConfigureServices in AddDbContext
Result:
In Startup.cs
services.AddDbContext<TestingContext>();
In TestingDbContext.cs
public class TestingDdContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=xxxxxx;Initial Catalog=xxxxxx;Integrated Security=False;User Id=xxxxx;Password=xxxxx;MultipleActiveResultSets=True");
}
public DbSet<Information> Information { get; set; }
public DbSet<ArticleUser> ArticleUser { get; set; }
}
Option 3: A parametric constructor is needed to create factory. Example:
public class TestDdContext : DbContext
{
public TestDdContext(DbContextOptions options) : base(options)
{
}
//TODO: DbSets
}
public class TestDbContextFactory : IDbContextFactory<TestDdContext>
{
public TestDdContext Create(DbContextFactoryOptions options)
{
var contextOptions = new DbContextOptionsBuilder();
contextOptions.UseSqlServer("...");
return new TestDdContext(contextOptions.Options);
}
}
If you are creating tests, do you need a backing Sql database? Would the In-memory provider not serve you better?
options.UseInMemoryDatabase("database-name");
For this reason, I'd ditch using the OnConfiguring method, and rely on passing the DbContextOptions to your constructor
Side note, you have to consider what you are testing - are you testing your code that is dependent on your DbContext, or are you testing your DbContext itself - if there is no custom logic and you are merely extending the DbContext, there may not be enough value in writing tests for it - and you're not responsible for testing EFCore itself.

looking for samples on how to user services.add* in asp.vnext

I would like to know where can I find samples the explains the differences among services.AddInstance, services.AddScoped, services.AddSingleton and service.AddTransient.
I found some articles that explain the point in a generic way, but I think a source sample is much more clear.
The scope of this questions is rather large, but since it seems you are specifically looking for AddScoped information I narrowed the sample down to scoping inside a web application.
Inside a web application AddScoped will mean pretty much the scope of the request. EntityFramework is using scoping internally, but it doesn't affect the user code in most cases so I'm sticking with the user code as shown below.
If you register a DbContext as a service, and also register a scoped service, for each request you will get a single instance of the scoped service where you resolve the DbContext.
The example code below should make it clearer. In general I would recommend just trying it out the way I'm showing it below to familiarize yourself with the behavior, by stepping through the code in the debugger. Start from an empty web application. Note the code I'm showing is from Beta2 (since in Beta2 we added the [FromServices] attribute which makes it easier to demonstrate, the underlying behavior is the same regardless of version.
startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add EF services to the services container.
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<UserDbContext>();
services.AddScoped<UserService>();
// Add MVC services to the services container.
services.AddMvc();
}
UserDbContext.cs
public class UserDbContext : DbContext
{
public UserService UserService { get; }
public UserDbContext(UserService userService)
{
_userService = userService;
}
}
HomeController.cs
public class HomeController : Controller
{
private UserDbContext _dbContext;
public HomeController(UserDbContext dbContext)
{
_dbContext = dbContext;
}
public string Index([FromServices]UserDbContext dbContext, [FromServices]UserService userService)
{
// [FromServices] is available start with Beta2, and will resolve the service from DI
// dbContext == _ctrContext
// and of course dbContext.UserService == _ctrContext.UserService;
if (dbContext != _dbContext) throw new InvalidOperationException();
if (dbContext.UserService != _dbContext.UserService) throw new InvalidOperationException();
if (dbContext.UserService != userService) throw new InvalidOperationException();
return "Match";
}
}
Alternatively if you resolve the user service from another service, this time registered as transient the transient service will have a new instance everytime it is resolved, but the scoped service will remain the same within the scope of the request.
Create the new service
public class AnotherUserService
{
public UserService UserService { get; }
public AnotherUserService(UserService userService)
{
UserService = userService;
}
}
Add the following lines to startup.cs
services.AddTransient<AnotherUserService>();
And rewrite the HomeController.cs as follows
public class HomeController : Controller
{
private AnotherUserService _anotherUserService;
public HomeController(AnotherUserService anotherUserService)
{
_anotherUserService = anotherUserService;
}
public string Index([FromServices]AnotherUserService anotherUserService,
[FromServices]UserService userService)
{
// Since another user service is tranient we expect a new instance
if (anotherUserService == _anotherUserService)
throw new InvalidOperationException();
// but the scoped service should remain the same instance
if (anotherUserService.UserService != _anotherUserService.UserService)
throw new InvalidOperationException();
if (anotherUserService.UserService != userService)
throw new InvalidOperationException();
return "Match";
}
}

Does WCF OData Service Support Projection?

I'm using WCF OData service as my application Data Provider.OData service expose a entity that I don't want to get whole entity,I create LINQ query to get projection from this Entity.
But i have error in OData Service.This is my code:
from n in NewsInfos
select new NewsInfos
{
n.NewsId,
n.NewsTitle,
n.NewsLead,
n.NewsDate
};
This is entire code:
[System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class NewsDataService : DataService<NewsODataModel>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
config.DataServiceBehavior.AcceptProjectionRequests = true;
}
}
Yes, WCF Data Services and OData support projection. Projection is codified in the URL with the $select system query option, e.g.: http://services.odata.org/Experimental/OData/OData.svc/Products?$select=Name&$format=json. The LINQ Provider in the client bits enable this similarly to what you've shown in your example. Here is one such example:
using System;
using System.Data.Services.Client;
using System.Linq;
namespace Scratch
{
public class Program
{
public static void Main()
{
var context = new DataServiceContext(new Uri("http://services.odata.org/OData/OData.svc/"));
var categories = context.CreateQuery<Category>("Categories").Select(c => new { c.Name });
Console.WriteLine("context.Categories.Where(...): {0}", categories);
foreach (var category in categories)
{
Console.WriteLine(category.Name);
}
}
}
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
}
}
One thing to consider with projection is that the magic in our client-side bits frequently requires you to use anonymous objects (hence the new { c.Name }).
Your error may be unrelated; if you're still getting the error after reading this can you update your service to return verbose errors as per http://blogs.msdn.com/b/phaniraj/archive/2008/06/18/debugging-ado-net-data-services.aspx? My guess is that you may be missing the [DataServiceKey] attribute on NewsInfos.
Just return an anonymous object from your select and it should work.
from n in NewsInfos
select new
{
n.NewsId,
n.NewsTitle,
n.NewsLead,
n.NewsDate
};