1

Both parent and child have to access db context in order to get their specific data, bellow is their code.

Parent:

[Inject]
private IProductsService ProductService { get; set; }
private IEnumerable<ProductModel> ProdList;      
private bool FiltersAreVisible = false;

protected override async Task OnInitializedAsync()
{
  ProdList = await ProductService.GetObjects(null);            
}

Child:

[Parameter]
public IEnumerable<ProductModel> ProdList { get; set; }
[Parameter]
public EventCallback<IEnumerable<ProductModel>> ProdListChanged { get; set; } 
[Inject]
private IRepositoryService<ProdBusinessAreaModel> ProdBAreasService { get; set; }
[Inject]
private IRepositoryService<ProdRangeModel> ProdRangesService { get; set; }
[Inject]
private IRepositoryService<ProdTypeModel> ProdTypesService { get; set; }
[Inject]
private IProductsService ProductService { get; set; }        
private ProductFilterModel Filter { get; set; } = new ProductFilterModel();
private EditContext EditContext;
private IEnumerable<ProdBusinessAreaModel> ProdBAreas;
private IEnumerable<ProdRangeModel> ProdRanges;
private IEnumerable<ProdTypeModel> ProdTypes;

protected override async Task OnInitializedAsync()
{
  EditContext = new EditContext(Filter);            
  EditContext.OnFieldChanged += OnFieldChanged;

  ProdBAreas = await ProdBAreasService.GetObjects();
  ProdRanges = await ProdRangesService.GetObjects();
  ProdTypes = await ProdTypesService.GetObjects();
}

This is throwing the following exception: InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext.

Using break points I see that parent runs OnInitializedAsync and when reaches ProdList = await ProductService.GetObjects(null); jumps right away to child OnInitializedAsync.

I solved it by making all the requests from parent and then passing to child but I wonder if there is a better way to do this, leaving child with the ability to get its own data and of course without making DB context Transient..

Regards

Mister Magoo
  • 7,452
  • 1
  • 20
  • 35
MarchalPT
  • 1,268
  • 9
  • 28
  • How did your Register your services? Are they Scoped in some way? – H H Jul 21 '21 at 16:05
  • My services are all scoped – MarchalPT Jul 21 '21 at 16:05
  • That can be pretty wasteful and lead to problems. Good luck. – H H Jul 21 '21 at 16:44
  • @HenkHolterman All those services access to DB context with dependecy injection so they cant be set sigleton, most be Scoped – MarchalPT Jul 21 '21 at 19:26
  • Singleton would be very bad for a DbContext. Scoped doesn't work too well in Blazor, but you can try. Note that Transient services are the easiest to use, you can't do anything wrong with them. – H H Jul 21 '21 at 20:30
  • You cant use Singleton with DB context it throws an exception.. Scoped is the way to go. Trasient as I sayd before you lose track on entities and its to heavy to create an instance in every class – MarchalPT Jul 22 '21 at 09:26

3 Answers3

2

Blazor does not have convenient Scopes for managing a Db. The way to resolve this is to use a Factory (no management required) and scope the actual DbContext in each of your methods with a using block.

We can't see how you implemented ProductService but it should look like

// inject a DbContextFactory and not the DbContext
public ProductService (IDbContextFactory<ProductDbContext> contextFactory)
{
    _contextFactory = contextFactory;
}

public Task<IEnumerable<ProductModel>> GetObjects()
{
   using var dbCtx = _contextFactory.CreateDbContext();

   // use dbCtx to return your results here
}

and in your Startup class

services.AddDbContextFactory<ProductDbContext>(
    options => options.UseSqlServer(config.MyConnectionString));

you can use whatever Db config you now have.

H H
  • 263,252
  • 30
  • 330
  • 514
  • By the way for Blazor Factory you need to implement IDisposable in every component using it otherwise the context will remain even after the component its destroyed, so it requires this type of management – MarchalPT Jul 21 '21 at 15:52
  • No, there is a using in every method. The service is not IDisposbale and can be registered as Transient. – H H Jul 21 '21 at 15:54
  • Components (Pages) are not Disposed soon enough or often enough. – H H Jul 21 '21 at 15:55
  • 1
    Registereing as Trasient is a problem because you will lose track of its entities every class you pass the model an using a using in every method you are creating multiple contexts and destroying them in the same component, making it bad performing and working like a Trasient.. – MarchalPT Jul 21 '21 at 15:59
  • Yes, you can lose track but that is not a problem here. – H H Jul 21 '21 at 16:00
  • 1
    Yes it is a problem here, if I dont want Trasient is for a reason – MarchalPT Jul 21 '21 at 16:01
  • You should of course use `.AsNoTracking` in your queries and fully Update changed entities. There are no performance issues. – H H Jul 21 '21 at 16:03
  • "Blazor does not have convenient Scopes for managing a Db." Actually it has, although not very well known : https://www.google.com/amp/s/chrissainty.com/an-introduction-to-owningcomponentbase/amp/ – Liero Oct 25 '21 at 18:02
  • @Liero - yeah, I know that. They don't seem to be used a lot. Using `@implements IDisposable` is just as convenient. – H H Oct 25 '21 at 18:07
1

Welcome to the Async World. You have two processes trying to use the same DbContext.

The solution is to use multiple DbContexts managed through DbContextFactory.

Here's the relevant Ms-Docs information.

The relevant section is here - using-a-dbcontext-factory-eg-for-blazor.

You can then do something like:

public override async ValueTask<List<MyModel>> SelectAllRecordsAsync()
{
   var dbContext = this.DBContext.CreateDbContext();
   var list =  await dbContext
     .MyModelDbSet
     .ToListAsync() ?? new List<TRecord>();
  dbContext?.Dispose();
  return list;
}

IDisposable and IAsyncDisposable on Services

You need to be very careful implementing IDisposable or IAsyncDisposable on services. The Scoped Services container creates an instance of any Transient service, passes on a reference to the requester and forgets about it, leaving the garbage collector to clean it up when the component is finished with it. However, if the service implements IDisposable or IAsyncDisposable it keeps a reference, but only calls Dispose when the service container itself gets Disposed (when the user session ends). There's therefore the potential for significant memory leakage using a Transient service for DbContexts.

There is a workaround (not solution) using OwningComponentBase<T> instead of ComponentBase for the component. This creates a services container for the lifetime of the Component and thus Dispose gets run when the component goes out of scope. There's still the potential for memory leaks, but the lifespan is much shorter!

MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31
  • Thank you for your answear, I have 3 Questions. What about this: "Notice that the DbContext instances created in this way are not managed by the application's service provider and therefore must be disposed by the application." This means I have to dispose the context always after unig it? And creating instances everytime I need to access DB doesnt make it less performant then my solution of asking everything in parent? Would it not be the same as making dbcontext Transient(loosing the tracking between classes)? – MarchalPT Jul 21 '21 at 15:19
  • 1
    I've updated my example to include the disposal. I've never used the transient method. The Unit of Work with the DBContextFactory methodology works and seems to be the accepted practice. There's always more that one way to code something. – MrC aka Shaun Curtis Jul 21 '21 at 15:47
  • Reading the documentation you could Implement IDisposable in the component and when the component is destroyed it will destroy its context, otherwise the context will exist – MarchalPT Jul 21 '21 at 15:50
  • 1
    A later response - see my updated answer about `IDisposable` and `IAsyncDisposable` on Services. – MrC aka Shaun Curtis Jul 24 '21 at 12:07
1

You should implement the DbContext factory in order to prevent a situation when two or more units of work for the same request compete for the same resources. See code sample below how to do that. Generally speaking, you should always implement the DbContext factory... However, it is a better code design to retrieve your data from a single location, as for instance, from your parent component, and pass it to its child component in the form of parameters. Still better, it is a good idea to create a service that implement the State and Notify patterns to provide data to interested components, notify them of changes, and generally manage and handle everything related to data. The FlightFinder Blazor App sample created by maestro Steve Anderson is a good example how to do that. However, you should follow your heart, and code as you wish. I'm just pointing out the recommended patterns.

Here's the code sample you can preview and adapt into your app:

ContactContext.cs

/// <summary>
    /// Context for the contacts database.
    /// </summary>
    public class ContactContext : DbContext
    {
        /// <summary>
        /// Magic string.
        /// </summary>
        public static readonly string RowVersion = nameof(RowVersion);

        /// <summary>
        /// Magic strings.
        /// </summary>
        public static readonly string ContactsDb = nameof(ContactsDb).ToLower();

        /// <summary>
        /// Inject options.
        /// </summary>
        /// <param name="options">The <see cref="DbContextOptions{ContactContext}"/>
        /// for the context
        /// </param>
        public ContactContext(DbContextOptions<ContactContext> options)
            : base(options)
        {
            Debug.WriteLine($"{ContextId} context created.");
        }

        /// <summary>
        /// List of <see cref="Contact"/>.
        /// </summary>
        public DbSet<Contact> Contacts { get; set; }

        /// <summary>
        /// Define the model.
        /// </summary>
        /// <param name="modelBuilder">The <see cref="ModelBuilder"/>.</param>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // this property isn't on the C# class
            // so we set it up as a "shadow" property and use it for concurrency
            modelBuilder.Entity<Contact>()
                .Property<byte[]>(RowVersion)
                .IsRowVersion();

            base.OnModelCreating(modelBuilder);
        }

        /// <summary>
        /// Dispose pattern.
        /// </summary>
        public override void Dispose()
        {
            Debug.WriteLine($"{ContextId} context disposed.");
            base.Dispose();
        }

        /// <summary>
        /// Dispose pattern.
        /// </summary>
        /// <returns>A <see cref="ValueTask"/></returns>
        public override ValueTask DisposeAsync()
        {
            Debug.WriteLine($"{ContextId} context disposed async.");
            return base.DisposeAsync();
        }
    } 

ConfigureServices

 // register factory and configure the options
            #region snippet1
            services.AddDbContextFactory<ContactContext>(opt =>
                opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db")
                .EnableSensitiveDataLogging());
            #endregion 

Here's how you inject it into your component:

@inject IDbContextFactory<ContactContext> DbFactory

And here's a code sample how to use it:

using var context = DbFactory.CreateDbContext();

        // this just attaches
        context.Contacts.Add(Contact);

        try
        {
            await context.SaveChangesAsync();
            Success = true;
            Error = false;
            // ready for the next
            Contact = new Contact();
            Busy = false;
        }
        catch (Exception ex)
        {
            Success = false;
            Error = true;
            ErrorMessage = ex.Message;
            Busy = false;
        }

UPDATE:

Parent passing to child data and using only one context throught the hole scope is how much better performing then DB context Factory?

First off, you should implement the DbContext factory in any case, right !? Once again, I do not suggest to use "Parent passing...the hole scope" instead of implementing the DbContext factory. In Blazor you must implement the DbContext factory resource racing. OK. But It is also recommended to expose your data from a single location: be it a service or a parent component. In the Component Model used in framework like Angular and Blazor, data is usually flows downstream, from a parent to its child. I'm sure you saw many code samples that do that, and this is how you should code.

enet
  • 41,195
  • 5
  • 76
  • 113
  • Parent passing to child data and using only one context throught the hole scope is how much better performing then DB context Factory? – MarchalPT Jul 21 '21 at 16:04
  • I've added an update in response to your comment. Please, be sure to understand what I'm saying, as it seems to me that you misunderstand me. – enet Jul 21 '21 at 16:23
  • I understand what you say, I can do an entire blazor app without using Context factory, making parent fetch all data and then pass to childs. I was trying to see reasons to choose one over the other.. – MarchalPT Jul 21 '21 at 19:23
  • There are no reasons to choose one over the other, as each provides a solution to a specific issue, though in your case, a badly code design and state management exposes an issue with the DBContext. Again, you should implement the DBContext factory as a precaution step against unpredictable issues that may arise as a result of using the DBContext object in Blazor, and yet you should handle state properly, whether you use services or downstreaming your data from parent component to children. – enet Jul 21 '21 at 21:46