I have a WebApi controller that has services injected by AutoFac in the OWIN Startup class
builder.Register(c => new MyEntities()).InstancePerRequest();
I have also tried
builder.Register(c => new MyEntities()).InstancePerLifetimeScope();
In a controller action I call a service method to create a new record, pass the id created to an external api through HttpClient to get some more data, then update the new record with some return data.
[HttpPost, Route("")]
public async Task<IHttpActionResult> MyControllerAction(MyModel model)
{
var id = await _MyService.CreateNewThing(model.SomeId);
var externalData = await CallExternalApiThroughHttpClient(id);
await _MyService.UpdateNewThing(id, externalData);
return Ok();
}
service code
public class MyService : IMyService
{
private MyEntities _context;
public MyService(MyEntities context)
{
_context = context;
}
public async Task<int> CreateNewThing(int someId)
{
var thing = new Thing
{
SomeId = someId
};
_context.Things.Add(thing);
await _context.SaveChangesAsync();
return thing.Id;
}
public async Task UpdateNewThing(int id, string externalDataField)
{
var thing = await _context.Things.SingleOrDefaultAsync(o => o.Id == id);
if (thing == null)
{
throw new ServiceNotFoundException("Thing " + transactionId + " not found");
}
thing.ExternalDataField= externalDataField;
await _context.SaveChangesAsync();
}
}
But I get an InvalidOperationException in UpdateNewThing var thing = await _context.Things.SingleOrDefaultAsync(o => o.Id == id);
System.InvalidOperationException: The connection was not closed. The connection's current state is connecting.
It seems like I have to give up either injecting the context, async/await or use something like a contextfactory; unless anyone can spot something simple I have missed that would let me continue with this design.
Your code looks fine in a single-threaded context. However, DbContext is not thread safe, and I suspect what is happening is you're executing CreateNewThing() on one thread, and the task scheduler is in this case executing UpdateNewThing() on a different thread.
Either way, a better metaphor is to use a context factory, which you inject into your IMyService in this case, and then for every IMyService method you create a new MyEntities context in a using() block.
DbContext's are cheap to create and this is how they are intended to be used; long-lived contexts are almost always incorrect usage.
Edit 1 - example context factory as requested. I tend to implement a generic factory that can create multiple contexts, but that's probably moving outside the scope of this question.
public interface IMyEntitiesFactory
{
MyEntities Create();
}
public class MyEntitiesFactory : IMyEntitiesFactory
{
MyEntities IMyEntitiesFactory.Create()
{
return new MyEntities();
}
}
// For use with unit tests; e.g. pass a mock object to the constructor.
public class TestMyEntitiesFactory : IMyEntitiesFactory
{
private readonly MyEntities _value;
public TestMyEntitiesFactory(MyEntities value)
{
_value = value;
}
MyEntities IMyEntitiesFactory.Create()
{
return _value;
}
}
Related
There is a problem with async executing query to web api using entity framework.
General view such that a request is sent to API and ActionFilter catch request to function of controller, send to client status ok with response key, execute request async and after send data by SignalR.
ActionFilter starts async executing like this:
HostingEnvironment.QueueBackgroundWorkItem(async (ct) =>
{
var response = await actionContext.ActionDescriptor.ExecuteAsync(actionContext.ControllerContext,
actionContext.ActionArguments, ct);
var data = new JavaScriptSerializer().Serialize(response);
await connectionContext.Connection.Send(connectionId, $"{requestKey};{data}");
});
Controller:
[HttpPost]
[Route("")]
public ICollection<TradeAccountModel> GetAll()
{
using (var ls = _lifetimeScope.BeginLifetimeScope())
{
return _tradeAccountService.GetAll();
}
}
Service:
public ICollection<TradeAccountModel> GetAll()
{
using (_tradeAccountRepository.BeginTransaction())
{
return _tradeAccountRepository.Get().Select(acc => acc.ToModel());
}
}
Respository uses UOW pattern.
And when repository try to get data from DB there is error: System.InvalidOperationException: The operation cannot be completed because the DbContext has been disposed.
TDataRepository containts common operations and extends BaseDataRespository, such as GetById and ect
public interface ITradeRepository: ITDataRepository<TradeAccount>
{
}
internal class TradeRepository : T1DataRepository<TradeAccount>,
ITradeRepository
{
}
IEnumerable<TEntity> ITDataRepository<TEntity>.Get()
{
return base.Get<TEntity>();
}
BaseDataRespository has BeginTransaction method
public IDisposable BeginTransaction()
{
if (_scope == null)
{
_scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions()
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TimeSpan.FromSeconds(300)
},
TransactionScopeAsyncFlowOption.Enabled);
}
return _scope;
}
Context creates by BaseDataRespository
private TransactionScope _scope;
private readonly Lazy<DataContext> _contextFactory;
private DataContext Context => _contextFactory.Value;
public BaseDataRepository()
{
_contextFactory = new Lazy<DataContext>(()=>
{
var ctx = CreateContext();
ctx.FireBuild += Build;
return ctx;
});
}
If your DbContext is registered as scoped service / HTTP request scoped service, you should refrain from passing DbContext from your request pipeline to your background task.
This is because as per the docs:
Schedules a task which can run in the background, independent of any request.
Any scoped services that also implement IDisposable will be automatically disposed after the request completes.
You should activate new DbContext using an independent scope in your controller action / request pipeline, and pass it to your background task.
This docs is for .Net Core, but it may give you ideas how scoped services could be used in a background task.
This question already has answers here:
Entity Framework Core: A second operation started on this context before a previous operation completed
(20 answers)
Closed 4 years ago.
I'd want to know how why creating instances of other classes with current database context instances as a parameter and using that db context causes this exception to be raised
'A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
Imma use this sample code to show the problem
public class TestController : Controller
{
private readonly DbContext dbContext;
public Controller(DbContext ctx)
{
dbContext = ctx;
}
public async Task<IActionResult> Test(string id)
{
var isValid = new otherClass(dbContext).Validate(id);
if (!isValid)
{
return View("error");
}
var user = dbContext.Users.FirstOrDefault(x => x.Id == id);
user.Age++;
dbContext.SaveChanges(); // exception is being raised here. It is second .SaveChanges() here
return View();
}
}
public class otherClass
{
private readonly DbContext dbContext;
public otherClass(DbContext ctx)
{
dbContext = ctx;
}
public bool Validate(string id)
{
var user = dbContext.Users.FirstOrDefault(x => x.Id == id);
user.ValidationAttempt = DateTime.Now;
dbContext.SaveChanges();
return user.HasConfirmedEmail;
}
}
Generally in an MVC fashion youre going to want a DbContext on a per request basis but when using threading more control through using blocks can be beneficial, an easy way to set that up would be something along the lines of
public class TestController : Controller
{
private readonly Func<DbContext> dbContext;
public Controller(Func<DbContext> ctx)
{
dbContext = ctx;
}
public async Task<IActionResult> Test(string id)
{
using(var cntx = dbContext())
{
var isValid = new otherClass(cntx).Validate(id);
if (!isValid)
{
return View("error");
}
var user = cntx.Users.FirstOrDefault(x => x.Id == id);
user.Age++;
cntx.SaveChanges();
return View();
}
}
}
that essentially resolves a new DbContext per using block - and since each thread is then handling its own DbContext - shouldnt have any issues
I am developing an app that should manage teambuildings, and I am using .NET Core and EF Core for my backend, together with Autofac for dependency injection. In my page, after I get all my teambuildings in a list from the backend, and then I try to modify the values for one of them, I get the following error:
The instance of entity type 'TeamBuilding' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values
Here are the classes and methods I use:
Controller
[Produces("application/json")]
[Route("api/teamBuildings")]
public class TeamBuildingController : Controller
{
public ITeamBuildingService _service;
public TeamBuildingController(ITeamBuildingService serviceTeam)
{
_service = serviceTeam;
}
[HttpPost]
public IActionResult Create([FromBody]TeamBuildingForCreationDto teamBuilding)
{
try
{
var existingTb = _service.GetByID(teamBuilding.Id);
if (existingTb != null)
{
return BadRequest("An entry with this id already exists");
}
_service.Create(teamBuilding);
return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
[HttpGet]
public IActionResult GetAll()
{
var teamBuildings = _service.GetAll();
if (teamBuildings == null)
{
return NotFound("There are no team buidings");
}
return Ok(teamBuildings);
}
[HttpGet("{id}")]
public IActionResult GetTeambuilding(int id)
{
var teamBuilding = _service.GetByID(id);
if (teamBuilding == null)
{
return NotFound("There is no team buiding with such an ID");
}
return Ok(teamBuilding);
}
[HttpPut]
public IActionResult UpdateTeamBuilding([FromBody]TeamBuildingViewModel viewModel)
{
try
{
var existingTeamBuilding = _service.GetByID(viewModel.Id);
if (existingTeamBuilding == null)
{
return NotFound("There is no team buiding with such an ID");
}
_service.UpdateTeamBuilding(viewModel);
return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
Service
public class TeamBuildingService : ITeamBuildingService
{
private IGenericRepository<DAL.Models.TeamBuilding> _repositoryTeam;
public TeamBuildingService(IGenericRepository<DAL.Models.TeamBuilding> repositoryTeam)
{
_repositoryTeam = repositoryTeam;
}
public TeamBuildingDetailsViewModel GetByID(int id)
{
var teamBuilding = _repositoryTeam.GetByID(id);
var viewModel = Mapper.Map<TeamBuildingDetailsViewModel>(teamBuilding);
return viewModel;
}
public IEnumerable<TeamBuildingViewModel> GetAll()
{
//code which returns all the teambuilding from the database, omitted on purpose
}
public TeamBuildingViewModel UpdateTeamBuilding(TeamBuildingViewModel teamBuildingViewModel)
{
var teamBuilding = Mapper.Map<DAL.Models.TeamBuilding>(teamBuildingViewModel);
_repositoryTeam.Edit(teamBuilding);
_repositoryTeam.Commit();
return teamBuildingViewModel;
}
}
}
Repository
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
public DbContext _context;
public DbSet<T> dbset;
public GenericRepository(DbContext context)
{
_context = context;
dbset = context.Set<T>();
}
public IQueryable<T> GetAll()
{
return dbset;
}
public T GetByID(params object[] keyValues)
{
return dbset.Find(keyValues);
}
public void Edit(T entity)
{
_context.Entry(entity).State = EntityState.Modified;
}
public void Insert(T entity)
{
dbset.Add(entity);
}
public void Delete(T entity)
{
_context.Entry(entity).State = EntityState.Deleted;
}
public T GetByFunc(Func<T, bool> func)
{
return dbset.AsQueryable().Where(x => func(x)).FirstOrDefault();
}
public void Commit()
{
_context.SaveChanges();
}
}
The Dependency Injection part
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterType<UserController>();
builder.RegisterType<TeamBuildingController>();
builder.RegisterType<UserService>().As<IUserService>();
builder.RegisterType<TeamBuildingService>().As<ITeamBuildingService>();
builder.RegisterType<TeamBuildingContext>().As<DbContext>().InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(GenericRepository<>))
.As(typeof(IGenericRepository<>));
this.ApplicationContainer = builder.Build();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(this.ApplicationContainer);
To detail the problem more exactly, I do the following things:
make a GET request to get all the teambuildings
from the same browser, server instance and immediately after, I try to modify some of the fields on a random teambuilding by making a PUT request
I get the error shown above
I know one of the solutions is to get the object I want to update from the database first , then on that object to modify its fields with the new values, then pass it to the update function.
But shouldn't the request, according to my code, create a new context, then after the request is done and the response was given to the client the context to be disposed, and for a new request a completely new context that has no information about the previous one be created? As I see it now, I create a context with the GET request, then the context is reused by the PUT request, hence the "Cannot be tracked" error.
What am I doing wrong, and if everything is actually ok, is the method of getting the object after the Id first the good practice?
Edit: I just noticed your GetById method returns a viewmodel. You must manipulate the entity like that
var teamBuilding = _repositoryTeam.GetByID(id);
Mapper.Map(teamBuildingViewModel, teamBuilding);
_repositoryTeam.Edit(teamBuilding);
_repositoryTeam.Commit();
It is this line here
var teamBuilding = Mapper.Map<DAL.Models.TeamBuilding>(teamBuildingViewModel);
This creates a new instance of the object Teambuilding. You need to load the existing one as you do in the controller (which should not be done there anyway). Do it like that from your service-class:
var teamBuilding = this.GetByID(viewModel.Id);
Mapper.Map(teamBuildingViewModel, teamBuilding);
_repositoryTeam.Edit(teamBuilding);
_repositoryTeam.Commit();
Now the object that is being tracked by the dbcontext is the same and update will work just fine. The way you are doing it now it would try to create a new row in the database. This is related to the change-tracking of ef-core.
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();
}
}
Your code has multiple serious problems, let's go through them.
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.
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.
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)
{
var item = await context.Items.FindAsync(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();
}
}
I got two console applications which calls my webapi the same time and I get back in the console application the follow response from my api:
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
So they call at the same time my webapi and then something inside the webapi cannot handle those 2 async calls so this error is returned.
I checked all my code on the webapi project and all methods are async and got await so I cannot see why I get this.
Here is the code of the webapi.
Controller:
public class FederationsController : ApiController
{
private readonly IFederationRepository _federationRepository;
public FederationsController(IFederationRepository federationRepository)
{
_federationRepository = federationRepository;
}
[HttpGet]
[Route("federations", Name = "GetFederations")]
public async Task<IHttpActionResult> GetFederations()
{
var federations = await _federationRepository.GetAllAsync();
return Ok(federations.ToModel());
}
}
Repository
public class FederationRepository : IFederationRepository, IDisposable
{
private Models.DataAccessLayer.CompetitionContext _db = new CompetitionContext();
#region IQueryable
private IQueryable<Models.Entities.Federation> FederationWithEntities()
{
return _db.Federations.Include(x => x.Clubs)
.Where(x => !x.DeletedAt.HasValue && x.Clubs.Any(y => !y.DeletedAt.HasValue));
}
#endregion IQueryable
public async Task<IEnumerable<Models.Entities.Federation>> GetAllAsync()
{
return await FederationWithEntities().ToListAsync();
}
}
Mapper
public static class FederationMapper
{
public static List<Federation> ToModel(this IEnumerable<Models.Entities.Federation> federations)
{
if (federations == null) return new List<Federation>();
return federations.Select(federation => federation.ToModel()).ToList();
}
public static Federation ToModel(this Models.Entities.Federation federation)
{
return new Federation()
{
Name = federation.Name,
FederationCode = federation.FederationCode,
CreatedAt = federation.CreatedAt,
UpdatedAt = federation.UpdatedAt
};
}
}
DbContext
public class CompetitionContext : DbContext
{
public CompetitionContext() : base("ContextName")
{
}
public DbSet<Federation> Federations { get; set; }
}
UnityConfig
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType<IFederationRepository, FederationRepository>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
}
Thank you for all the advices/help.
In your repository you are creating a single CompetitionContext and reusing it. I'm assuming that IoC setup is registring the repository as some kind of single instance, so the same repository is getting used every time. If that's the case you should create a new CompetitionContext for each method call.
Also, probably should make sure it's closed with a using statement.
I'm also not clear from your code snippets why you are returning an IQueryable from that FederationWithEntities, method, do you have other things that are using it?
Anyway, I'd probably change that GetAllMethod to be something like this:
public async Task<IEnumerable<Models.Entities.Federation>> GetAllAsync()
{
using (Models.DataAccessLayer.CompetitionContext _db = new CompetitionContext())
{
return _db.Federations.Include(x => x.Clubs)
.Where(x => !x.DeletedAt.HasValue && x.Clubs.Any(y => !y.DeletedAt.HasValue))
.ToListAsync();
}
}