Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use DbContext in separate class library .net core?

I'm trying to access my dbcontext from my .net core 3.1 MVC project in a class library. Currently I inject my database into the service collection in startup.cs

public class AppDbContext : DbContext
{
    public DbSet<User> Users {get; set;}
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    { }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        // Handles duplicate entry for email in user table
        builder.Entity<User>()
            .HasIndex(u => u.Email)
            .IsUnique();
    }
}

However, I'm unsure of how to access this AppDbContext in my class library. I tried accessing it like i would a controller, but obviously it doesn't know about the DI container.

More information: This library is used for common requests that are made. It must always be separate from the main web application and the user should never have to code in this class library. Therefore, I need to be able to access the dbcontext thats in the main web project from the class library.

like image 445
TheDizzle Avatar asked Jan 15 '20 14:01

TheDizzle


People also ask

How do I add EDMX file to class library project?

edmx . Go to solution Explorer > select Satyadatabasemodel.Context.tt under Satyadatabasemodel. edmx > Right click and Go to Properties > Enter "Custom Tool Namespace" value. Our class library project name is "Entities" > Save.

Which method is used for adding DbContext class as service?

The AddDbContext extension method registers DbContext types with a scoped lifetime by default.

Should DbContext be Singleton?

First, DbContext is a lightweight object; it is designed to be used once per business transaction. Making your DbContext a Singleton and reusing it throughout the application can cause other problems, like concurrency and memory leak issues. And the DbContext class is not thread safe.


3 Answers

As you said you are developing the class library to use any DbContext passing by the client of the library then you have to do as follows:

First considering your class library has following interfaces and classes where your DbContext will be used:

public interface IUnitOfWork 
{
    IRepository<T> Repository<T>() where T : class;
    Task SaveChangesAsync();
}

internal class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _dbContext;
    private Hashtable _repositories;
    public UnitOfWork(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IRepository<T> Repository<T>() where T : class
    {
        if (_repositories == null)
            _repositories = new Hashtable();

        var type = typeof(T).Name;

        if (!_repositories.ContainsKey(type))
        {
            var repositoryType = typeof(Repository<>);

            var repositoryInstance =
                Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), _dbContext);

            _repositories.Add(type, repositoryInstance);
        }

        return (IRepository<T>)_repositories[type];
    }

    public async Task SaveChangesAsync()
    {
        await _dbContext.SaveChangesAsync();
    }
}

public interface IRepository<TEntity> where TEntity : class
{
     Task InsertEntityAsync(TEntity entity);
}

internal class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
    private readonly DbContext _dbContext;
    public Repository(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task InsertEntityAsync(TEntity entity)
    {
        await _dbContext.Set<TEntity>().AddAsync(entity);
    }
 }

Now write a a service collection extension method in your class library as follows:

public static class ServiceCollectionExtensions
{
   
    public static void RegisterYourLibrary(this IServiceCollection services, DbContext dbContext)
    {
        if (dbContext == null)
        {
            throw new ArgumentNullException(nameof(dbContext));
        }

        services.AddScoped<IUnitOfWork, UnitOfWork>(uow => new UnitOfWork(dbContext));
    }
}

Now in the Startup.ConfigureServices of your client application as follows:

public void ConfigureServices(IServiceCollection services)
{
    string connectionString = Configuration.GetConnectionString("ConnectionStringName");
    services.AddDbContext<AppDbContext>(option => option.UseSqlServer(connectionString));

    ServiceProvider serviceProvider = services.BuildServiceProvider();
    AppDbContext appDbContext = serviceProvider.GetService<AppDbContext>();

    services.RegisterYourLibrary(appDbContext); // <-- Here passing the DbConext instance to the class library

    .......
}

Usage:

public class EmployeeController : Controller
{
    private readonly IUnitOfWork _unitOfWork;
 
    public EmployeeController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public async Task<IActionResult> Insert()
    {
        Employee employee = new Employee();
        await _unitOfWork.Repository<Employee>().InsertEntityAsync(employee);
        await _unitOfWork.SaveChangesAsync();
        return View();
    }
}
like image 112
TanvirArjel Avatar answered Oct 22 '22 17:10

TanvirArjel


I solved it like this after getting the error from PMC command Add-Migration InitialCreate:

Unable to create an object of type 'ApplicationDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

Added IDesignTimeDbContextFactory as @JoseGonzalez but I did not want to hard code my connection string in the implementation of IDesignTimeDbContextFactory but instead pick it up from my applications appsettings.json.

Final solution looked like this:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace MyNamespace
{
    public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
    {
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var configuration = new ConfigurationBuilder()
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json")
                    .Build();

            var optionsBuilder = new DbContextOptionsBuilder();

            var connectionString = configuration
                        .GetConnectionString("DefaultConnection");

            optionsBuilder.UseSqlServer(connectionString);

            return new ApplicationDbContext(optionsBuilder.Options);
        }
    }
}

I could then use this in my other project like this:

var applicationDbContextFactory = new ApplicationDbContextFactory();

using (var dbContext = applicationDbContextFactory.CreateDbContext(args))
{
    
}
like image 20
Ogglas Avatar answered Oct 22 '22 17:10

Ogglas


if you created your dbcontext in another project/lib you first need to migrated it, And update it. EF uses IDesignTimeDbContextFactory notify entity framwork about the lib.

 public class ContextFactoryNeededForMigrations : IDesignTimeDbContextFactory<AppDbContext >
    {
        private const string ConnectionString =
            "Server=(localdb)\\mssqllocaldb;Database=EfCoreInActionDb;Trusted_Connection=True;MultipleActiveResultSets=true";

        public EfCoreContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<EfCoreContext>();
            optionsBuilder.UseSqlServer(ConnectionString,
                b => b.MigrationsAssembly("DataLayer"));

            return new EfCoreContext(optionsBuilder.Options);
        }

Then you can add it to your startup.cs DI container like this.

 services.AddDbContextPool<AppDbContext >( 
                options => options.UseSqlServer(connection,
                b => b.MigrationsAssembly("DataLayer"))); 

Here is good tutorial on the subject. enter link description here

like image 35
Jose Gonzalez Avatar answered Oct 22 '22 17:10

Jose Gonzalez