4

I have some problems with EF-Core that I'm trying to figure out.
I use the startup code in the MVC Core application to initalize the db context.
This is my DB context:

public class AccountsDBContext : DbContext
{
    public AccountsDBContext(DbContextOptions<AccountsDBContext> options)
        :base(options)
    {

    }

    // ...

}

And startup code:

 public void ConfigureServices(IServiceCollection services)
 {
        // Inject the account db
        services.AddDbContext<AccountsDBContext>(options =>
           options.UseMySQL(Configuration.GetConnectionString("AccountsStore")));

        // ...

In all the exampes I see the DB Context is a delivered via the constructor to the controller (I assume by dependency injection) and from there on to other entities\ layers.

 [Route("api/[controller]")]
 public class AccountsController : Controller
 {
    private AccountsDBContext _db;

    public AccountsController(AccountsDBContext context)
    {
        this._db = context;
    }

However, I'm not very fond of the idea that the db context will be a member at the controller.
I really prefer to get a hold of the db context in the data access layer instead of getting it passed into the repositories classes.
Is there a way to get the context inside the data access layer? (There is no IServiceCollection, IApplicationBuilder, IServiceScopeFactory there as far as I know)

3
  • 1
    You're supposed to inject your DAL into the controller, and in your DAL have the constructor expect a EF context as a dependency. The built-in DI of .Net Core should be able to resolve both easily. See learn.microsoft.com/en-us/aspnet/core/fundamentals/… Commented Nov 29, 2016 at 9:36
  • If you would like to get(create) the dbcontext in DAL, just initialize a DAL.DbContextFactory in startup.cs, and let the DbContextFactory create the dbcontext at the right time. See http://stackoverflow.com/a/40837070/7045253 Commented Nov 29, 2016 at 10:07
  • @haim770 The example is great. Thank you. Commented Nov 29, 2016 at 10:18

4 Answers 4

2

I Understand what you are trying to do. I have done exactly that. The key is to Create a static class in your DAL that uses the IServiceCollection. then in here you add your context here's mine and it works a treat My front end doesn't even know about entity framework, nethier does my business layer:

public static IServiceCollection RegisterRepositoryServices(this IServiceCollection services)
    {
        services.AddIdentity<ApplicationUser, IdentityRole<int>>(
            config => { config.User.RequireUniqueEmail = true;
                config.Cookies.ApplicationCookie.LoginPath = "/Account/Login";
                config.Cookies.ApplicationCookie.AuthenticationScheme = "Cookie";
                config.Cookies.ApplicationCookie.AutomaticAuthenticate = false;
                config.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents()
                {
                    OnRedirectToLogin = async ctx =>
                    {
                        if (ctx.Request.Path.StartsWithSegments("/visualjobs") && ctx.Response.StatusCode == 200)
                        {
                            ctx.Response.StatusCode = 401;
                        }
                        else
                        {
                            ctx.Response.Redirect(ctx.RedirectUri);
                        }
                        await Task.Yield();
                    }
                };
            }).AddEntityFrameworkStores<VisualJobsDbContext, int>()
          .AddDefaultTokenProviders();

        services.AddEntityFramework().AddDbContext<VisualJobsDbContext>();

        services.AddScoped<IRecruiterRepository, RecruiterRepository>();
        services.AddSingleton<IAccountRepository, AccountRepository>();

        return services;
    }

then in my service layer I have another static class. My service layer has a reference to the repository layer and I register the repository services here (bootstrapping the repository into the service layer), like so and then I do the same again in the UI:

Service layer code:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection RegisterServices(this IServiceCollection services)
    {
        services.RegisterRepositoryServices();
        services.AddScoped<IRecruiterService, RecruiterService>();
        services.AddSingleton<IAccountService, AccountService>();

        return services;
    }
}

The Magic in the Repository Layer:

public partial class VisualJobsDbContext : IdentityDbContext<ApplicationUser, IdentityRole<int>, int>
{
    private IConfigurationRoot _config;

    public VisualJobsDbContext() { }

    public VisualJobsDbContext(IConfigurationRoot config, DbContextOptions<VisualJobsDbContext> options) : base(options)
    {
        _config = config;
    }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);

        optionsBuilder.UseSqlServer(@_config["ConnectionStrings:VisualJobsContextConnection"]);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {....
Sign up to request clarification or add additional context in comments.

3 Comments

How can you specify a connection string in DAL if you haven't IConfiguration there?
@Pyrejkee My example above only shows the service layer. in the repository layer, I pass in an IConfigurationRoot on the constructor with an override on the OnConfiguring method and set it in there.
@Pyrejkee for clarity, I've extended my answer with the bit of code you are probably looking for,
0

Inject your repository/DAL implementation into the controller and have the DbContext injected into the repo constructor. The DI container will hook it all up as long as the appropriate classes are registered

Comments

0

Should be quite simple in .NET core MVC

 public interface IAuthorsDataRepository
 {
     Task Create(AuthorViewModel author);
     Task<AuthorViewModel> GetDetails(int id);

     DbSet<AuthorViewModel> GetAll();

     Task<AuthorViewModel?> GetNotTracking(int id);

     Task Edit(AuthorViewModel author);

     Task Delete(int id);
 }

your repo:

public class AuthorstDataRepository : IAuthorsDataRepository
 {
     private readonly BooksManagementSystemContext _context;

     public AuthorstDataRepository(BooksManagementSystemContext context)
     {
         this._context = context;
     }

     public async Task Create(AuthorViewModel author)
     {
         _context.Add(author);
         await _context.SaveChangesAsync();
     }

     public async Task Delete(int id)
     {
         var authorToDelete = new AuthorViewModel() { Id = id };
         _context.Entry(authorToDelete).State = EntityState.Deleted;
         await _context.SaveChangesAsync();
     }

     public async Task Edit(AuthorViewModel author)
     {
         _context.Update(author);
         await _context.SaveChangesAsync();
     }

     public DbSet<AuthorViewModel> GetAll()
     {
         return _context.Authors;
     }

     public async Task<AuthorViewModel> GetDetails(int id)
     {
         return await _context.Authors.FindAsync(id);
     }

     public async Task<AuthorViewModel?> GetNotTracking(int id)
     {
         return await _context.Authors
              .AsNoTracking()// EF extension to update existing user instead of inserting a new one
              .FirstOrDefaultAsync(m => m.Id == id);
     }
 }

Usage in the controller:

public class AuthorController : Controller
{
    private readonly IAuthorsDataRepository _repository;

    public AuthorController(IAuthorsDataRepository repository)
    {
        this._repository = repository;
    }

    // GET: AuthorController
    [Authorize(Roles = "Admin")]
    public ActionResult Index()
    {
        return View(_repository.GetAll());
    }

    // GET: AuthorController/Details/5
    [Authorize(Roles = "Admin")]
    public async Task<IActionResult> Details(int id)
    {
        var author = await _repository.GetDetails(id);
        if (author == null)
        {
            return NotFound();
        }
        return View(author);
    }

    // GET: AuthorController/Create
    public ActionResult Create()
    {
        return View();
    }

    // POST: AuthorController/Create
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("Id,AuthorFirstname,AuthorSecondname")]
                AuthorViewModel author)
    {
        try
        {
            if (ModelState.IsValid)
            {
                await _repository.Create(author);
                return RedirectToAction(nameof(Index));
            }
        }
        catch (DbUpdateException ex)
        {
            //Log the error (uncomment ex variable name and write a log.
            ModelState.AddModelError("", "Unable to save changes. " +
                "posible reason is " + ex.Message);
        }
        return View(author);
    }

    // GET: AuthorController/Edit/5
    public async Task<IActionResult> Edit(int id)
    {
        var author = await _repository.GetNotTracking(id);

        if (id != author.Id)
        {
            return NotFound();
        }

        if (author == null)
        {
            return NotFound();
        }

        return View(author);
    }

    // POST: AuthorController/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(int id, [Bind("Id,AuthorFirstname,AuthorSecondname")]
                AuthorViewModel author)
    {
        if (id != author.Id)
        {
            return NotFound();
        }

        if (ModelState.IsValid)
        {
            try
            {
                await _repository.Edit(author);
                return RedirectToAction(nameof(Index));
            }
            catch (DbUpdateException ex)
            {
                //Log the error (uncomment ex variable name and write a log.
                ModelState.AddModelError("", "Unable to save changes. " +
                    "posible reason is " + ex.Message);
            }
        }
        return View(author);
    }

    // GET: AuthorController/Delete/5
    public async Task<IActionResult> Delete(int? id, bool? saveChangesError = false)
    {
        if (id == null)
        {
            return NotFound();
        }

        var author = await _repository.GetNotTracking(id.GetValueOrDefault());
        if (author == null)
        {
            return NotFound();
        }

        if (saveChangesError.GetValueOrDefault())
        {
            ViewData["ErrorMessage"] =
                "Delete failed. Try again, and if the problem persists " +
                "see your system administrator.";
        }

        return View(author);
    }

    // POST: AuthorController/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DeleteConfirmed(int id)
    {
        try
        {
            await _repository.Delete(id);
            return RedirectToAction(nameof(Index));
        }
        catch (DbUpdateException /* ex */)
        {
            //Log the error (uncomment ex variable name and write a log.)
            return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
        }
    }
}

and the connection between your class in the interface in the program class:

builder.Services.AddScoped<IAuthorsDataRepository, AuthorstDataRepository>();

One note: AddSingleton does not work with the data context.

Comments

-1

How about this?

DALAccount.cs

public class DALAccount
{
     private AccountsDBContext _db;
     public DALAccount(AccountsDBContext db)
     {
          _db = db;
     }
     public IQueryable<User> Get()
         => _db.User.AsQueryable();
}

Your Api

public class AccountsController : Controller
{
     private AccountsDBContext _db;

     public AccountsController(AccountsDBContext context)
     {
          this._db = context;
     }
     public IActionResult Index()
     {
          DALAccount dal = new DALAccount(_db);
          var list = dal.Get();
     }
}

1 Comment

OP mentioned, ".. However, I'm not very fond of the idea that the db context will be a member at the controller. ... "

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.