Fernando Carvajal
Fernando Carvajal

Reputation: 1945

Add DbContext to all Classes not just controllers in .NET Core

I want do add my current DbContext to whatever Class I use, for the moment I'm just passing the _context like a common variable.

In this controller example I Pass the _context every time i want to create a Item

[HttpPost("receta/{id}")]
[APIauth("medico")]
public IActionResult PostItemsReceta(int id, [FromBody]Data[] items) {
    var tran = _context.Database.BeginTransaction(); //DBB transaction begins
    try {
        foreach (Data item in items)
            new Item(id, item, _context); //I pass the _context like this.

        tran.Commit();
        return Ok();

    } catch (Exception e) {
        tran.Rollback();
        return BadRequest("Not inserted!!");
    }            
}

And in the Class Item I have this

public class Item
{
    [Key]
    public int id { get; set; }
    public DateTime? fcaducidad { get; set; }
    public string nombre { get; set; }
    public Int32 diagnostico { get; set; }

    public Item() { }

    public Item (int receta, Data i, MyDbContext _context) {
        try {
            var q = $"EXEC ItemReceta_Insert @idItemFarmacia={i.itemFarmacia.id}" +
            $", @idDiagnostico={i.diagnostico}, @cantidad={i.cantidad}" +
            $", @receta={receta}, @posologia='{i.posologia}'";

            _context.Database.ExecuteSqlCommand(q); 
        } catch (Exception e) {
            throw new Exception("Error al insertar ItemReceta", e);
        }            
    }

    public static Item[] Report( int receta, MyDbContext _context) 
    {
        string q = $"EXEC Item_Report @receta={receta}";
        return _context.Item.FromSql(q).ToArray();
    }
}

I don't want just to have direct access to the context in the controllers because I'm using it many times, I want

new Item(id, item);

Not

new Item(id, item, _context);
Item.Report(bla, _context);
new WhatEverClass(name, age, _context);

Upvotes: 2

Views: 5189

Answers (2)

Doug
Doug

Reputation: 35106

Just use using (var db = new DbContext()) { ... } inside your constructor.

This will totally work:

public class AppDbContext : DbContext
{ 
  public DbSet<Item> Items { get; set; }
}

public class Data
{
  public int Id { get; set; }
  public string Value { get; set; }
}

public class Item
{
  public Item()
  {
  }

  public Item(IEnumerable<Data> data)
  {
    Data = data.ToList();
    using (var db = new AppDbContext())
    {
      db.Items.Add(this);
      db.SaveChanges();
    }
  }

  public override string ToString()
  {
    return Data != null ? $"Item:{Id}, Data:{string.Join(',', Data.Select(i => i.Value))}" : $"Item:{Id}, Data:None";
  }

  public int Id { get; set; }
  public ICollection<Data> Data { get; set; }
}

...

var i1 = new Item(new[] {new Data {Value = "1"}, new Data {Value = "2"}});
var i2 = new Item(new[] {new Data {Value = "3"}, new Data {Value = "4"}});

using (var db = new AppDbContext())
{
  db.Items.Include(i => i.Data).ToList().ForEach(Console.WriteLine);
}

There are plenty of reasons why this is a bad idea (unable to create items with persisting them, need default constructor anyway for ef, cannot perform bulk inserts easily, high coupling, impossible to test), but it seems like my solution is exactly your solution, so I'm not sure if that helps?

For what its worth note that due to connection pooling it is better and more performant to create a new DbContext with:

using (var db = new AppDbContext())
{
    ...
}

Than it is to have a singleton object that you pass around manually or using DI; this is because the context internally caches query results, and you're basically having a slow memory leak if you use it as a singleton.

ie. This: public Item (int receta, Data i, MyDbContext _context) is actually tangibly worse than several using (var db = new AppDbContext() { ... } unless you're specifically using the result cache.

Upvotes: -1

Keith Nicholas
Keith Nicholas

Reputation: 44288

I think you aren't really separating your concerns, there's a number of approaches to this, however I'll present a very simple approach to getting things injected into your own classes (I'll leave out some of the properties in your solution to keep it simple ). The main thing you need to do is register your class as a service. So..

first, make your Item a POCO

 public class Item
    {
        public int Id { get; set; }
    }

now put your Code related to how you are trying to put items in and out of the database into a service, make the constructor take your DbContext so that it will get injected

public class ItemService 
{
    private readonly ApplicationContext _context;

    public ItemService(ApplicationContext context)
    {
        _context = context;
    }

    public void Add(Item item)
    {
        // A paramatized query of some sort to prevent sql injection
        // see https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/advanced
        var parameters = new List<object>();
        _context.Database.ExecuteSqlCommand("", parameters);
    }
}

now, where you configure your services add your Item Service

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationContext>();
            services.AddScoped<ItemService>();         
        }

now in your controllers constructor say you want the service,

private readonly ItemService _itemService
public MyFancyPantsController(ItemService itemService)
{
  _itemService = itemService;
}

now on your post you can access the service

 [Route("post")]
    [HttpPost()]        
    public IActionResult PostItem()
    {
        // construct / deserialize items, then add them...
        _itemService.Add(new Item());
        return Ok();
    }

Now....you may find that you have a lot of Entities ( like Item ) and creating services for them all is a PITA, so you may want are more generic Repository service.

Upvotes: 9

Related Questions