Get MemoryCache in Different Controller for Entity Framework Database - c#

I am taking small lookup table values from Entity Framework database and storing in MemoryCache. Currently using both ways, regular memoryCache, and Singleton MemoryContainer seen here Asp.Net Core: Use memory cache outside controller.
In Home Controller, this saves ProductTypes in a MemoryCache, and we see values store correctly in debug window (ProductType, ProductName, etc).
public class HomeController : Controller
{
public IMemoryCache _memoryCache;
public readonly StoreContext _storeContext;
public MemoryContainer _memoryContainer;
public HomeController(StoreContext storeContext, IMemoryCache memoryCache, MemoryContainer memoryContainer)
{
_storeContext= storeContext;
_memoryCache = memoryCache;
_memoryContainer = memoryContainer;
}
public IActionResult Index()
{
var productTypes = storeContext.ProductTypes;
_memoryCache.Set("ProductTypesKey", productTypes );
_memoryContainer._memoryCache.Set("ProductTypesKey2", test);
return View(); //both values store correctly
}
Then when going to ProductController,
public class ProductsController : Controller
{
public StoreContext _storeContext;
public IMemoryCache _memoryCache;
public MemoryContainer _memoryContainer;
public ProductsController(StoreContext storeContext, IMemoryCache memoryCache, MemoryContainer memoryContainer)
{
_storeContext = storeContext;
_memoryCache = memoryCache;
_memoryContainer = memoryContainer;
}
public async Task<IActionResult> Details(int? id)
{
var test = _memoryCache.Get<DbSet<ProductTypes>>("ProductTypesKey");
var test2 = _memoryContainer._memoryCache.Get<DbSet<ProductTypes>>("ProductTypesKey2");
I see the following error result in MemoryCache for both, how can this be fixed?
How do to make sure MemoryCache will get/store correctly with DbContext, regardless of going from Controller to Controller?
"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: 'StoreContext'."
Other Code:
public class MemoryContainer
{
public IMemoryCache _memoryCache { get; set; }
public MemoryContainer(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
}
Startup.cs
services.AddMemoryCache();
services.AddSingleton<MemoryContainer>();
Other Resources:
MVC Net Core Pass MemoryCache to All Controllers

DbSet<T> implements IQueryable so those ProductTypes aren't really inside your application until materialized.
A simple ToList() will do the job:
var productTypes = storeContext.ProductTypes.ToList();
Then, inside the other controller:
var test = _memoryCache.Get<List<ProductTypes>>("ProductTypesKey");
Calling ToList() on a DbSet without any filters isn't the best approach in all scenarios because it will load the whole sql table behind the DbSet. However, I guess this is kind of what you are trying to do, just make sure that you never do this on a table with a lot of data.
Note: you are seeing the data in debug window, because expanding IQueryable results actually performs sql queries.

Related

Declaring a controller-level instance of the database context

In a controller, is there any difference between declaring a database context instance like this:
public class MyController : Controller
{
private readonly MyDatabaseEntities _context = new MyDatabaseEntities();
public ActionResult Index()
{
...
}
}
Versus this:
public class MyController : Controller
{
private readonly MyDatabaseEntities _context;
public MyController()
{
_context = new MyDatabaseEntities();
}
public ActionResult Index()
{
...
}
}
I'm just beginning to realise that the way I've been handling database context in my ASP.NET apps may not have been correct (or at least ideal) for a while and I'm reading up on it and thinking of how I'm going to start using Dependency Injection, but for starters I was just wondering if I'm even instantiating database contexts in my controllers correctly.
Incidentally, I'm still working in .NET Framework 4.7.2. Hopefully we'll start working in .NET 5 soon!

Get DbContext in async action

I am rewriting/ moving a website from ASP MVC to ASP MVC Core. This application has a dynamic menu which depends on the logged in user. In order to build the menu, each controller derives from a custom BaseController who sets in ViewBag menu items, an later, in Layout, those items are retrieved and passed as arguments to a PartialView.
public BaseController:Controller
{
public BaseController()
{
...
ViewBag.Menu=Utils.GetMenu();
...
}
}
I don't want to use the same logic as the lads who wrote the old code. So I thought to use a ViewComponent to render the menu. But I have a problem with this approach. In Invoke method I need to query for the menu items. Right now I get a DbContext instance from the service provider (HttpContext.RequestServices), and I use it to query whatever data I need. But the Invoke function is called asynchronously from Layout and I know that it is not very good to send DbContext to async methods:
<cache expires-after="#TimeSpan.FromHours(2)" enabled="true">
#await Component.InvokeAsync(typeof(Admin.ViewComponents.MeniuViewComponent))
</cache>
Is this a good approach? Is it safe to get a DbContext (registered as Scoped in Startup) in Invoke method (or any other async method or action) and use it? And if it is not a good idea, how should I deal with this kind of situations where I need data from db in async methods?
I had the case in my project to get the DbContext inside a HostedService from asp.net core.
I injected the IServiceProvider inside the constructor and build my own scope, to get the registered DbContext:
private readonly IServiceProvider _serviceProvider;
public BaseController(IServiceProvider provider){
_serviceProvider = provider;
}
private void DoSth(){
using var scope = _serviceProvider.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<YOUR_DbContext_CLASS>();
... //do something great stuff here with the database
}
I think you can directly dependency inject the DbContext in the ViewComponent
public class MenuViewComponent : ViewComponent
{
private readonly MenuDbContext _context;
public TopMenuViewComponent(MenuDbContext context)
{
_context = context;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var model = _context.MenuItems.ToList();
return View("Default", model);
}
}
For more details, refer to the doc.

How to load data into InMemory database on startup in Asp.Net Core API?

I am working on Asp.Net Core 3.1 API. I have a few tables in the SQL Server database.
I am trying to use the InMemory database and I am trying to load the data into these tables on application startup.
I have written the below code.
public partial class MyContext : DbContext
{
public MyContext ()
{
}
public MyContext (DbContextOptions<MyContext > options)
: base(options)
{
}
public virtual DbSet<TestEntity> TestEntity{ get; set; }
...
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseInMemoryDatabase("name=RuleDB");
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyContext>(opt => opt.UseInMemoryDatabase("myDbName"));
appsettings.json
"ConnectionStrings": {
"RuleDB": "Server=tcp:xyz.database.windows.net,1433;Initial Catalog=myDB;Persist Security Info=False;User ID=test;Password=****;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30"
}
When I try to do context.TestEntity.ToList();, it shows no records.
I am basically trying to load the data from my SQL Server database to my InMemory database on application startup.
Am I missing any steps this is not a good approach ( I mean should I use InMemory Cache in place of InMemory database)?
According to your code snippet, I didn't find where you add data to the in-memory database when the application starts up. So, I assume the issue is relate it.
Try to refer the following articles to Seed Data to The Database:
Applying Seed Data To The Database
Using EF Core's InMemory Provider To Store A "Database" In Memory
Entity Framework Core InMemory Database
In StartUp.cs, ConfigureServices make a reference to
services.AddTransient<IEntityRepository, EntityRepository>();
services.AddTransient<IEntityService, EntityService>();
services.AddMemoryCache();
Add a new repository interface.
public interface IEntityRepository
{
Task<List<Entity>> GetEntity();
}
Add a new class that will encapsulate the DbContext, and implement the interface
public class EntityRepository : IEntityRepository
{
private readonly DbContext _dbContext;
public(EntityRepository dbContext){
_dbContext = dbContext;
}
public async Task<List<Entity>> GetEntity()
{
return await _dbContext.Entity.ToListAsync();
}
}
Add another interface named IEntityService
public interface IEntityService
{
Task<List<Entity>> GetEntity();
}
Add another class named EntityService, encapsulating the caching and repository calls.
public class EntityService : IEntityService
{
private readonly IEntityRepository _repository;
private readonly IMemoryCache _cache;
public(EntityService repository, IMemoryCache cache){
_repository = repository;
_cache = cache
}
public async Task<List<Entity>> GetEntity()
{
var cacheKey = $"{anything-can-be-user-specific-too}";
return await _memoryCache.GetOrCreateAsync(cacheKey, x =>
{
var entities = _repository.GetEntity().GetAwaiter().GetResult();
x.SlidingExpiration = TimeSpan.FromSeconds(900);
x.AbsoluteExpiration = DateTime.Now.AddHours(1);
return Task.FromResult(entities);
});
}
}
The cache policy that I have used as an example indicates that if the cache is not referenced in 10 minutes (slidingRefresh), it will expire and be removed. This can be used together with AbsoluteExpiration so that you can force the cache to be refreshed after let's say 1 hour...
Now make the IEntityService call your starting point, lets say you have a controller...
Inject the service to the controller e.g
public class RandomController : ControllerBase
{
private readonly IEntityService _entityService;
public RandomController(IEntityService entityService)
{
_entityService = entityService;
}
[HttpGet]
[Route("randomroute")]
public async Task<IActionResult> GetEntities()
{
return Ok( await _entityService.GetEntity());
}
}
the first time executed the application will reference the database, however, all subsequent calls (until the cache policy has expired) will reference the memory cache.
Now if you want to change the cached item, you can introduce new services to perform database updates and then force a cache update by invalidating the cache key.
Updating IMemoryCache once an entity has changed

How to refactor from static methods to Dependency Injection using MS.DI and .NET Core?

I am in the process of migrating a project from .Net Framework to .Net Core. In the existing project we have a utility class with a few functions like below:
public static class BudgetUtilities
{
public static decimal CalculateBudgetRemaining(string fiscalYear = null)
{
if (string.IsNullOrWhiteSpace(fiscalYear))
fiscalYear = DateTime.Now.GetFiscalYear().ToString();
using (AppContext _context = new AppContext())
{
FiscalYearBudget currentBudget = _context.FiscalYearBudgets.Find(fiscalYear);
return currentBudget.BudgetAllocation - currentBudget.ExpenditureToDate;
}
}
// other functions removed for brevity
}
I can then reference it anywhere else using BudgetUtilities.CalculateBudgetRemaining(). Very simple and straightforward.
When migrating this function to .Net Core I need to use Dependency Injection so I have amended the class by removing the static modifier (since static constructors cannot have parameters) and injecting the AppContext into the constructor:
public class BudgetUtilities
{
private readonly AppContext _context;
public BudgetUtilities(AppContext context)
{
_context = context;
}
public decimal CalculateBudgetRemaining(string financialYear = null)
{
if (string.IsNullOrWhiteSpace(fiscalYear))
fiscalYear = DateTime.Now.GetFiscalYear().ToString();
FiscalYearBudget currentBudget = _context.FiscalYearBudgets.Find(fiscalYear);
return currentBudget.BudgetAllocation - currentBudget.ExpenditureToDate;
}
}
I then tried to call my code by doing the following:
BudgetUtilities utils = new BudgetUtilities();
decimal remaining = utils.CalculateBudgetRemaining();
But I cannot make a new instance of BudgetUtilities without providing an AppContext in the constructor which makes sense. Every method in this application is at some point initiated by a controller action, and I know that DbContexts are supposed to be short lived, so I assume passing the context the whole way down to this BudgetUtilities class from the initial controller is a bad idea.
The only other option I can see is to keep going back up the call stack from where CalculateBudgetRemaining() is referenced and keep adding in constructor injections until I get to a controller but this is not the only class I will have to inject like this so my constructors further up the chain are going to be really bloated and this will make my ConfigureServices() method bloated too.
I'm sure there's a simple way to do this but I just can't see it.
Don't manually create a new BudgetUtilities instance, that type should also be registered with the DI Framework, preferably interfaced:
public interface IBudgetUtilities
{
decimal CalculateBudgetRemaining(string financialYear);
}
public class BudgetUtilities : IBudgetUtilities
Then in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddScoped<IBudgetUtilities, BudgetUtilities>();
}
Then it can be injected into any class that needs it, such as a controller:
public class YourController : Controller
{
private readonly IBudgetUtilities _utils;
public YourController(IBudgetUtilities utils)
{
_utils = utils;
}
public ActionResult YourMethod()
{
//...
decimal remaining = _utils.CalculateBudgetRemaining();
}
}
By default, registered DbContexts have a scoped lifetime, which means a single instance is used for the entirety of a HTTP request.

Unable to register DbConnection with Unity and Entity Framework

I am not at all sure what the underlying problem is that is causing this exception.
I am using ASP.NET MVC, with Unity.Mvc, and Entity Framework 6. I have the following code to register my repositories:
public static void RegisterTypes(IUnityContainer container)
{
// NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
// container.LoadConfiguration();
// TODO: Register your types here
// container.RegisterType<IProductRepository, ProductRepository>();
container.RegisterType<IGenericRepository<Customer>, GenericRepository<Customer>>();
container.RegisterType<IGenericRepository<Product>, GenericRepository<Product>>();
container.RegisterType<IGenericRepository<Order>, GenericRepository<Order>>();
container.RegisterType<IGenericRepository<OrderItem>, GenericRepository<OrderItem>>();
container.RegisterType<IGenericRepository<Supplier>, GenericRepository<Supplier>>();
}
And then in a controller I have:
public class IndexController : Controller
{
public IndexController(IGenericRepository<Customer> testGenericRepository)
{
var result = testGenericRepository.SelectAll();
}
public ActionResult Index()
{
return View();
}
}
And the repository has the following code:
public class GenericRepository<T> : IGenericRepository<T>
where T : class
{
private readonly DbContext _dbContext;
private readonly IDbSet<T> _dbSet;
public GenericRepository(DbContext dbContext)
{
if (dbContext == null)
{
throw new ArgumentNullException(nameof(dbContext));
}
_dbContext = dbContext;
_dbSet = _dbContext.Set<T>();
}
public IEnumerable<T> SelectAll()
{
return _dbSet.AsEnumerable<T>();
}
}
The problem that I'm having is that if I have a breakpoint in the "RegisterTypes" method, I can see that the container is definitely getting all the repositories registered, but a breakpoint in the constructor of the repositories never gets hit.
So I think that the fact that the breakpoint does not get hit, and I have not registered a "System.Data.Common.DbConnection" means that the DbContext that the repository uses never gets set.
I can't find any useful information about how to use "System.Data.Common.DbConnection" with Unity and the DbContext from Entity Framework.
How do I resolve this?
You should add to your RegisterTypes how to build your DbContext, and probably with which lifetime.
If you have your own class (say CustomContext) inheriting from DbContext, register it. Supposing your default lifetime is adequate:
container.RegisterType<DBContext, CustomContext>();
If you use directly DbContext, instruct Unity which constructor it should use. By example, supposing your connection string is named appConnectionString:
container.RegisterType<DBContext>(
new InjectionConstructor("name=appConnectionString"));

Categories

Resources