Will the dbcontext dispose and close if using adddbcontext this way? - .net-core

Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TestContext>(options => options.UseSqlServer("dbconnection"));
}
TestController.cs
private readonly TestContext _db;
public TestController(TestContext db)
{
_db = db;
}
public void Get()
{
DALUser dal = new DALUser(_db);
var list = dal.Get();
}
DALUser.cs
private readonly TestContext _db;
public DALUser(TestContext db)
{
_db = db;
}
public IQueryable<User> Get()
{
_db.Users.AsQueryable();
}
Will the dbcontext dispose and close after executing Get() function?

Default lifetime for DatabaseContext is scoped . So in your web application , DatabaseContext disposed after your http request finished ( TestController-Get ) .
public static IServiceCollection AddDbContext<TContext>(
[NotNull] this IServiceCollection serviceCollection,
[CanBeNull] Action<DbContextOptionsBuilder> optionsAction = null,
ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
where TContext : DbContext

Related

Asp.Net Core Web API entity Framework connect to two databases

I am doing an Asp.Net Core API and I am connecting to a two databases using EF setted in appsettings.json
"ConnectionStrings": {
"DBConnection": "Server=2679; Database=A; Trusted_Connection=true; MultipleActiveResultSets=true; Integrated Security=true;Encrypt=false;",
"DBConnection2": "Server= 2684; Database=B; Trusted_Connection=true; MultipleActiveResultSets=true; Integrated Security=true;Encrypt=false;"
}
In my Program.cs I have setted this two connections
var connectionString = (builder.Configuration.GetConnectionString("DBConnection") ?? String.Empty).Trim();
var connectionString2 = (builder.Configuration.GetConnectionString("DBConnectionAnthem") ?? String.Empty).Trim();
builder.Services.ConfigureServices(connectionString);
builder.Services.ConfigureServices(connectionString2);
I call ConfigureServices with both connections and looks like this
public static class Configure
{
public static void ConfigureServices(this IServiceCollection services, string connectionString)
{
services
.AddDbContext<CobraDbContext>(options => options.UseSqlServer(connectionString));
........
services.AddScoped<IUnitOfWork, UnitOfWork>();
}
}
}
I am using EF and I have defined my DbContext like this
public class CobraDbContext : DbContext
{
public CobraDbContext(DbContextOptions<CobraDbContext> options)
: base(options)
{
}
public DbSet<SearchResultModel> ParticipantSearch { get; set; } = null!;
....
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
}
From My Controller Method I call the Service.cs witch use UnitOfwork
public class ParticipantService : IParticipantService
{
private readonly ILogger<ParticipantService> _logger;
private readonly IUnitOfWork _iUnitOfwork;
public ParticipantService(ILogger<ParticipantService> logger, IUnitOfWork iUnitOfwork)
{
_logger = logger;
_iUnitOfwork = iUnitOfwork;
}
public async Task<HttpResponseMessage> Search(string participantId)
{
try
{
List<SearchResultModel>? search = await _iUnitOfwork.Participant.AAA(participantId);
return Request.CreateResponse(HttpStatusCode.OK, search);
}
catch (Exception ex)
{
}
}
From My Service I call the Repository that have a generic repository
public class ParticipantRepository : GenericRepository<SearchResultModel>, IParticipantRepository
{
private readonly CobraDbContext _db;
public ParticipantRepository(CobraDbContext db) : base(db)
{
_db = db;
}
public async Task<List<ParticipantPlanModel>?> AAA(string participantId)
{
Query participantGetByID = new();
Dictionary<string, string> dictionary = new Dictionary<string, string>();
participantGetByID.SelectFrom = " exec sp";
List<ParticipantPlanModel>? _return = await ExecuteGeneric(participantGetByID);
return _return;
}
}
I have my generic repo like this
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
protected readonly CobraDbContext Context;
internal DbSet<T> dbSet;
public GenericRepository(CobraDbContext context)
{
Context = context;
dbSet = context.Set<T>();
}
public async Task<List<T>?> ExecuteGeneric(Query query)
{
// var defaultVal = default(T);
var cParameters = new SqlParameter[query.Parameters?.Count ?? 0];
if (query.Parameters != null)
{
int i = 0;
foreach (KeyValuePair<string, string> _param in query.Parameters)
{
cParameters[i] = new SqlParameter() { ParameterName = _param.Key, Value = _param.Value };
i++;
}
}
return await Context.Set<T>().FromSqlRaw(query.SelectFrom + query.Where + query.OrderBy, cParameters).ToListAsync();
}
Depending on the parameter I have to call a database or a the another. I know I can do this duplicating almost all the code... Having to DbContext and two generic Repo..
Is there a way to simplify it and not replicate most of the code?
Thanks

How to get an email provider into a logger using DI in ASP.NET Core?

Sorry this is a bit new to me so I don't quite 'get it'.
I already have a logging provider
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(loggingBuilder =>
{
var loggingSection = Configuration.GetSection("Logging");
loggingBuilder.AddFile(loggingSection);
loggingBuilder.AddConsole();
loggingBuilder.AddDebug();
I am using the package NReco.Logging.File to define AddFile etc.
I want to make it so that exceptions are emailed to me too. So I followed https://learn.microsoft.com/en-us/dotnet/core/extensions/custom-logging-provider to create a custom logger.
public sealed class EmailLoggerConfiguration
{
public int EventId { get; set; }
public string EmailToSendTo { get; set; }
public IEmailSender EmailSender { get; set; }
}
internal class EmailLoggingProvider : ILoggerProvider
{
private readonly IDisposable? _onChangeToken;
private EmailLoggerConfiguration _currentConfig;
private readonly ConcurrentDictionary<string, EmailLogger> _loggers =
new(StringComparer.OrdinalIgnoreCase);
private readonly IEmailSender emailSender;
public EmailLoggingProvider(
IOptionsMonitor<EmailLoggerConfiguration> config)
{
_currentConfig = config.CurrentValue;
_onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
}
public ILogger CreateLogger(string categoryName) =>
_loggers.GetOrAdd(categoryName, name => new EmailLogger(name, GetCurrentConfig ));
private EmailLoggerConfiguration GetCurrentConfig() => _currentConfig;
public void Dispose()
{
_loggers.Clear();
_onChangeToken?.Dispose();
}
}
internal class EmailLogger : ILogger
{
private readonly string categoryName;
private Func<EmailLoggerConfiguration> getCurrentConfig;
IEmailSender emailSender;
public EmailLogger(string categoryName, Func<EmailLoggerConfiguration> getCurrentConfig)
{
this.getCurrentConfig = getCurrentConfig;
this.categoryName = categoryName;
}
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;
public bool IsEnabled(LogLevel logLevel) => !String.IsNullOrEmpty(getCurrentConfig().EmailToSendTo);
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var emailTo = getCurrentConfig().EmailToSendTo;
//var emailServer = getCurrentConfig().EmailSender;
if (!String.IsNullOrEmpty(emailTo) && exception != null)
{
emailSender.SendEmailAsync(emailTo, "Admin exception", exception.ToString());
}
}
}
public static class EmailLoggingExtensions
{
public static ILoggingBuilder AddEmailLogger(
this ILoggingBuilder builder)
{
builder.AddConfiguration();
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, EmailLoggingProvider>());
LoggerProviderOptions.RegisterProviderOptions<EmailLoggerConfiguration, EmailLoggingProvider>(builder.Services);
return builder;
}
public static ILoggingBuilder AddEmailLogger(
this ILoggingBuilder builder,
Action<EmailLoggerConfiguration> configure)
{
builder.AddEmailLogger();
builder.Services.Configure(configure);
return builder;
}
}
You can see that EmailLogger.Log requires emailSender which should be an IEmailSender but I cannot figure out how to get it there using DI.
I realise that you can chain dependencies in DI but ???? I don't see how in this context.
I tried this
loggingBuilder.AddEmailLogger(c =>
{
c.EmailToSendTo = Configuration["Logging:Email:EmailToSendTo"];
c.EmailSender = new AuthMessageSender(????, Configuration);
});
but that didn't help and wouldn't even be right anyway.
In fact, by default, EmailSender is the implementation method of IEmailSender, which is used to call the SendEmailAsync() method. You don't need to go and set c.EmailSender = xxx.
You can consider the following dependency injection approach:
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}
public class EmailSender : IEmailSender
{
//...
private readonly ILogger<EmailSender> logger;
public EmailSender(ILogger<EmailSender> logger) {
//...
this.logger = logger;
}
public Task SendEmailAsync(string email, string subject, string message) {
//...
}
}
At this point, IEmailSender will exist as a custom interface instead of inheriting from Microsoft.AspNetCore.Identity.UI.Services.
And you need to register it as a service:
services.AddTransient<IEmailSender, EmailSender>();
Helpful links:
Add ILogger to send email service
Should I use IEmailSender?
Using IEmailSender from Configure() in my Startup.cs file
Hope this will help you better understand IEmailSender and dependency injection.

How to inject my AddDbContext<ContainerContext> into my DAL project (Core 3.1)

I'm using MVC 5, Core 3.1
I have 'AddDbContext' added to my service in Startup.cs.
I then have a Class library core 3.1 project which is my ADO Dal layer.
This is added as a service as well in The ConfigureServices of Startup.cs.
I want to inject the Connection String into the DAL application.
I have:
public partial class ContainerContext : DbContext
{
public ContainerContext()
{
}
public ContainerContext(DbContextOptions<ContainerContext> options) : base(options)
{
}
}
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<ContainerContext>(options => options.UseSqlServer(connection));
services.AddDAL();
}
In the Dal project:
public static class ServiceCollectionExtensions
{
// Add parameters if required, e.g. for configuration
public static IServiceCollection AddDAL(this IServiceCollection services)
{
// Register all services as required
services.AddScoped<ILeaseBll, LeaseDal>();
return services;
}
}
The Dal class.
public class LeaseDal : ILeaseBll
{
private string conString;
public LeaseDal(???????)
{
// Some validation for the Context maybe (isNull etc?) throw new ArgumentNullException("conString");
//this.connectionString = conString;
}
How would / should it be done?
Thanks
There is a philosophy change with Dot-Net-Core and Dot-Net-Framework....
public class LeaseDal : ILeaseBll
{
private string conString;
This is not best practice in dot-net-CORE.
You do NOT inject your "connection string" in your concrete DataAccessLayer object.
You inject the db-context.
(and the db-context already has been wired to the Ioc...with its correct connection string)
Something like this:
public interface IDepartmentQueryDomainData()
{
Task<int> GetCountAsync(CancellationToken token);
}
..
public class DepartmentQueryEntityFrameworkDomainDataLayer : IDepartmentQueryDomainData
{
public const string ErrorMessageILoggerFactoryIsNull = "ILoggerFactory is null";
public const string ErrorMessageMyCoolDbContextIsNull =
"MyCoolDbContext is null";
private readonly ILogger<DepartmentQueryEntityFrameworkDomainDataLayer> logger;
private readonly MyCoolDbContext entityDbContext;
public DepartmentQueryEntityFrameworkDomainDataLayer(
ILoggerFactory loggerFactory,
MyCoolDbContext context
{
if (null == loggerFactory)
{
throw new ArgumentNullException(ErrorMessageILoggerFactoryIsNull, (Exception)null);
}
this.logger = loggerFactory.CreateLogger<DepartmentQueryEntityFrameworkDomainDataLayer>();
this.entityDbContext = context ?? throw new ArgumentNullException(
ErrorMessageMyCoolDbContextIsNull,
(Exception)null);
}
public async Task<int> GetCountAsync(CancellationToken token)
{
int returnValue = await this.entityDbContext.Departments.AsNoTracking().CountAsync(token);
this.logger.Log(
new LogEntry(
LoggingEventTypeEnum.Trace,
string.Format(
LogMessages.Count,
returnValue)));
return returnValue;
}
}
You can also "see" this here:
https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}
I would never inject the dbContext into a "controller"...(I agree with you that the Dal should be a separate layer)...
but besides that "miscue" on the microsoft example, you do see that you inject the dbContext.
Also see:
https://hovermind.com/aspnet-core/using-dbcontext-with-dependency-injection.html

How to Properly Inject SignalR IHubContext to Quartz.net Job?

I have a ASP.NET MVC Core project with a SignalR hub called MyHub and Quartz.net job called MyJob which could resolve all registered services but IHubContext. However, the IHubContext is successfully resolved inside all controllers.
Here is the error I get inside my JobFactory:
System.InvalidOperationException: 'Unable to resolve service for type
'Microsoft.AspNetCore.SignalR.IHubContext`1[MyProject.Hubs.MyHub]' while
attempting to activate 'MyProject.Jobs.MyJob'.'
Would someone let me know how to resolve the IHubContext inside the Quartz.net MyJob please?
Here is my JobFactory, MyHub, and MyJob:
public class JobFactory : IJobFactory
{
protected readonly IServiceProvider ServiceProvider;
public JobFactory(IServiceProvider serviceProvider)
=> ServiceProvider = serviceProvider;
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
=> ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob;
public void ReturnJob(IJob job)
=> (job as IDisposable)?.Dispose();
}
public class MyHub : Hub
{
private static readonly List<string> ConnectionIds = new();
public static bool Connected => ConnectionIds.Count > 0;
public override Task OnConnectedAsync()
{
ConnectionIds.Add(Context.ConnectionId);
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
ConnectionIds.Remove(Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
}
}
public class MyJob : IJob
{
private readonly ILogger<MyJob> _logger;
private readonly IHubContext<MyHub> _context;
public MyJob(ILogger<MyJob> logger, IHubContext<MyHub> context)
{
_logger = logger;
_context = context;
}
public Task Execute(IJobExecutionContext context)
{
return Task.CompletedTask;
}
}

How to do UnitOfWork + Repository patterns with entity framework core and unit testing

I'm running into an issue while unit testing where if I run multiple tests at once, the DbContext will lose track of records I've added during unit tests and I think this may have to do with how services are registered in my ServiceCollection.
I have the following setup:
IUnitOfWork:
public interface IUnitOfWork : IDisposable
{
IUserRepository Users { get; }
int Complete();
}
UnitOfWork
public class UnitOfWork : IUnitOfWork
{
private readonly MyDbContext _context;
public IUserRepository Users { get; }
public UnitOfWork(MyDbContext context,
IUserRepository userRepository)
{
_context = context;
Users = userRepository;
}
public void Dispose() => _context.Dispose();
public int Complete() => _context.SaveChanges();
}
UserRepository
public class UserRepository : Repository<User>, IUserRepository
{
public UserRepository(MyDbContext context) : base(context) { }
public MyDbContext MyDbContext => Context as MyDbContext;
public Task<User?> GetUserDetailsAsync(int userID)
{
var user = MyDbContext.Users.Where(user => user.Id == userID)
.Include(user => user.Emails)
.Include(user => user.PhoneNumbers).FirstOrDefault();
return Task.FromResult(user);
}
}
Here is my base test:
public abstract class BaseTest : IDisposable
{
protected ServiceProvider ServiceProvider { get; }
private MyDbContext MyDbContext { get; }
protected IUnitOfWork UnitOfWork { get; }
public BaseTest()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<IUserService, UserService>()
.AddScoped<IUnitOfWork, UnitOfWork>()
.AddScoped(typeof(IRepository<>), typeof(Repository<>))
.AddScoped<IOrganizationRepository, OrganizationRepository>()
.AddScoped<IExercisePostRepository, ExercisePostRepository>()
.AddScoped<IUserRepository, UserRepository>()
.AddTransient<IRestClient, RestClient>()
.AddAutoMapper(typeof(Startup).Assembly)
.AddDbContext<MyDbContext>(options =>
options.UseInMemoryDatabase("Core")
.EnableSensitiveDataLogging());
ServiceProvider = serviceCollection.BuildServiceProvider();
SpotcheckrCoreContext = ServiceProvider.GetRequiredService<MyDbContext>();
MyDbContext.Database.EnsureCreated();
UnitOfWork = ServiceProvider.GetRequiredService<IUnitOfWork>();
}
public void Dispose()
{
MyDbContext.Database.EnsureDeleted();
UnitOfWork.Dispose();
}
}
Sample test:
public class UserServiceTests : BaseTest
{
private readonly IUnitOfWork UnitOfWork;
private readonly IUserService Service;
public UserServiceTests()
{
UnitOfWork = ServiceProvider.GetRequiredService<IUnitOfWork>();
Service = ServiceProvider.GetRequiredService<IUserService>();
}
[Fact]
public async void GetUserAsync_WithValidUser_ReturnsUser()
{
var user = new User
{
FirstName = "John",
LastName = "Doe"
};
UnitOfWork.Users.Add(user);
UnitOfWork.Complete();
var result = await Service.GetUserAsync(user.Id);
Assert.Equal(user.Id, result.Id);
}
}
If I run this test by itself, then it will correctly pass and I can see the user in the repository. However if I run it with other tests and debug, then that user is lost once I inspect UnitOfWork.Users in the repository but I do see it in the UnitOfWork.Users in the test.
What is the correct approach here?
Edit 1:
Tried some other changes but no luck yet. Adjusted UnitOfWork to take in the interfaces of each repository and registering them in BaseTest as scoped services. Also tried marking BaseTest as implementing IDisposable and then executing:
public void Dispose()
{
MyDbContext.Database.EnsureDeleted();
UnitOfWork.Dispose();
}
In the service layer I'll see the Users just fine but as soon as I step into the repository layer I'll lose the Users :/ I have a suspicion it is related to dependency injection AddScoped vs AddTransient and how all of that works with running multiple unit tests.
Edit 2:
Tried some more things...Used IClassFixture<BaseTest> on each test class and then ensured that each test class implemented IDisposable and in there I would ensure the Context database was deleted; also ensured in the test class constructor that it was created. With this I ended up with the following error:
The instance of entity type cannot be tracked because another instance with the same key value for {'Id'} is already being tracked
And so I added .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) but the problem still persisted.
This is very annoying to setup.
This is what has resolved it for me for now.
Summary: Created a new ServiceFixture. This ServiceFixture is applied to a BaseTest class as IClassFixture<ServiceFixture>. The ServiceFixture is responsible for initializing the service collection and allowing for it to be reused across different test classes. The purpose of the BaseTest is to allow for disposal of the database and other clean up that is necessary after each test. The Dispose method of this class will detach entity state and also delete the database.
ServiceFixture.cs
public class ServiceFixture
{
public ServiceProvider ServiceProvider { get; }
public ServiceFixture()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<IUserService, UserService>()
.AddScoped<ICertificationService, CertificationService>()
.AddScoped<IOrganizationService, OrganizationService>()
.AddScoped<ICertificateService, CertificateService>()
.AddScoped<IUnitOfWork, UnitOfWork>()
.AddScoped<IUserRepository, UserRepository>()
.AddScoped<IExercisePostRepository, ExercisePostRepository>()
.AddScoped<IEmailRepository, EmailRepository>()
.AddScoped<IPhoneNumberRepository, PhoneNumberRepository>()
.AddScoped<ICertificationRepository, CertificationRepository>()
.AddScoped<ICertificateRepository, CertificateRepository>()
.AddScoped<IOrganizationRepository, OrganizationRepository>()
.AddTransient<IRestClient, RestClient>()
.AddSingleton<NASMCertificationValidator>()
.AddAutoMapper(typeof(Startup).Assembly)
.AddDbContext<SpotcheckrCoreContext>(options =>
options.UseInMemoryDatabase("Spotcheckr-Core")
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
.EnableSensitiveDataLogging());
ServiceProvider = serviceCollection.BuildServiceProvider();
}
}
BaseTest.cs
public abstract class BaseTest : IClassFixture<ServiceFixture>, IDisposable
{
protected readonly ServiceProvider ServiceProvider;
protected readonly IUnitOfWork UnitOfWork;
private readonly SpotcheckrCoreContext Context;
public BaseTest(ServiceFixture serviceFixture)
{
ServiceProvider = serviceFixture.ServiceProvider;
Context = serviceFixture.ServiceProvider.GetRequiredService<SpotcheckrCoreContext>();
UnitOfWork = serviceFixture.ServiceProvider.GetRequiredService<IUnitOfWork>();
Context.Database.EnsureCreated();
}
public void Dispose()
{
Context.ChangeTracker.Entries().ToList().ForEach(entry => entry.State = EntityState.Detached);
Context.Database.EnsureDeleted();
}
}
UserServiceTests.cs
public class UserServiceTests : BaseTest
{
private readonly IUserService Service;
public UserServiceTests(ServiceFixture serviceFixture) : base(serviceFixture)
{
Service = serviceFixture.ServiceProvider.GetRequiredService<IUserService>();
}
[Fact]
public async void GetUserAsync_WithInvalidUser_ThrowsException()
{
Assert.ThrowsAsync<InvalidOperationException>(() => Service.GetUserAsync(-1));
}
[Fact]
public void CreateUser_UserTypeAthlete_CreatesAthleteUser()
{
var result = Service.CreateUser(Models.UserType.Athlete);
Assert.IsType<Athlete>(result);
}
[Fact]
public void CreateUser_UserTypePersonalTrainer_CreatesPersonalTrainerUser()
{
var result = Service.CreateUser(Models.UserType.PersonalTrainer);
Assert.IsType<PersonalTrainer>(result);
}
}

Resources