I try to inject my DbContext using services.AddDbContext in my Startup.cs. It's gets injected in the service that needs to handle every call to the DB. This AzureService gets injected in other services that actually need the data. When I try this however I always get a system.ObjectDiposedException on de DbContext inside AzureService.
When I inject it directly in the CompareService it just work, so I'm probably making a grave mistake with the nested services. Help/insights much appreciated.
code:
Startup.cs
services.AddDbContext<SchoolDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SchoolSyncDb")));
services.AddScoped<IAzureService, AzureService>();
services.AddScoped<ICompareService, CompareService>();
AzureService
public class AzureService : IAzureService
{
private SchoolDbContext schoolDbContext;
private IMapper mapper;
private IHelperService helperSrv;
public AzureService(SchoolDbContext _sdb, IMapper _mapper, IHelperService _helper)
{
schoolDbContext = _sdb;
mapper = _mapper;
helperSrv = _helper;
}
CompareService
public class CompareService: ICompareService
{
private readonly IWisaService wisaSrv;
private readonly ISmartschoolService smtSrv;
private readonly IAzureService azureSrv;
private readonly IHelperService helperSrv;
private readonly ILoggerService loggerSrv;
public CompareService(IWisaService _ws, ILoggerService _ls, ISmartschoolService _ss, IAzureService _as, IHelperService _hs)
{
wisaSrv = _ws;
smtSrv = _ss;
azureSrv = _as;
helperSrv = _hs;
loggerSrv = _ls;
}
trying to get data from the AzureService in the CompareService fails because the DbContext in the AzureService is Diposed.
List<AdbStudentModels> DbList;
try
{
DbList = azureSrv.GetAllStudentInDbBySchool(school);
}
In AzureService
public List<AdbStudentModels> GetAllStudentInDbBySchool(string school)
{
try
{
return schoolDbContext.AdbStudentModels.Where(i => i.School.Schoolnaam.Equals(school)).ToList();
}
catch (Exception ex)
{
throw ex;
}
}
It's seems that I've found a solution reading this article. I changed the lifecycle for CompareService to singleton and the one from AzureService to transient.
I still inject AzureService into CompareService but instead of injecting the DbContext into the azureService, I inject IServiceprovider as a whole and create a scope for the DbContext.
I am by no means a professional programmer, so I'm not claiming this to be the solution or the way to implement this correctly. It just does what I need at the moment.
Startup.cs
services.AddDbContext<SchoolDbContext>(options => options.UseSqlServer(connString));
services.AddTransient<IAzureService, AzureService>();
services.AddSingleton<ICompareService, CompareService>();
CompareService
private readonly IAzureService azureSrv;
public CompareService(IAzureService _as)
{
azureSrv = _as;
}
AzureService
private IServiceProvider sp;
public AzureService(IServiceProvider _sp)
{
sp = _sp;
}
Method in AzureService needing the DbContext (using scope)
public List<AdbStudentModels> GetAllStudentInDbBySchool(string school)
{
try
{
using (var scope = sp.CreateScope())
{
SchoolDbContext db = scope.ServiceProvider.GetRequiredService<SchoolDbContext>();
return db.AdbStudentModels.Where(i => i.School.Schoolnaam.Equals(school)).ToList();
}
}
catch (Exception ex)
{
throw new Exception("GetAllStudentInDbBySchool" + " / " + ex);
}
}
Related
I'm trying to configure IoC (concept I'm not very familiar with yet) in my SF in a stateful service as explained here : https://www.codeproject.com/Articles/1217885/Azure-Service-Fabric-demo and here : https://alexmg.com/posts/introducing-the-autofac-integration-for-service-fabric.
in program.cs - main:
var builder = new ContainerBuilder();
builder.RegisterModule(new GlobalAutofacModule());
builder.RegisterServiceFabricSupport();
builder.RegisterStatefulService<Payment>("PaymentType");
using (builder.Build())
{
ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(Payment).Name);
Thread.Sleep(Timeout.Infinite);
}
GlobalAutofacModule :
public class GlobalAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ChargeRepository>().As<IChargeRepository>().SingleInstance();
builder.RegisterType<CustomerRepository>().As<ICustomerRepository>().SingleInstance();
builder.RegisterType<InvoiceItemRepository>().As<IInvoiceItemRepository>().SingleInstance();
builder.RegisterType<PlanRepository>().As<IPlanRepository>().SingleInstance();
builder.RegisterType<ProductRepository>().As<IProductRepository>().SingleInstance();
builder.RegisterType<SourceRepository>().As<ISourceRepository>().SingleInstance();
builder.RegisterType<SubscriptionRepository>().As<ISubscriptionRepository>().SingleInstance();
builder.RegisterType<TokenRepository>().As<ITokenRepository>().SingleInstance();
}
}
the service is called without problems
public Payment(StatefulServiceContext context,
IChargeRepository chargeRepo,
ICustomerRepository customerRepo,
IInvoiceItemRepository invoiceItemRepo,
IPlanRepository planRepository,
IProductRepository productRepo,
ISourceRepository sourceRepo,
ISubscriptionRepository subscriptionRepo,
ITokenRepository tokenRepo)
: base(context)
{ ... }
in one of it's methodes it needs to call a custom mapper (error on missing params)
var test = new Mapper().GetProductsDto(false, false);
the class is defined like this :
private readonly IChargeRepository _chargeRepo;
private readonly ICustomerRepository _customerRepo;
private readonly IInvoiceItemRepository _invoiceItemRepo;
private readonly IPlanRepository _planRepo;
private readonly IProductRepository _productRepo;
private readonly ISourceRepository _sourceRepo;
private readonly ISubscriptionRepository _subscriptionRepo;
private readonly ITokenRepository _tokenRepo;
public Mapper(IChargeRepository chargeRepo,
ICustomerRepository customerRepo,
IInvoiceItemRepository invoiceItemRepo,
IPlanRepository planRepository,
IProductRepository productRepo,
ISourceRepository sourceRepo,
ISubscriptionRepository subscriptionRepo,
ITokenRepository tokenRepo)
{
_chargeRepo = chargeRepo;
_customerRepo = customerRepo;
_invoiceItemRepo = invoiceItemRepo;
_planRepo = planRepository;
_productRepo = productRepo;
_sourceRepo = sourceRepo;
_subscriptionRepo = subscriptionRepo;
_tokenRepo = tokenRepo;
}
public IEnumerable<ProductListDto> GetStripeProductsDto(bool isLogged, bool isSubscriber) {...}
So how do I instantiate the mapper and call the method without passing every repo as params ?
EDIT: tmp solution until approuved/disapprouved
private static void Main()
{
try
{
using (ContainerOperations.Container)
{
ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(Payment).Name);
Thread.Sleep(Timeout.Infinite);
}
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
throw;
}
}
}
public class ContainerOperations
{
private static readonly Lazy<IContainer> _containerSingleton =
new Lazy<IContainer>(CreateContainer);
public static IContainer Container => _containerSingleton.Value;
private static IContainer CreateContainer()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new GlobalAutofacModule());
builder.RegisterServiceFabricSupport();
builder.RegisterStatefulService<Payment>("Inovatic.SF.Windows.PaymentType");
return builder.Build();
}
}
public class GlobalAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//builder.RegisterType<Mapper>();
builder.RegisterType<ChargeRepository>().As<IChargeRepository>().SingleInstance();
builder.RegisterType<CustomerRepository>().As<ICustomerRepository>().SingleInstance();
builder.RegisterType<InvoiceItemRepository>().As<IInvoiceItemRepository>().SingleInstance();
builder.RegisterType<PlanRepository>().As<IPlanRepository>().SingleInstance();
builder.RegisterType<ProductRepository>().As<IProductRepository>().SingleInstance();
builder.RegisterType<SourceRepository>().As<ISourceRepository>().SingleInstance();
builder.RegisterType<SubscriptionRepository>().As<ISubscriptionRepository>().SingleInstance();
builder.RegisterType<TokenRepository>().As<ITokenRepository>().SingleInstance();
}
}
call is now like this : var productListDto = Mapper.GetStripeProductsDto(isLogged, false);
mapper:
private static IProductRepository _productRepo => ContainerOperations.Container.Resolve<IProductRepository>();
public static IEnumerable<ProductListDto> GetStripeProductsDto(bool isLogged, bool isSubscriber)
{
var productList = _productRepo.GetAllStripeProducts().ToList();
I think you should also register Mapper class in IoC container and add it to Payment's constructor, then container will create Mapper with all required params for you. You can do it calling something like
builder.RegisterType<Mapper>().SingleInstance();
public sealed class SessionContext
{
private ISession httpContext;
public SessionContext(ISession httpContext)
{
this.httpContext = httpContext;
}
public string UserType
{
get
{
return httpContext.GetString("_UserType");
}
set
{
httpContext.SetString("_UserType", value);
}
}
...... More properties .....
}
public class HomeController : Controller
{
private AppSettings _appSettings;
private SessionContext session = null;
private readonly IHttpContextAccessor _httpContextAccessor;
private ISession httpContext => _httpContextAccessor.HttpContext.Session;
//I don't like this constructor as it is getting initialize or every controller call.
public HomeController(IOptions<AppSettings> myAppSettings, IHttpContextAccessor httpContextAccessor)
{
_appSettings = myAppSettings.Value;
_httpContextAccessor = httpContextAccessor;
appSettings = new AppSettings(_appSettings); //Should initialize only once.
session = new SessionContext(httpContext);
}
}
I have questions regarding ...
How to initialize and use Custom / Support classes in MVC 6 with Asp.Net Core 2.0
When I Initialize these classes, they getting initialize or every controller call. That is very redundant.
my SessionContext class is getting re-initialize every time. So I am loosing the values when I call this class from another controller.
I tried this approach but, not much of use.
services.AddSingleton();
Move from question to answer:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddSingleton<SessionContext, SessionContext>();
//calling the extension class to instantiate the classes which we require earlier.
services.AddMyProjectHelper(Configuration)
}
Created a extension class... where it initializes the support classes
public static class MyProjectHelperExtensions
{
public static IServiceCollection AddMyProjectHelper(this IServiceCollection services, IConfiguration configuration)
{
var section = configuration.GetSection("AppSettings");
// we first need to create an instance
var settings = new AppSettings();
// then we set the properties
new ConfigureFromConfigurationOptions<AppSettings>(section).Configure(settings);
var session = services.BuildServiceProvider().GetService<SessionContext>();
// then we register the instance into the services collection
services.AddSingleton<MyProjectHelper>(new MyProjectHelper(settings, session));
return services;
}
}
finally controller ctor uses the DI for the required class. Now I have avoided redundant initialization of support classes.
public SecurityController(MyProjectHelper objHelper, SessionContext sessionContext)
{
session = sessionContext;
projectHelper = projectHelper ?? objHelper;
}
Now, I am able to share the session variables which I have set in my support classes
private SessionContext session = null;
public HomeController(SessionContext sessionContext)
{
session = sessionContext;
}
[Authorize]
public IActionResult Index()
{
if (session.CurrEmployee != null)
{
ViewBag.Name = (session.CurrEmployee.FirstName + " " + session.CurrEmployee.LastName);
return View();
}
}
I have 2 project, Data and Data.test, I use ef core and .net core for both of them, for Data project I have ExpenseDb like this:
public class ExpenseDb: DbContext
{
private IConfigurationRoot _config;
public ExpenseDb(DbContextOptions<ExpenseDb> options, IConfigurationRoot config) : base(options)
{
_config = config;
}
public DbSet<Account> Accounts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_config["Data:ConnectionString"]);
}
}
And I have a repository for Account like this:
private ExpenseDb _db;
public AccountRepository(ExpenseDb db)
{
_db = db;
}
public IEnumerable<Account> All(Guid userId)
{
return (_db.Accounts.AsNoTracking().Where(a => a.UserId == userId).ToList());
}
I use ms IOC for injectiong dependencies like this :
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json");
_config = builder.Build();
}
IConfigurationRoot _config;
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_config);
services.AddDbContext<ExpenseDb>(ServiceLifetime.Scoped);
}
These all are in my Data project, and for Data.Test I would like to test All method, I realized I must Mock my ExpenseDb so I got Moq from Nuget Package and now I have test class like this :
[TestClass]
public class AccountRepositoryTest
{
private readonly Mock<ExpenseDb> _dbMock = new Mock<ExpenseDb>();
private readonly AccountRepository _repo;
public AccountRepositoryTest()
{
_repo = new AccountRepository(_dbMock.Object);
}
[TestMethod]
public void AllForInvalidUser()
{
var fakeaccount = new Account() { Name="cat2",OpenDate=DateTime.Now,StartBalance=100};
Mock < DbSet < Account >> acMock = DbSetMock.Create(fakeaccount);
var results = _repo.All(Guid.Parse("cf15c6c9-f688-47ee-892e-297e530be053"));
Assert.IsNotNull(results);
}
}
Obviously my test is failed, because I must pass config and options to my ExpenseDb somehow, but I don't know How?!
I searched and I found out all answer are saying "You must have an inteface for your service" but i don't want to create an unnecessary interface.
Since DbContextOptions and config are not being used in the actual test code. You could create a constructor in your db context marked as protected to allow the instantiation of the ExpenseDb object without any params.
My Entity framework context is as following
public partial class MyContext : DbContext, IMyContext
{
static MyContext()
{
System.Data.Entity.Database.SetInitializer<MyContext>(null);
}
public MyContext()
: base("Name=MyContext")
{
}
I am resolving it through autofac in the following way
builder.RegisterType(typeof(MainContext)).As(typeof(DbContext)).InstancePerLifetimeScope();
builder.RegisterType<MainContext>().As<IMainContext>().InstancePerRequest();
This db context gets called in repository layer
#region Fields
private readonly IMyContext _context;
#endregion
#region Constructors and Destructors
public EmployeeRepository(IMyContext context)
{
_context = context;
}
#endregion
public void Create(Employee emp)
{
this._context.Employee.Add(emp);
}
Now my issue is , I want to set the connection string dynamically per call. The connection string will be passed through a webapi which i want to pass on to this context. Can anyone help me how can i do that? I am confused about autofac here. Secondly how can i make sure each call sets connection string and does not cache it.
You can use a factory that will build the context and set the connectionstring for you.
public interface IContextFactory
{
IContext GetInstance();
}
public class MyContextFactory : IContextFactory
{
public IContext GetInstance()
{
String connectionString = this.GetConnectionString(HttpContext.Current);
return new MyContext(connectionString);
}
private String GetConnectionString(HttpContext context)
{
// do what you want
}
}
builder.RegisterType<MyContextFactory>()
.As<IContextFactory>()
.InstancePerRequest();
builder.Register(c => c.Resolve<IContextFactory>().GetInstance())
.As<IContext>()
.InstancePerRequest();
If you can't get connectionstring based on HttpContext, you can change contextFactory implementation to expect initialization by WebAPI before creating the instance. For example :
public interface IContextFactory
{
IContext GetInstance();
void Initialize(String connectionString);
}
public class MyContextFactory : IContextFactory
{
private String _connectionString;
public void Initialize(String connectionString)
{
this._connectionString = connectionString;
}
public IContext GetInstance()
{
if (this._connectionString == null)
{
throw new Exception("connectionString not initialized");
}
return new MyContext(this._connectionString);
}
}
At the beginning of your web API call (through attribute for example), you can call the Initialize method. Because the factory is InstancePerRequest you will have one instance for the duration of the request.
By the way, I'm not sure to understand this registration
builder.RegisterType(typeof(MainContext)).As(typeof(DbContext)).InstancePerLifetimeScope();
builder.RegisterType<MainContext>().As<IMainContext>().InstancePerRequest();
It looks buggy because you will have 2 different registration of the same type and not for the same scope, is it intended ? Furthermore, it doesn't sound a good idea to register a DbContext, do you need this registration ?
The following registration looks better :
builder.RegisterType<MainContext>()
.As<IMainContext>()
.As<DbContext>()
.InstancePerRequest();
I'm using EF and MVVM pattern. My question is about the Data Access Layer. in DAL I have the following classes:
MyObjectContext which is technically the standard ObjectContext now, but some Unit-of-work methods will be added to it later.
Repository<TModel> which handles the most needed queries (such as Add, GetAll, ...) on different ObjectSets.
A bunch of DataServices which make use of repositories to provide a higher level of data access for Core.
The project I'm working on is a business application with about 100 EntitySets so far, and there are times when a single interaction of a user can involve up to 20 different EntitySets (updating most of them). I currently add .Include(params string[]) to my queries to prevent ObjectContextDisposedException but it doesn't seem to be a reliable solution.
The question is should I create an instance of MyObjectContext (and therefore Repository) in each of DataService methods (like the following codes, it seems to me that the ability of Unit of work would be useless in this case) or should I create it outside of DataService and pass it to the DataServices through their constructors (or directly to each of the DataService methods) to handle a bunch of database actions (different tables and queries) together. And how?
Here's what MyObjectContext looks like:
public class MyObjectContext : ObjectContext, IUnitOfWork
{
public MyObjectContext()
: base("name=EdmContainer", "EdmContainer")
{
ContextOptions.LazyLoadingEnabled = true;
}
#region IUnitOfWork Members
public void Commit()
{
SaveChanges();
}
#endregion
}
This is how Repository looks like:
public class Repository<TModel>
{
private readonly SoheilEdmContext _context;
public Repository(IUnitOfWork unitOfWork)
{
if (unitOfWork == null)
throw new ArgumentNullException("unitOfWork");
_context = unitOfWork as SoheilEdmContext;
}
public TModel FirstOrDefault(Expression<Func<TModel, bool>> where)
{
return _context.CreateObjectSet<TModel>().FirstOrDefault(where);
}
public void Add(TModel entity)
{
_context.CreateObjectSet<TModel>().AddObject(entity);
}
...
}
And this is how a common DataService looks like:
public class JobDataService : IDataService<Job>
{
#region IDataService<Job> Members
public Job GetSingle(int id)
{
Job model = null;
using (var context = new MyObjectContext())
{
var repos = new Repository<Job>(context);
model = repos.FirstOrDefault(x => x.Id == id);
}
return model;
}
public IEnumerable<Job> GetAll()
{
using (var context = new MyObjectContext())
{
var repos = new Repository<Job>(context);
var models = repos.GetAll();
return models;
}
}
public IEnumerable<Job> GetActives()
{
throw new NotImplementedException();
}
public int AddModel(Job model)
{
using (var context = new MyObjectContext())
{
var repos = new Repository<Job>(context);
repos.Add(model);
context.SaveChanges();
}
}
public void UpdateModel(Job model)
{
throw new NotImplementedException();
}
public void DeleteModel(Job model)
{
using (var context = new MyObjectContext())
{
var repos = new Repository<Job>(context);
var model = repos.FirstOrDefault(x => x.Id == model.Id);
if (model == null) return;
repos.Delete(model);
context.SaveChanges();
}
}
#endregion
}
Any kind of idea or insight would be appreciated.
You can create an instance of MyObjectContext in each service, like JobDataService, however, it makes your code messy and it is hard to maintain. Create instance of MyObjectContext outside of DataService is better. What you have now, if you have 100 EntitySets, you have to create 100 DataServices. That is because the use of "Repository Pattern" and "UnitOfWork" here is not efficient. I would suggest doing the following:
ObjectContext
public class MyObjectContext : ObjectContext
{
public MyObjectContext() : base("name=EdmContainer", "EdmContainer")
{
ContextOptions.LazyLoadingEnabled = true;
}
#region IUnitOfWork Members
public void Commit()
{
SaveChanges();
}
#endregion
}
Generic Repository
public interface IRepository<TModel> where TModel : class
{
void Add(TModel entity);
IEnumerable<TModel> GetAll();
// Do some more implement
}
public class Repository<TModel> : IRepository<TModel> where TModel : class
{
private readonly ObjectContext _context;
public Repository(ObjectContext context)
{
_context = context;
}
public virtual void Add(TModel entity)
{
_context.CreateObjectSet<TModel>().AddObject(entity);
}
public virtual IEnumerable<TModel> GetAll()
{
return _context.CreateObjectSet<TModel>();
}
}
UnitOfWork
public interface IUnitOfWork : IDisposable
{
IRepository<Job> Jobs { get; }
IRepository<User> Users { get;}
void Commit();
}
public class UnitOfWork : IUnitOfWork
{
private readonly SoheilEdmContext _context;
private readonly IRepository<Job> _jobRepository;
private readonly IRepository<User> _userRepository;
public UnitOfWork(SoheilEdmContext context)
{
_context = context;
_jobRepository = new Repository<Job>(_context);
_userRepository = new Repository<User>(_context);
}
public IRepository<Job> Jobs{get { return _jobRepository; }}
public IRepository<User> Users{get { return _userRepository; }}
public void Commit(){_context.Commit();}
public void Dispose()
{
if (_context != null)
{
_context.Dispose();
}
GC.SuppressFinalize(this);
}
JodDataSerivce
public interface IDataService
{
IEnumerable<Job> GetAll();
}
public class DataService : IDataService
{
private readonly IUnitOfWork _unitOfWork;
public DataService(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public IEnumerable<Job> GetAll()
{
return _unitOfWork.Jobs.GetAll();
}
}
Here I used interface for implementing everything, if you want to do the same, you need to use IoC Container. I used the "Simple Injector", you can find it here:
Simple Injector
One more suggestion, if you feel like you have too many I/O operations to implement, like database access, querying data, etc., you should consider using Asynchronous. Below is a good video on Asynchronous.
How to Build ASP.NET Web Applications Using Async