We have upgraded our application from NserviceBus v5 to v6 and after that we run into major problem, most of the time receiving the following error.
The operation cannot be completed because the DbContext has been
disposed.
It was not obvious until we got load to our system.
We are running with eight concurrent threads and by that we receive the above error.
public class EndpointInitializer
{
public void Initialize(IKernel container)
{
var endpointConfiguration = new EndpointConfiguration("MyEndpoint");
endpointConfiguration.UseContainer<NinjectBuilder>(
customizations => { customizations.ExistingKernel(container); });
//More settings...
}
}
.
public class MyMessageHandler : IHandleMessages<MyCommand>
{
private readonly IPersonRepository _personRepository;
public MyMessageHandler(IPersonRepository personRepository)
{
_personRepository = personRepository;
}
public async Task Handle(MyCommand message, IMessageHandlerContext context)
{
var person = await _personRepository.GetByIdentifierAsync(message.Identifier).ConfigureAwait(false);
//More code...
await _personRepository.UpdateAsync(person);
}
}
.
[Serializable]
public class MyCommand
{
public string Identifier { get; set; }
}
.
public class DependencyRegistrar
{
public IKernel Container { get; set; }
public void Create()
{
Container = new StandardKernel();
RegisterTypes(Container);
}
public void RegisterTypes(IKernel kernel)
{
kernel.Bind<IPersonRepository>().To<PersonRepository>();
kernel.Bind<DbContext>().To<MyDbContext>().InThreadScope();
//More registrations...
}
}
.
public class MyDbContext : DbContext
{
public MyDbContext() : base("MyConn")
{
}
}
.
public interface IPersonRepository
{
Task<Person> GetByIdentifierAsync(string identifier);
Task UpdateAsync(Person entity);
//More methods...
}
.
public class PersonRepository : IPersonRepository
{
private readonly DbContext _dbContext;
public PersonRepository(DbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<Person> GetByIdentifierAsync(string identifier)
{
var personList = await _dbContext.Set<Person>().Where(x => x.Identifier == identifier).ToListAsync().ConfigureAwait(false);
//More code...
return personList.SingleOrDefault();
}
public async Task UpdateAsync(Person entity)
{
//More code...
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
}
}
.
public class Person
{
public int Id { get; set; }
public string Identifier { get; set; }
//More properties...
}
One option that we noticed is working is to pick up DataContext using Particulars example to use UnitOfWorkSetupBehavior. But it does not fit that well in our scenario because we have a complicated setup with services and repositories injecting the DbContext in the constructor throughout our application.
Ie, the (partial) solution for now is to call the method on the repository like;
var person = await _personRepository.GetByIdentifierAsync(context.DataContext(), message.Identifier).ConfigureAwait(false);
But now, when we run inte more complicated scenarios this won¨t suffice.
So, what are we missing? What is really the issue here?
Ninject PerThreadScope uses System.Threading.Thread.CurrentThread. With async in place the thread can change potentially for every continuation (the code that follows an await statement). You can either use a custom named scope, an async local scope or use the InUnitOfWorkScope from NServiceBus.Ninject.
Related
I am using MassTransit 7.2.2 in a .NET Core application with RabbitMQ(for local development) and SQS(for deployment) where a single message processing can result in multiple new messages getting created and processed.
All the messages share the same base type
public class BaseMessage : CorrelatedBy<Guid>
{
public BaseMessage()
{
CorrelationId = Guid.NewGuid();
CreationDate = DateTime.UtcNow;
}
public Guid CorrelationId { get; init; }
public DateTime CreationDate { get; }
public Guid? ConversationId { get; set; }
}
The basic flow of processing is same for all messages, there is a Service per Consumer.
public class FirstMessage : BaseMessage
{
}
public class FirstConsumer : IConsumer<FirstMessage>
{
private readonly ILogger<FirstConsumer> _logger;
private readonly FirstService _service;
public FirstConsumer(ILogger<FirstConsumer> logger, FirstService service)
{
_logger = logger;
_service = service;
}
public Task Consume(ConsumeContext<FirstMessage> context)
{
_logger.LogInformation($"FirstConsumer CorrelationId: {context.CorrelationId} and ConversationId: {context.ConversationId} and InitiatorId: {context.InitiatorId}");
_service.Process(context.Message);
return Task.CompletedTask;
}
}
public class FirstService
{
private readonly IBusControl _busControl;
private readonly ILogger<FirstService> _logger;
public FirstService(IBusControl busControl, ILogger<FirstService> logger)
{
_busControl = busControl;
_logger = logger;
}
public Task Process(FirstMessage firstMessage)
{
var secondMessage = new SecondMessage();
_busControl.Publish(secondMessage);
return Task.CompletedTask;
}
}
The above code is an example and the actual code base has 30+ consumers and all have the same pattern, i.e there is a Service per Consumer and the message is passed to the Service for processing.
I am trying to implement a solution for tracing messages end to end by using the Ids.
ConversationId - Unique Id for tracing logs of all Consumers in a graph
CorrelationId - Unique Id for tracing logs within a Consumer
InitiatorId - Parent Id
There is a message processing graph that looks like
FirstConsumer -> SecondConsumer -> ThirdConsumer.
I have the following Filters
ConsumeFilter
public class SimpleConsumeMessageFilter<TContext, TMessage> : IFilter<TContext>
where TContext : class, ConsumeContext<TMessage>
where TMessage : class
{
public SimpleConsumeMessageFilter()
{
}
public async Task Send(TContext context, IPipe<TContext> next)
{
LogContext.PushProperty("CorrelationId", context.CorrelationId);
LogContext.PushProperty("ConversationId", context.ConversationId);
LogContext.PushProperty("InitiatorId", context.InitiatorId);
await next.Send(context);
}
public void Probe(ProbeContext context)
{
context.CreateScope("consume-filter");
}
}
public class SimpleConsumeMessagePipeSpec<TConsumer, TMessage> : IPipeSpecification<ConsumerConsumeContext<TConsumer, TMessage>>
where TConsumer : class
where TMessage : class
{
public void Apply(IPipeBuilder<ConsumerConsumeContext<TConsumer, TMessage>> builder)
{
builder.AddFilter(new SimpleConsumeMessageFilter<ConsumerConsumeContext<TConsumer, TMessage>, TMessage>());
}
public IEnumerable<ValidationResult> Validate()
{
return Enumerable.Empty<ValidationResult>();
}
}
public class SimpleConsumePipeSpecObserver : IConsumerConfigurationObserver
{
public void ConsumerConfigured<TConsumer>(IConsumerConfigurator<TConsumer> configurator)
where TConsumer : class
{
}
public void ConsumerMessageConfigured<TConsumer, TMessage>(IConsumerMessageConfigurator<TConsumer, TMessage> configurator)
where TConsumer : class
where TMessage : class
{
configurator.AddPipeSpecification(new SimpleConsumeMessagePipeSpec<TConsumer, TMessage>());
}
}
PublishFilter
public class SimplePublishMessageFilter<TMessage> : IFilter<PublishContext<TMessage>> where TMessage : class
{
public SimplePublishMessageFilter()
{
}
public async Task Send(PublishContext<TMessage> context, IPipe<PublishContext<TMessage>> next)
{
if (context.Headers.TryGetHeader("ConversationId", out object #value))
{
var conversationId = Guid.Parse(#value.ToString());
context.ConversationId = conversationId;
}
else
{
if (context.Message is BaseMessage baseEvent && !context.ConversationId.HasValue)
{
context.ConversationId = baseEvent.ConversationId ?? Guid.NewGuid();
context.Headers.Set("ConversationId", context.ConversationId.ToString());
}
}
await next.Send(context);
}
public void Probe(ProbeContext context)
{
context.CreateScope("publish-filter");
}
}
public class SimplePublishMessagePipeSpec<TMessage> : IPipeSpecification<PublishContext<TMessage>> where TMessage : class
{
public void Apply(IPipeBuilder<PublishContext<TMessage>> builder)
{
builder.AddFilter(new SimplePublishMessageFilter<TMessage>());
}
public IEnumerable<ValidationResult> Validate()
{
return Enumerable.Empty<ValidationResult>();
}
}
public class SimplePublishPipeSpecObserver : IPublishPipeSpecificationObserver
{
public void MessageSpecificationCreated<TMessage>(IMessagePublishPipeSpecification<TMessage> specification)
where TMessage : class
{
specification.AddPipeSpecification(new SimplePublishMessagePipeSpec<TMessage>());
}
}
Added to config via
x.UsingRabbitMq((context, cfg) =>
{
cfg.ConnectConsumerConfigurationObserver(new SimpleConsumePipeSpecObserver());
cfg.ConfigurePublish(ppc =>
{
ppc.ConnectPublishPipeSpecificationObserver(new SimplePublishPipeSpecObserver());
});
cfg.UseDelayedMessageScheduler();
cfg.ConfigureEndpoints(context);
cfg.Host("localhost", rmq =>
{
rmq.Username("guest");
rmq.Password("guest");
});
});
With the above approach the 'CorrelationId' header is lost when the SecondConsumer's filters are run.
I have tried the following change and it seems to flow the Ids across the Consumers.
However, taking this approach will impact large sections of code / tests that rely on the IBusControl interface. I am keeping this as a backup option in case I can't find any other solution.
public class FirstService
{
private readonly ILogger<FirstService> _logger;
public FirstService(ILogger<FirstService> logger)
{
_logger = logger;
}
public Task Process( ConsumeContext<FirstMessage> consumeContext)
{
var secondMessage = new SecondMessage();
consumeContext.Publish(secondMessage);
return Task.CompletedTask;
}
}
Question: Is there a way to share the Context data between Consumers while using IBusControl for sending / publishing messages ?
Many thanks
As explained in the documentation, consumers (and their dependencies) must use one of the following when sending/publishing messages:
ConsumeContext, typically within the consumer itself
IPublishEndpoint or ISendEndpointProvider, typically used by scoped dependencies of the consumer
IBus, last resort, as all contextual data is lost from the inbound message
As for your final question, "Is there a way to share the Context data between Consumers while using IBusControl for sending / publishing messages?" the answer is no. The consume context would be needed to access any of the contextual data.
I have created a generic interface
public interface ISqlTradeDataRetriever<T> where T : class
{
Task<T> GetSingleDayForSymbolAsync(string symbol, string date);
Task<ICollection<T>> GetAllAsync(string symbol);
}
and an implementation of such
public class SqlCommodityDataRetriever: ISqlTradeDataRetriever<Commodity>
{
private readonly BatlGroupWebContext _context;
public SqlCommodityDataRetriever(BatlGroupWebContext context)
{
_context = context;
}
public Task<Commodity> GetSingleDayForSymbolAsync(string symbol, string date)
{
var data = _context.Commodities.FirstOrDefaultAsync(m => m.Symbol == symbol
&& m.TradeDate == Convert.ToDateTime(date));
return data;
}
public Task<ICollection<Commodity>> GetAllAsync(string symbol)
{
throw new NotImplementedException();
}
}
and in startup.cs of the web application
services.AddScoped<ISqlTradeDataRetriever<Commodity>, SqlCommodityDataRetriever>();
but when I try to access the page where I am using this implementation I get an unresolved DI error
Unable to resolve service for type 'Infrastructure.DataRetrievers.SqlCommodityDataRetriever' while attempting to activate 'BATLGroupApp.Pages.Commodity.TradeData.EditModel'.
The edit model is as follows
public class EditModel : PageModel
{
private readonly SqlCommodityDataRetriever _retriever;
public EditModel(SqlCommodityDataRetriever retriever)
{
_retriever = retriever;
}
[BindProperty]
public DomainClasses.Classes.Commodity Commodity { get; set; }
public string CommoditySymbol { get; set; }
public async Task<IActionResult> OnGetAsync([FromQuery]string symbol, [FromQuery]string date)
{
if (string.IsNullOrEmpty(symbol) || string.IsNullOrEmpty(date))
{
return NotFound();
}
Commodity = await _retriever.GetSingleDayForSymbolAsync(symbol, date);
if (Commodity == null)
{
return NotFound();
}
CommoditySymbol = Commodity.CommoditySymbol.Symbol;
return Page();
}
}
What am I doing wrong in terms of registering the DI for this OR is my implementation wrong?
The registration is fine in its current form.
services.AddScoped<ISqlTradeDataRetriever<Commodity>, SqlCommodityDataRetriever>();
The page model however expects the concrete SqlCommodityDataRetriever implementation while the container is only aware of its ISqlTradeDataRetriever<Commodity> abstraction.
Refactor the page model to match the registration
public class EditModel : PageModel {
private readonly ISqlTradeDataRetriever<Commodity> _retriever;
public EditModel(ISqlTradeDataRetriever<Commodity> retriever) {
_retriever = retriever;
}
//... omitted for brevity since nothing else needs to change
as the class should ideally be dependent on abstractions instead of concretions for a more SOLID design.
when reading data from the database I get this error:
A second operation started on this context before a previous operation
completed. Any instance members are not guaranteed to be thread safe.
I have the following ApplicationContext.cs:
public class ApplicationContext : Microsoft.EntityFrameworkCore.DbContext
{
public ApplicationContext(DbContextOptions<ApplicationContext> options)
: base(options)
{ }
public DbSet<MyClass> MyClasses{ get; set; }
}
The following ApplicationContextFactory.cs
public class ApplicationContextFactory : IDesignTimeDbContextFactory<ApplicationContext>
{
public ApplicationContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<ApplicationContext>();
var connection = "myConnectionString";
builder.UseSqlServer(connection);
return new ApplicationContext(builder.Options);
}
}
The following ServiceLoader.cs (where I declare the DI):
public static class ServiceLoader
{
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IRepository, Repository>();
var connection = "myConnectionString";
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connection));
}
}
and finally, the following Repository, where the exception is thrown:
public class Repository : IRepository
{
private ApplicationContext _db;
public Repository (ApplicationContext db)
{
_db = db;
}
public List<MyClass> Get()
{
_db.MyClasses.ToList();
}
}
I have also tried to declare the Repository as Transient instead of Singleton, but a similar error is thrown
'An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
Any idea on how to fix this? Thanks!
In my case I found the following information helpful:
https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext
And changed the lifetime scope of my Db Context to transient using the overloaded AddDbContext method in startup:
services.AddDbContext<MyAppDbContext>(options => {
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"));
}, ServiceLifetime.Transient);
You can wrap an async Task around your Get() function and await your results:
public async Task<List<MyClass>> Get()
{
return await _db.MyClasses.ToListAsync();
}
I wrote a solution, which uses a queue. It is still single threaded, but you can call it from different threads.
public class ThreadSafeDataContext
{
private Thread databaseThread;
private Queue<PendingQuery> pendingQueries = new Queue<PendingQuery>();
private DatabaseContext db = new DatabaseContext();
private bool running = true;
public ThreadSafeDataContext()
{
databaseThread = new Thread(new ThreadStart(DoWork));
databaseThread.Start();
}
public void StopService()
{
running = false;
}
private void DoWork()
{
while(running)
{
if (pendingQueries.Count > 0)
{
// Get and run query
PendingQuery query = pendingQueries.Dequeue();
query.result = query.action(db);
query.isFinished = true;
}
else
{
Thread.Sleep(1); // Waiting for queries
}
}
}
public T1 Query<T1>(Func<DatabaseContext, T1> action)
{
Func<DatabaseContext, object> a = (DatabaseContext db) => action(db);
PendingQuery query = new PendingQuery(a);
pendingQueries.Enqueue(query);
while (!query.isFinished) {
Thread.Sleep(1); // Wait until query is finished
}
return (T1)query.result;
}
}
class PendingQuery
{
public Func<DatabaseContext, object> action;
public bool isFinished;
public object result;
public PendingQuery(Func<DatabaseContext, object> action)
{
this.action = action;
}
}
Then you can just run a query from different threads by using:
TeamMembers teamMembers = threadSafeDb.Query((DatabaseContext c) => c.team.ToArray())
I have partial interface
public partial interface ISponsoredService { GetX(), AddX() }
public partial interface ISponsoredService { GetY(), AddY() }
And partial implementations
public partial class SponsoredService : ISponsoredService {
public SponsoredService(
ISponsoredBrandsRepository sponsoredBrandRepository,
ISponsoredDiscountsRepository sponsoredDiscountsRepository) { }
}
public partial class SponsoredService { GetX(); AddX(); }
public partial class SponsoredService { GetY(); AddY(); }
And the problem is with these repositories because they use the same context injected in request scope like services and repositories.
public class SponsoredDiscountsRepository : BaseSponsoredRepository, ISponsoredDiscountsRepository
{
public SponsoredDiscountsRepository(
Context sponsoredContext) : base(sponsoredContext)
{
}
}
public class SponsoredBrandsRepository : BaseSponsoredRepository, ISponsoredBrandsRepository
{
public SponsoredBrandsRepository (
Context sponsoredContext) : base(sponsoredContext)
{
}
}
Ninject configuration:
kernel.Bind<Context>().ToSelf();
kernel.Bind<ISponsoredService>().To<SponsoredService>().InRequestScope();
kernel.Bind<ISponsoredBrandsRepository>().To<SponsoredBrandsRepository>().InRequestScope();
kernel.Bind<ISponsoredDiscountsRepository>().To<SponsoredDiscountsRepository>().InRequestScope();
And where is a problem?
When I execute AddX() and then GetX() in the same HTTP request the second operation(GetX()) hangs on the connection to the base. It lasts forever and never ends.
But if I do AddX() and then in other HTTP request GetX() it work.
Combination AddY() and in the same request GetX() do not work as well.
What is going on here? Context are not disposed because it's created in request scope. In the same request scope is created SponsoredService which contains repository1 and repository2 which contains created context.
Edit: Add implementation of repositories
These two repositories use one generic wchich contains:
protected readonly Context SponsoredContext;
protected readonly DbSet<TEntity> DbSet;
and all operations:
public virtual async Task<IEnumerable<TEntity>> GetAsync()
{
return await this.DbSet.ToListAsync();
}
GetX() { sponsoredBrandsRepository.GetAsync(); }
AddX() {
sponsoredBrandsRepository.GetAsync();
some operations
sponsoredBrandsRepository.AddAsync();
}
Edit add full code
WebAPI:
[RoutePrefix("api/sponsored")]
public partial class SponsoredController : BaseApiController
{
private readonly ISponsoredService _sponsoredService;
public SponsoredController(
ISponsoredService sponsoredService,
IBrandService brandService,
ICampaignsService discountService)
{
_sponsoredService = sponsoredService;
_discountService = discountService;
_brandService = brandService;
}
}
public partial class SponsoredController
{
private readonly IBrandService _brandService;
[Route("brands"), HttpGet]
public async Task<IHttpActionResult> GetBrands()
{
try
{
var viewModels = await GetBrandViewModels();
return Ok(viewModels);
}
catch (Exception e)
{
base.Log(e);
return InternalServerError();
}
}
[Route("brands"), HttpPost, ValidateModelStateFilter]
public async Task<IHttpActionResult> Post([FromBody] IEnumerable<SponsoredBrandAddOrUpdateViewModel> viewModels)
{
try
{
await this._sponsoredService.AddOrUpdate(viewModels.Select(vm => (SponsoredBrand)vm));
return Created("api/sponsored/brands", GetBrandViewModels());
}
catch (Exception e)
{
base.Log(e, viewModels);
return InternalServerError();
}
}
private async Task<List<SponsoredBrandListViewModel>> GetBrandViewModels()
{
var dbSponsoredBrands = await this._sponsoredService.GetSponsoredBrandsAsync();
var viewModels =
dbSponsoredBrands.Select(sponsoredBrand =>
{
var viewModel = (SponsoredBrandListViewModel)sponsoredBrand;
viewModel.Name = (this._brandService.GetBrandByBrandIdAsync(viewModel.BrandId).Result).Entity.Name;
return viewModel;
}).ToList();
return viewModels;
}
}
public partial class SponsoredController
{
private readonly ICampaignsService _discountService;
[Route("discounts"), HttpGet]
public async Task<IHttpActionResult> GetDiscounts()
{
try
{
var viewModels = await GetDiscountsViewModels();
return Ok(viewModels);
}
catch (Exception e)
{
base.Log(e);
return InternalServerError();
}
}
[Route("discounts"), HttpPost, ValidateModelStateFilter]
public async Task<IHttpActionResult> Post([FromBody] IEnumerable<SponsoredDiscountAddOrUpdateViewModel> viewModels)
{
try
{
await this._sponsoredService.AddOrUpdate(viewModels.Select(vm => (SponsoredDiscount)vm));
return Created("api/sponsored/discounts", GetDiscountsViewModels());
}
catch (Exception e)
{
base.Log(e, viewModels);
return InternalServerError();
}
}
private async Task<List<SponsoredDiscountListViewModel>> GetDiscountsViewModels()
{
var dbSponsoredBrands = await this._sponsoredService.GetSponsoredDiscountsAsync();
var viewModels =
dbSponsoredBrands.Select(sponsoredBrand =>
{
var viewModel = (SponsoredDiscountListViewModel)sponsoredBrand;
viewModel.Name = (this._discountService.GetCampaignByCampaignIdAsync(viewModel.DiscountId).Result)?.Entity?.Discount?.Name;
return viewModel;
}).ToList();
return viewModels;
}
}
Service:
public partial interface ISponsoredService
{
Task<IEnumerable<SponsoredDiscount>> GetSponsoredDiscountsAsync();
Task<IEnumerable<SponsoredDiscount>> AddOrUpdate(IEnumerable<SponsoredDiscount> sponsoredDiscounts);
}
public partial interface ISponsoredService
{
Task<IEnumerable<SponsoredDiscount>> GetSponsoredDiscountsAsync();
Task<IEnumerable<SponsoredDiscount>> AddOrUpdate(IEnumerable<SponsoredDiscount> sponsoredDiscounts);
}
public partial class SponsoredService : ISponsoredService
{
public SponsoredService(
ISponsoredBrandsRepository sponsoredBrandRepository,
ISponsoredDiscountsRepository sponsoredDiscountsRepository,
IBrandService brandService,
ICampaignsService discountsService)
{
_sponsoredBrandRepository = sponsoredBrandRepository;
_brandService = brandService;
_discountsService = discountsService;
_sponsoredDiscountsRepository = sponsoredDiscountsRepository;
}
}
public partial class SponsoredService
{
private readonly ISponsoredBrandsRepository _sponsoredBrandRepository;
private readonly IBrandService _brandService;
public async Task<IEnumerable<SponsoredBrand>> GetSponsoredBrandsAsync() => await this._sponsoredBrandRepository.GetAsync();
public async Task<IEnumerable<SponsoredBrand>> AddOrUpdate(IEnumerable<SponsoredBrand> sponsoredBrands)
{
// remove
var dbSponsored = await this.GetSponsoredBrandsAsync();
foreach (var dbS in dbSponsored)
{
if (!sponsoredBrands.Any(s => s.RelatedEntityId == dbS.RelatedEntityId))
{
await this.DeleteSponsoredBrand(dbS.Id);
}
}
// new
foreach (var newS in sponsoredBrands)
{
var brand = (await this._brandService.GetBrandByBrandIdAsync(newS.RelatedEntityId)).Entity;
brand.BrandRules = new List<BrandRule>
{
new BrandRule
{
IsHighlighted = true, Order = newS.Order, ValidTo = newS.To, ValidFrom = newS.From
}
}.ToList();
await this._brandService.UpdateAsync(brand);
}
this._sponsoredBrandRepository.Clear();
await this._sponsoredBrandRepository.Add(sponsoredBrands);
return null;
}
}
public partial class SponsoredService
{
private readonly ISponsoredDiscountsRepository _sponsoredDiscountsRepository;
private readonly ICampaignsService _discountsService;
public async Task<IEnumerable<SponsoredDiscount>> GetSponsoredDiscountsAsync()
=> await this._sponsoredDiscountsRepository.GetAsync();
public async Task<IEnumerable<SponsoredDiscount>> AddOrUpdate(IEnumerable<SponsoredDiscount> sponsoredDiscounts)
{
// remove
var dbSponsored = await this.GetSponsoredDiscountsAsync();
foreach (var dbS in dbSponsored)
if (!sponsoredDiscounts.Any(s => s.RelatedEntityId == dbS.RelatedEntityId))
await this.DeleteSponsoredDiscount(dbS.Id);
// new
foreach (var newS in sponsoredDiscounts)
if (!await this._discountsService.X(newS.RelatedEntityId, newS))
return null;
this._sponsoredDiscountsRepository.Clear();
await this._sponsoredDiscountsRepository.Add(sponsoredDiscounts);
return null;
}
}
Repositories:
public interface ISponsoredRepository<TEntity> : IBaseRepository<TEntity, int>
where TEntity : Sponsored.Sponsored
{
void Clear();
Task Add(IEnumerable<TEntity> entities);
}
public interface ISponsoredBrandsRepository : ISponsoredRepository<SponsoredBrand> { }
public interface ISponsoredDiscountsRepository : ISponsoredRepository<SponsoredDiscount> { }
public abstract class SponsoredRepository<TEntity> : ISponsoredRepository<TEntity>
where TEntity : Domain.Sponsored.Sponsored
{
protected readonly Context SponsoredContext;
protected readonly DbSet<TEntity> DbSet;
protected SponsoredRepository(Context sponsoredContext)
{
SponsoredContext = sponsoredContext;
DbSet = this.SponsoredContext.Set<TEntity>();
}
public virtual async Task<IEnumerable<TEntity>> GetAsync()
{
return await this.DbSet.ToListAsync();
}
public virtual void Clear()
{
this.DbSet.RemoveRange(this.DbSet);
this.SponsoredContext.SaveChanges();
}
public virtual async Task Add(IEnumerable<TEntity> entities)
{
this.DbSet.AddRange(entities);
await this.SponsoredContext.SaveChangesAsync();
}
}
public class SponsoredBrandsRepository : SponsoredRepository<SponsoredBrand>, ISponsoredBrandsRepository
{
public SponsoredBrandsRepository(
Context sponsoredContext) : base(sponsoredContext)
{
}
}
public class SponsoredDiscountsRepository : SponsoredRepository<SponsoredDiscount>, ISponsoredDiscountsRepository
{
public SponsoredDiscountsRepository(
Context sponsoredContext) : base(sponsoredContext)
{
}
}
ContexT:
public class Context
{
public virtual DbSet<SponsoredBrand> Brands { get; set; }
public virtual DbSet<SponsoredDiscount> Discounts { get; set; }
public Context()
{
}
}
IoC configuration(Ninject):
kernel.Bind<Context>().ToSelf();
kernel.Bind<ISponsoredService>().To<SponsoredService>().InRequestScope();
kernel.Bind<ISponsoredBrandsRepository>().To<SponsoredBrandsRepository>().InRequestScope();
kernel.Bind<ISponsoredDiscountsRepository>().To<SponsoredDiscountsRepository>().InRequestScope();
You're not binding Context InRequestScope, so by default it gets created as Transient scope, and therefore is probably not being disposed/closed.
more on Scopes:
https://github.com/ninject/ninject/wiki/Object-Scopes
Managing a single DbContext in a request is hard. You may have lots of different classes requesting it as a dependency and using it different ways. I ended up with a complex scoping mechanism to manage the DbContexts. However, it may be easier/better to inject a DbContext factory and use using to control the lifetime of the DbContext (unit of work pattern), or just new it up in your repository (and dispose it).
if you Google "entity framework dbcontext request", you will find many discussions and opinions about the matter. here is a good overview/intro: http://mehdi.me/ambient-dbcontext-in-ef6/
I am working on an asp.net mvc web application. now i have created multiple repositories classes, for example i have the following abstract repository classes:-
public interface ISkillRepository : IDisposable
{//code goes here..
&
public interface IStaffRepository : IDisposable
{//code goes here
and the model Repositories:-
public class SkillRepository : ISkillRepository , IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
//code goes here
&
public class StaffRepository : IStaffRepository , IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
now inside y controller i am intializing and creating the repo as follow:-
public class SkillController : Controller
{
private ISkillRepository skillRepository;
public SkillController() : this(new SkillRepository()) {}
public SkillController(ISkillRepository repository)
{
skillRepository = repository;
}
but currently i got the following error inside my application:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
and the problem is that i need to be passing the same context accross the repos and controllers. so can anyone adivce on this:-
how i can inside one model repo to reference another repo using the same context class. for example inside the Staff repositoryto referecne the skill repository?
how i can inside a controller class to refer multiple repos , but at the same time pass the same context object among them , so if i issue a save() it will wrap all the statements inside one transaction. for example insie my skillController to reference both the skill & staff repos using the same context object ?
Thanks
Edit
I have created the following Unit of work class:-
public class UnitOfWork : IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
private SkillRepository skillRepository;
private StaffRepository staffRepository;
private SecurityRoleRepository securityroleRepository;
public SkillRepository SkillRepository
{
get
{
if (this.skillRepository == null)
{
this.skillRepository = new SkillRepository(context);
}
return skillRepository;
}
}
public StaffRepository StaffRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new StaffRepository(context);
}
return staffRepository;
}
}
public SecurityRoleRepository SecurityRoleRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new SecurityRoleRepository(context);
}
return securityroleRepository;
}
}
public async Task Save()
{
await context.SaveChangesAsync();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
and then inside my repo i did the following:-
public class SecurityRoleRepository : ISecurityRoleRepository , IDisposable
{
private SkillManagementEntities context;// = new SkillManagementEntities();
public SecurityRoleRepository(SkillManagementEntities context)
{
this.context = context;
and on the controller class i will be referencing the UnitOfWork as follow:-
public class SecurityRoleController : Controller
{
private UnitOfWork unitOfWork = new UnitOfWork();
public async Task<ActionResult> Index(string filter = null, int page = 1, int pageSize = 20, string sort = "Name", string sortdir = "ASC")
{
try
{
var records = new PagedList<SecurityRole>();
ViewBag.filter = filter;
records.Content = await unitOfWork.SecurityRoleRepository.GetSecurityRoleForGrid(filter, page, pageSize, sort, sortdir).ToListAsync();
now i am facing a problem is that how i can referecne a repo from another Repo ? for example how i can reference the Skill repo inside the SecurityRole repo ?
EDIT Final
i did the following steps:-
1. i install
Install-Package Ninject.MVC5
2. then i created the following dependency class:-
public class YourDependencyResolverClass : IDependencyResolver
{
private IKernel kernel;
public YourDependencyResolverClass()
{
kernel = new StandardKernel();
AddBindings();
}
public object GetService(Type serviceType)
{
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return kernel.GetAll(serviceType);
}
private void AddBindings()
{
kernel.Bind<ISkillRepository>().To<SkillRepository>();
kernel.Bind<IStaffRepository>().To<StaffRepository>();
kernel.Bind<ISecurityRoleRepository>().To<SecurityRoleRepository>();
kernel.Bind<ICustomerRepository>().To<CustomerRepository>();
kernel.Bind<ISkillVersionHistoryRepository>().To<SkillVersionHistoryRepository>();
}
}
}
3.now inside my SkillRepository class i will be referencing the StaffRepository as follow:-
public class SkillRepository : ISkillRepository , IDisposable
{
private SkillManagementEntities context ;
private IStaffRepository staffrepo = (IStaffRepository)DependencyResolver.Current.GetService(typeof(IStaffRepository));
public SkillRepository(SkillManagementEntities context)
{
this.context = context;
}
Finally inside my action method i will be calling the Uiteofwork class as follow:-
public class StaffController : Controller
{
//private SkillManagementEntities db = new SkillManagementEntities();
UnitOfWork unitofwork = new UnitOfWork();
public async Task<ActionResult> AutoComplete(string term)
{
var staff = await unitofwork.StaffRepository.GetAllActiveStaff(term).Select(a => new { label = a.SamAccUserName }).ToListAsync();
return Json(staff, JsonRequestBehavior.AllowGet);
and the unite of work class is :-
public class UnitOfWork : IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
private SkillRepository skillRepository;
private StaffRepository staffRepository;
private SecurityRoleRepository securityroleRepository;
private CustomerRepository customerRepository;
private SkillVersionHistoryRepository SVH;
public SkillRepository SkillRepository
{
get
{
if (this.skillRepository == null)
{
this.skillRepository = new SkillRepository(context);
}
return skillRepository;
}
}
public StaffRepository StaffRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new StaffRepository(context);
}
return staffRepository;
}
}
public CustomerRepository CustomerRepository
{
get
{
if (this.customerRepository == null)
{
this.customerRepository = new CustomerRepository(context);
}
return customerRepository;
}
}
public SecurityRoleRepository SecurityRoleRepository
{
get
{
if (this.securityroleRepository == null)
{
this.securityroleRepository = new SecurityRoleRepository(context);
}
return securityroleRepository;
}
}
public SkillVersionHistoryRepository SkillVersionHistoryRepository
{
get
{
if (this.SVH == null)
{
this.SVH = new SkillVersionHistoryRepository(context);
}
return SVH;
}
}
public async Task Save()
{
await context.SaveChangesAsync();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
So can you adivce if my approach of using unitefwork and DI will guarantee that all my statements will be warped inside a single DB transaction ? thnaks?
We handle this by sharing a context using a singleton that is scoped to the request using HttpContext:
public MyContext GetContext()
{
if (System.Web.HttpContext.Current.Items["MyScopedContext"] == null)
{
System.Web.HttpContext.Current.Items["MyScopedContext"] = new MyContext();
}
return (MyContext)System.Web.HttpContext.Current.Items["MyScopedContext"];
}
The context object (repository) itself essentially houses a Unit of Work. The code I added above just gives you a way to share a single repository across all code running within a request. If your repository classes are defined in the scope of a web application, you can just replace your direct instantiation of SkillManagementEntities() with a call to a GetContext() method.
On the other hand if your repositories are defined in a layer shared by heterogeneous applications, you may need to get your context from a factory object that you can inject as needed. Either way, creating a new context object per repository is what's causing your issue.
Not an answer: this "use DI" suggestion answers a bit different question - OP is looking for "unit-of-work" pattern - while basic case (lifetime of unit of work matches lifetime of request/controller) can easily be solved with any DI framework, managing multiple units of work or units of work with longer lifetime is much harder and dedicated "unit of work factory" (sample usage) is likely the solution.
Usually when you go that far with interfaces/repositories and constructor dependency injection you have some Dependency Injection framework. There is a good chance that one you are using already provides "per HTTP request" resolution or allows to easily add one.
I.e. if you using Unity there is PerRequestLifetime lifetime manager that makes all .Resolve calls for the same interface/object to return the same instance for given request. See more info in DI with Unity MSDN article.
Approximate sample:
container.RegisterType<ISkillRepository, SkillRepository>();
container.RegisterType<IOtherRepository, OtherRepository>();
container.RegisterType<TheContext, TheContext>(new PerRequestLifetime());
With such registration and assuming you've configured ASP.Net MVC to use Unity to resolve types when controller is created it will get necessary dependencies (new instances as registered with default lifetime), but both will share the same context (assuming each depends on TheContext class either via constructor or property injection).