The dbcontext tracking behaviour is set to NoTracking, but I still get a fail in the test.
BaseDbContext in this context does not have any relevant code compared to IdentityDbContext provided by EF. Also is the same with BaseUser, where it is basically only IdentityUser.
Method for creating DbContext:
public static T GetDbContext<T>()
where T : BaseDbContext<BaseUser<Guid>>
{
var optionBuilder = new DbContextOptionsBuilder<T>();
optionBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString());
optionBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
var obj = Activator.CreateInstance(typeof(T), optionBuilder.Options);
if (obj == null)
{
throw new SystemException(typeof(T) + " was null!");
}
var ctx = (T)obj;
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
return ctx;
}
The test, that fails:
[Fact]
public async void Test_UpdateSingle()
{
var dbContext = DbContextFactory.GetDbContext<SimpleDbContext>();
var uow = UowFactory.GetUow<SimpleUow, SimpleDbContext>(dbContext);
var id1 = Guid.NewGuid();
var name1 = Guid.NewGuid().ToString();
var testEntity1 = new DalSimpleEntity
{
Id = id1,
Name = name1
};
uow.SimpleRepo.Add(testEntity1);
await uow.SaveChangesAsync();
var newName = Guid.NewGuid().ToString();
testEntity1.Name = newName;
//Fails here:
uow.SimpleRepo.Update(testEntity1);
await uow.SaveChangesAsync();
Assert.Single(await uow.SimpleRepo.GetAllAsync());
var getEntity1 = await uow.SimpleRepo.FirstOrDefaultAsync(id1);
Assert.Equal(newName, getEntity1?.Name);
}
The UnitOfWork is for using it as a layer on top of the dbcontext.
The SaveChanges method calls directly the DbContext savechanges.
UnitOfWork also contains the reference for Repository.
The SimpleRepo is derived from BaseRepository. Nothing is changed in the SimpleRepo.
public abstract class BaseRepository<TDbContext, TEntityIn, TEntityOut> : BaseRepositoryWebApp<TDbContext, TEntityIn, TEntityOut, Guid>, IBaseRepositoryWebApp<TEntityOut>
where TEntityIn : class, IDomainEntityId, IDomainEntityId<Guid>
where TEntityOut : class, IDomainEntityId, IDomainEntityId<Guid>
where TDbContext : BaseDbContext<BaseUser<Guid>>
{
protected readonly DbContext RepoDbContext;
protected readonly DbSet<TEntityIn> RepoDbSet;
protected readonly IBaseMapper<TEntityIn, TEntityOut> Mapper;
public BaseRepository(TDbContext dbContext, IBaseMapper<TEntityIn, TEntityOut> mapper)
{
RepoDbContext = dbContext;
RepoDbSet = dbContext.Set<TEntityIn>();
Mapper = mapper;
}
public virtual async Task<IEnumerable<TEntityOut>> GetAllAsync(bool noTracking = true, Guid userId = default)
{
var entities = await InitQuery(noTracking, userId).ToListAsync();
return entities.Select(e => Mapper.Map(e)!);
}
public virtual async Task<TEntityOut?> FirstOrDefaultAsync(TKey id, bool noTracking = true, Guid userId = default)
{
var query = InitQuery(noTracking, userId).FirstOrDefaultAsync(e => e.Id.Equals(id));
return Mapper.Map(await query);
}
public virtual async Task<bool> ExistsAsync(TKey id, Guid userId = default)
{
return await InitQuery(userId: userId).AnyAsync(e => e.Id.Equals(id));
}
public virtual async Task<TEntityOut?> RemoveAsync(TKey id, Guid userId = default)
{
var entity = await InitQuery(userId: userId).FirstOrDefaultAsync(e => e.Id.Equals(id));
if (entity == null) return null;
return Mapper.Map(RepoDbSet.Remove(entity).Entity);
}
public TEntityOut Add(TEntityOut? entity)
{
return Mapper.Map(RepoDbSet.Add(Mapper.Map(entity)!).Entity)!;
}
public TEntityOut Update(TEntityOut? entity)
{
return Mapper.Map(RepoDbSet.Update(Mapper.Map(entity)!).Entity)!;
}
protected virtual IQueryable<TEntityIn> InitQuery(bool noTracking = true, Guid userId = default)
{
var query = RepoDbSet.AsQueryable();
if (typeof(IDomainEntityUsers).IsAssignableFrom(typeof(TEntityIn)))
{
query = query.Where(e => (e as IDomainEntityUsers)!.UserId.Equals(userId));
}
if (noTracking)
{
query = query.AsNoTracking();
}
return query;
}
}
My question is, have I forgotten some place, where I should also state to use NoTracking behaviour?
The problem was made difficult with the fact, that every time the entity moved down or up the layers (example: from uow to dbcontext and back, containing 2 mappings here alone).
This ensured that calling
dbContext.Entity(entity).State = EntityState.Detached;
was not an option, because Savechanges was called with the uow object, not dbContext. Also in the real use case the dbContext is out of reach, thus not being able to call functions from dbContext at all.
So I searched for options to detach all objects, that were still attached after saveChanges.
ChangeTracker.Clear();
satisfied this requirement.
The solution for this problem is:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new())
{
var res = await base.SaveChangesAsync(cancellationToken);
ChangeTracker.Clear();
return res;
}
Related
I have a Repository pattern that interacts with Entity Framework.
I'd like to run some unit tests on the repository, and for this reason, I would like to mock DbContext.
So I've created a unit test project (.Net Core 3.1), using Moq as package for unit testing, everything seems to be ok, but when I perform a .ToListAsync() on my repository it throws the following exception:
System.NotImplementedException : The method or operation is not
implemented. Stack Trace:
IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken)
ConfiguredCancelableAsyncEnumerable1.GetAsyncEnumerator() EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1
source, CancellationToken cancellationToken)
The source code:
public class Customer
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class CustomersDbContext : DbContext
{
public virtual DbSet<Customer> Customers { get; set; }
public CustomersDbContext(DbContextOptions<Customer> options) : base(options) { }
}
public interface ICustomerRepository
{
Task<IEnumerable<Customer>> GetCustomersAsync(Guid? customerId);
}
public class CustomerRepository : ICustomerRepository
{
private readonly CustomersDbContext _dbContext;
public CustomerRepository(CustomersDbContext dbContext)
{
_dbContext = dbContext;
_dbContext.Database.EnsureCreated();
}
public async Task<IEnumerable<Customer>> GetCustomersAsync(Guid? customerId)
{
IEnumerable<Customer> customers = null;
if (customerId.HasValue)
{
var customer = await _dbContext.Customers.FindAsync(new object[] { customerId.Value }, CancellationToken.None);
if (customer != null)
customers = new List<Customer>() { customer };
}
else
{
customers = await _dbContext.Customers.ToListAsync(CancellationToken.None);
}
return customers;
}
}
public class CustomerServiceUnitTests
{
private Mock<CustomersDbContext> GetCustomerDbContextMock()
{
var data = new List<Customer>()
{
new Customer()
{
Id = Guid.NewGuid(),
Name = "Name 1"
},
new Customer()
{
Id = Guid.NewGuid(),
Name = "Name 2"
}
}.AsQueryable();
var mockSet = new Mock<DbSet<Customer>>();
mockSet.As<IQueryable<Customer>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Customer>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Customer>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Customer>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
var optionsBuilder = new DbContextOptions<CustomersDbContext>();
var mockContext = new Mock<CustomersDbContext>(optionsBuilder);
Mock<DatabaseFacade> databaseFacade = new Mock<DatabaseFacade>(mockContext.Object);
databaseFacade.Setup(d => d.EnsureCreatedAsync(CancellationToken.None)).Returns(Task.FromResult(true));
mockContext.Setup(c => c.Database).Returns(databaseFacade.Object);
mockContext.Setup(c => c.Customers).Returns(mockSet.Object);
return mockContext;
}
[Fact]
public async Task Infrastructure_CustomerRepository_GetAll()
{
var mockContext = this.GetCustomerDbContextMock();
ICustomerRepository customerRepository = new CustomerRepository(mockContext.Object);
var customers = await customerRepository.GetCustomersAsync(null);
Assert.NotNull(customers);
Assert.Equal(2, customers.Count());
}
}
If I send an ID filled to the repository it works fine, so this seems to be not ok only for .ToListAsync().
I'm kinda stuck here, what can I do to overcome this?
You cannot mock DbSet query functionality. This is explained in the docs:
Properly mocking DbSet query functionality is not possible, since queries are expressed via LINQ operators, which are static
extension method calls over IQueryable. As a result, when some
people talk about "mocking DbSet", what they really mean is that they
create a DbSet backed by an in-memory collection, and then evaluate
query operators against that collection in memory, just like a simple
IEnumerable. Rather than a mock, this is actually a sort of fake,
where the in-memory collection replaces the the real database.
In order to execute Asynchronous read operation (ToListAsync()) you need to mock an additional interface called "IDBAsyncQueryProvider".
Here's is the required link you can follow. It is under the heading "Testing with async queries"
Overview
I am currently unit/integration testing my repository pattern with the in-memory database EF Core provides. I am on version 6.0.1 for the Microsoft.EntityFrameworkCore.InMemory nuget and using Visual Studio 2022. I am running into issues saving data to the database. Below I have included snippets of my code.
My data model:
public class Example : IExample
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
My base repository class:
public class Repository<T> : IRepository<T> where T : class
private readonly DbSet<T> _dbSet;
private readonly DbContext _db;
public Repository(DbContext db)
{
_dbSet = db.Set<T>();
_db = db;
}
public virtual async Task Add(T entity)
{
await _dbSet.AddAsync(entity);
}
public virtual async Task<int> SaveAsync(CancellationToken token = default)
{
return await _db.SaveChangesAsync(token);
}
My repository class:
public class ExampleRepo : Repository<Example>
public ExampleRepo(ExampleContext db) : base(db)
{
}
My DbContext Class
I can show my IEntityConfiguration class for the ExampleBuilder shown below if it is needed but I don't believe that to be the problem.
public class ExampleContext : DbContext
private readonly IHttpContextAccessor? _httpContextAccessor;
public ExampleContext(DbContextOptions<ExampleContext> options) : base(options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
}
public ExampleContext(DbContextOptions<ExampleContext> options, IHttpContextAccessor httpContextAccessor)
: base(options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
public DbSet<Example>? Examples { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
var assembly = Assembly.GetAssembly(typeof(ExampleBuilder));
if (assembly is null)
{
throw new DataException("Could not find the assembly containing the designated model builder");
}
builder.ApplyConfigurationsFromAssembly(assembly);
foreach (var entity in builder.Model.GetEntityTypes())
{
entity.SetSchema("dbo");
}
}
public override Task<int> SaveChangesAsync(CancellationToken token = default)
{
foreach (var entry in ChangeTracker.Entries()
.Where(x => x.State is EntityState.Added or EntityState.Modified or EntityState.Deleted))
{
var user = _httpContextAccessor?.HttpContext?.User?.Identity?.Name ?? "User";
entry.Property("ModifiedBy").CurrentValue = user;
if (entry.State == EntityState.Added)
{
entry.Property("CreatedBy").CurrentValue = user;
}
if (entry.State != EntityState.Deleted) continue;
entry.State = EntityState.Modified;
entry.Property("IsActive").CurrentValue = false;
}
return base.SaveChangesAsync(token);
}
My DbContext factory method:
private ExampleContext GenerateDbContext()
{
var options = new DbContextOptionsBuilder<ExampleContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new ExampleContext(options);
}
My unit/integration test utilizing xUnit and NET6.0
[Fact]
public async Task GetAllEntities_ShouldReturnEntities_WhenEntitiesExist()
{
// Arrange
// I used Bogus nuget for single source for generating valid models
var entity = ExampleFaker.GetModelFaker()
.Generate();
await using var context = GenerateDbContext();
var repo = new ExampleRepo(context);
await repo.Add(entity);
var changes = await repo.SaveAsync();
// Act
// Consulted this link already. Bottom answer is most related
//https://stackoverflow.com/questions/46184937/dbcontext-not-returning-local-objects
var response = await repo.GetAll();
// Assess
TestOutputHelper.WriteLine($"Added entity to repository: {entity.ToJson()}");
TestOutputHelper.WriteLine("Expected entities saved: 1");
TestOutputHelper.WriteLine($"Actual entities saved: {changes}");
TestOutputHelper.WriteLine($"Response: {response?.ToJson()}");
// Assert
Assert.Equal(1, changes);
Assert.NotNull(response);
Assert.NotEmpty(response);
Assert.IsType<List<Example>>(response.ToList());
}
Analysis & Issue
The changes variable returns 1 so I interpret this as EF does not have any issue with my model as well as I would think it successfully saved my model in the in-memory database. However, during my GetAll retrieval, no data is returned. When I debug and look into the repository private members, it shows the DbSet is empty so it is not the GetAll method causing the issue either. Since this is also just within the scope of the unit test, I don't think my Program.cs configuration has anything to do with the issue I am seeing. I have been looking at this for quite a while and can't figure out the small detail I am probably missing for the life of me.
Thank you for your help in advance.
I got the following error when I try to test an update operation using Entity Framework core:
System.InvalidOperationException : The instance of entity type 'Companies' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
After doing some research, I tried everything that I found:
Create in scope DB context
deattach and attached the object I want to update from the DB context
Return the object to be updated using "AsNoTracking()" , my repository actually do this.
For the testing I am using EF in-memmory database with it fixture, I am using XUnit and .NET 5.
Can I get any help with this please?
Here is my code:
// The repository I am trying to test
public class RepositoryBase<T> : ICrudRepository<T> where T : class, IModel
{
protected PrjDbContext DatabaseContext { get; set; }
public RepositoryBase(PrjDbContext databaseContext) => DatabaseContext = databaseContext;
protected IQueryable<T> FindAll() => DatabaseContext.Set<T>().AsNoTracking();
protected IQueryable<T> FindBy(Expression<Func<T, bool>> expression) => DatabaseContext.Set<T>().Where(expression).AsNoTracking();
public void Create(T entity) => DatabaseContext.Set<T>().Add(entity);
public void Update(T entity) => DatabaseContext.Set<T>().Update(entity);
public void Delete(T entity) => DatabaseContext.Set<T>().Remove(entity);
public async Task<IEnumerable<T>> ReadAllAsync() => await FindAll().ToListAsync().ConfigureAwait(false);
public async Task<T> ReadByIdAsync(int id) => await FindBy(entity => entity.Id.Equals(id)).FirstOrDefaultAsync().ConfigureAwait(false);
}
//The Database context
public partial class PrjDbContext : DbContext
{
public PrjDbContext()
{
}
public PrjDbContext(DbContextOptions<PrjDbContext> options)
: base(options)
{
}
public virtual DbSet<Companies> Companies { get; set; }
}
// This is my fixture with the in-memory Database
public sealed class PrjSeedDataFixture : IDisposable
{
public PrjDbContext DbContext { get; }
public PrjSeedDataFixture(string name)
{
string databaseName = "PrjDatabase_" + name + "_" + DateTime.Now.ToFileTimeUtc();
DbContextOptions<PrjDbContext> options = new DbContextOptionsBuilder<PrjDbContext>()
.UseInMemoryDatabase(databaseName)
.EnableSensitiveDataLogging()
.Options;
DbContext = new PrjDbContext(options);
// Load Companies
DbContext.Companies.Add(new Companies { Id = 1, Name = "Customer 1", Status = 0, Created = DateTime.Now, LogoName = "FakeLogo.jpg", LogoPath = "/LogoPath/SecondFolder/", ModifiedBy = "Admin" });
DbContext.Companies.AsNoTracking();
DbContext.SaveChanges();
}
public void Dispose()
{
DbContext.Dispose();
}
}
The test method "Update_WhenCalled_UpdateACompanyObject", is not working for me.
// And finally, this is my test class, Create_WhenCalled_CreatesNewCompanyObject pass the test, but Update_WhenCalled_UpdateACompanyObject isn't passing the test.
public class RepositoryBaseCompanyTests
{
private Companies _newCompany;
private PrjDbContext _databaseContext;
private RepositoryBase<Companies> _sut;
public RepositoryBaseCompanyTests()
{
_newCompany = new Companies {Id = 2};
_databaseContext = new PrjSeedDataFixture("RepositoryBase").DbContext;
_sut = new RepositoryBase<Companies>(_databaseContext);
}
[Fact]
public void Create_WhenCalled_CreatesNewCompanyObject()
{
//Act
_sut.Create(_newCompany);
_databaseContext.SaveChanges();
//Assert
Assert.Equal(2, _databaseContext.Companies.Where( x => x.Id == 2).FirstOrDefault().Id);
}
[Fact]
public async void Update_WhenCalled_UpdateACompanyObject()
{
//Arrange
var company = await _sut.ReadByIdAsync(1);
company.Name = "Customer 2";
//_databaseContext.Entry(company).State = EntityState.Detached;
//_databaseContext.Attach(company);
//_databaseContext.Entry(company).State = EntityState.Modified;
//Act
_sut.Update(company);
await _databaseContext.SaveChangesAsync();
//Assert
Assert.Equal("Customer 2", _databaseContext.Companies.Where(x => x.Id == 1).FirstOrDefault().Name);
}
}
If you are using EF Core 5.0 then call DbContext.ChangeTracker.Clear() (or go through DbContext.Entries collection and set state to Detached for earlier ones) after DbContext.SaveChanges(); in PrjSeedDataFixture ctor. Adding/Updating an entry makes it tracked and you are reusing the context that created an entry with Id = 1, so when _sut.Update(company); is called it will try to track it again (since ReadByIdAsync should return an untracked one).
P.S.
Adding an extra repository abstraction layer around EF can be considered as antipattern (because EF already implements repository/UoW patterns) and the issue you are having can be one of the examples of why that is true and why this abstraction can be a leaky one. So if you still decide that having one is a good idea - you need to proceed with caution.
I have API built with .net core 2, and I am trying to implement change log feature.
I have done basic part, but I am not sure if it's a best way for doing this.
Here is my EntityBaseRepository
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IFullAuditedEntity, new()
{
private readonly ApplicationContext context;
public EntityBaseRepository(ApplicationContext context)
{
this.context = context;
}
public virtual IEnumerable<T> items => context.Set<T>().AsEnumerable().OrderByDescending(m => m.Id);
public virtual T this[int id] => context.Set<T>().FirstOrDefault(m => m.Id == id);
public virtual T GetSingle(int id) => context.Set<T>().FirstOrDefault(x => x.Id == id);
public virtual T Add(T entity) => Operations(entity: entity, state: EntityState.Added);
public virtual T Update(T entity) => Operations(entity: entity, state: EntityState.Modified);
public virtual T Delete(T entity) => Operations(entity: entity, state: EntityState.Deleted);
public virtual T Operations(T entity, EntityState state)
{
EntityEntry dbEntityEntry = context.Entry<T>(entity);
if (state == EntityState.Added)
{
entity.CreationDateTime = DateTime.UtcNow;
entity.CreationUserId = 1;
context.Set<T>().Add(entity);
dbEntityEntry.State = EntityState.Added;
}
else if (state == EntityState.Modified)
{
entity.LastModificationDateTime = DateTime.UtcNow;
entity.LastModificationUserId = 1;
var local = context.Set<T>().Local.FirstOrDefault(entry => entry.Id.Equals(entity.Id));
if (local != null)
{
context.Entry(local).State = EntityState.Detached;
}
dbEntityEntry.State = EntityState.Modified;
}
else if (state == EntityState.Deleted)
{
entity.DeletionFlag = true;
entity.DeletionUserId = 1;
entity.DeletionDateTime = DateTime.UtcNow;
dbEntityEntry.State = EntityState.Modified;
}
return entity;
}
Here is one of my controller.
[Produces("application/json")]
[Route("api/Item")]
public class ItemController : Controller
{
private readonly IItemRepository repository;
private readonly IChangeLogRepository changeLogRepository;
private readonly IMapper mapper;
public ItemController(IItemRepository repository, IChangeLogRepository _changeLogRepository, IMapper mapper)
{
this.repository = repository;
this.changeLogRepository = _changeLogRepository;
this.mapper = mapper;
}
[HttpPost]
public IActionResult Post([FromBody]ItemDto transactionItemDto)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var item = repository.Add(mapper.Map<ItemDto, Item>(source: transactionItemDto));
repository.Commit();
ChangeLog log = new ChangeLog()
{
Log = "New Item Added"
};
changeLogRepository.Add(log);
changeLogRepository.Commit();
return new OkObjectResult(mapper.Map<Item, ItemDto>(source: item));
}
}
if you see in controller, I have added one item, and commited it, then I prepared log for that insertion, added and commited it.
Now, I have few questions, like
I have to commit my transaction twice, is there any way I can optimize it? I don't know if I can handle it on EntityBaseRepository or not.
I also want to check each property, if it gets changed or not. I want to log that to if it's changed. what would be the best way to handle it?
It would be great if anyone can help me with this. really appreciate. thanks.
You can use Action filters for a changelog Like
using System;
public class TrackMyChange : IActionFilter
{
private readonly string _chengeMessage;
private readonly IChangeLogRepository _changeLogRepository;
public TrackMyChange(string changeMessage,IChangeLogRepository changeLogRepository)
{
this._changeLogRepository = changeLogRepository;
this._chengeMessage = chengeMessage;
}
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
ChangeLog log = new ChangeLog()
{Log = this._chengeMessage};
changeLogRepository.Add(log);
changeLogRepository.Commit();
}
}
In your controller, you can use it before actions you want to log like
[TrackMyChange("Your change log here")]
public IActionResult Post()
{
}
Reference :
Action Filter Attributes in .NET core
DbContext is shared between multiple repositories within the same HTTP request scope. You won't need UoW. Just try use one Commit() and see if all changes are saved in one transaction.
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-implemenation-entity-framework-core
Use yourDbContext.Entry(your_entity_obj).State to get entity state. Don't log if its state is EntityState.Unchanged.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entitystate?view=efcore-2.1
I'm reading a lot about Entity Framework async lately, and I'm looking for the correct way to change my repositories to async versions. They now look more or less as follows:
public class MyRepo
{
private DbContext _context;
public MyRepo()
{
_context = new DbContext();
}
public int DoSomeStuff(int id)
{
var item = _context.MyModels.Where(x => x.Id == id).SingleOrDefault();
item.MyProperty = "abcd";
return _context.SaveChanges();
}
}
Example of calling this repository:
var myRepo = new MyRepo();
myRepo.DoSomeStuff(1);
myRepo.DoSomeStuff(2);
Can I just change the DoSomeStuff function to:
public async Task<int> DoSomeStuffAsync(int id)
{
var item = await _context.MyModels.Where(x => x.Id == id).SingleOrDefaultAsync();
item.MyProperty = "abcd";
return await _context.SaveChangesAsync();
}
or is this the wrong way of handling the DbContext instance, and should it be:
public async Task<int> DoSomeStuffAsync(int id)
{
using(var db = new DbContext())
{
// ...
}
}
You can do it either way, depending on the semantics you want. If you're using a shared DbContext, you just have to document that so your callers will only call one asynchronous method at a time.