In-memory database doesn't save data

asp.net-core c# entity-framework-core

Question

I have a simple web-app with angular on client-side and asp.net core web-api on server-side. I use InMemoryDatabase

services.AddDbContext<ItemsContext>(options => options.UseInMemoryDatabase("ItemsDB"));

to store data for the simplisity of the development. But I've encountered an issue with that. I have one controller on web-api to response for users' requests:

[Route("api/[controller]")]
public class ItemsController : Controller
{
    private readonly IApiService apiService;

    public ItemsController(IApiService apiService)//using DI from Startup.cs
    {
       this.apiService = apiService;
    }

    [HttpPost, Route("addItem")]
    public async Task<Response> Add([FromBody]Item item)
    {
        return await apiService.Add(item);
    }

    [HttpDelete("{id}")]
    public async Task<Response> Delete(int id)
    {
        return await apiService.Delete(id);
    }

    [HttpPut]
    public async Task<Response> Put([FromBody]Item item)
    {
         return await apiService.Put(item);
    }
}

and the following Startup.cs configurations:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddDbContext<ItemsContext>(options => options.UseInMemoryDatabase("ItemsDB"));
    services.AddSingleton<IUnitOfWork, UnitOfWork>(provider => {
        var context = services.BuildServiceProvider().GetService<ItemsContext>();
        return new UnitOfWork(context);
    });
    services.AddSingleton<IApiService, ApiService>(provider => {
        return new ApiService(services);
    });
}

The problem is, that when I add new item, everything goes just fine...but then I post another request to delete this item it may show there there is no such an item at all or sometimes it may delete it...so in other words, the database exists and then disappears and I'm not sure when. Here is some additional code refering to the above

public class ApiService: IApiService
{
    private readonly IUnitOfWork database;
    private readonly IServiceProvider provider;

    public ApiService(IServiceCollection serviceCollection)
    {
        provider = serviceCollection.BuildServiceProvider();
    }

    public IUnitOfWork Database 
    { 
        get 
        {
            return provider.GetService<IUnitOfWork>();
        }
    }

    public async Task<Response> Add(Item item)
    {
        Database.Items.Add(item);
        await Database.SaveAsync();

        var id = Database.Items.LastItem().Id;
        return new Response() { Result = true, ItemId = id };
    }

    public async Task<Response> Delete(int id)
    {
        var item = await db.Items.Find(id);
        Database.Items.Remove(item);
        await Database.SaveAsync();

        return new Response() { Result = true };
    }

    public async Task<Response> Put(Item item)
    {
        Database.Items.Update(item);
        await Database.SaveAsync();
        return new Response() { Result = true };
    }
}

Update: UnitOfWork Implementation:

 public class UnitOfWork: IUnitOfWork
{
    private readonly DbContext context;
    private IRepository<Item> itemsRepository;

    public UnitOfWork(DbContext dbContext)
    {
        context = dbContext;
    }

    public IRepository<Item> Items
    {
        get
        {
            return itemsRepository ?? (itemsRepository = new Repository<Item>(context));
        }
    }

    public void Dispose()
    {
        context.Dispose();
    }

    public void Save()
    {
        context.SaveChanges();
    }

    public async Task SaveAsync()
    {
        await context.SaveChangesAsync();
    }
}
1
1
5/25/2018 9:31:44 AM

Accepted Answer

Your code has multiple serious problems, let's go through them.

  1. services.AddDbContext adds a Scoped service, meaning that instances will be created and disposed on each request. services.AddSingleton adds a Singleton service, so only a single instance will ever be created. You cannot add a scoped service to a singleton one, because the reference the singleton service uses will be disposed and you will end up with a disposed context.
  2. This code:

    return provider.GetService<IUnitOfWork>();
    

    represents the service locator anti-pattern. As you can guess, an anti-pattern is something you want to avoid. I also don't know why you would want a service to build the entire DI container nor why you would want a service to have the responsibility of getting the dependencies it needs itself.

  3. This part here is where your question actually comes from:

    Database.SaveAsync();
    

    You are calling an asynchronous function and not awaiting for it to finish. The task may finish or not, it may throw an error or not, you will never know what happened.

The best thing is that all of these could be avoided if people stopped attempting to create a Unit of Work + Repository pattern over yet another Unit of Work and Repository. Entity Framework Core already implements these:

DbContext => Unit of Work
DbSet => Repository (generic)

Why do you want yet another abstraction? Will you really ever throw away EF Core from the project to justify the maintenance cost of your code?

The entire question code could have just been this:

[Route("api/[controller]")]
public class ItemsController : Controller
{
    private readonly YourContext _context;

    public ItemsController(YourContext context)
    {
       _context = context;
    }

    [HttpPost]
    public async Task<IActionResult> Add([FromBody]Item item)
    {
        context.Items.Add(item);
        await context.SaveChangesAsync();

        return Ok(item.Id);
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(int id)
    {
        context.Items.Remove(item);
        await context.SaveChangesAsync();

        return Ok();
    }

    [HttpPut]
    public async Task<IActionResult> Put([FromBody]Item item)
    {
        context.Items.Update(item);
        await context.SaveChangesAsync();

        return Ok();
    }
}
5
5/21/2018 11:31:55 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow