Entity Framework and Multi threading

First off, I'm assuming you have read the article "Multithreading and the Entity Framework" on MSDN.

Solution #1 is almost certainly the safest from a threading perspective, since you are guaranteeing that only one thread is interacting with the context at any give time. There is nothing inherently wrong with keeping the context around- it isn't keeping database connections open behind the scenes, so it's just memory overhead. This could, of course, present a performance problem if you end up bottlenecked on that thread and the whole application is written with single-db-thread assumptions.

Solution #2 seems unworkable to me- you'd end up with subtle bugs throughout the application where people forget to re-attach (or detach) entities.

One solution is to not use your entity objects in the UI layer of the application. I'd recommend this anyway- chances are, the structure/layout of the entity objects is not optimal for how you want to display things on your user interface (this is the reason for the family of MVC patterns). Your DAL should have methods which are business logic specific (UpdateCustomer for instance), and it should decide internally whether to create a new Context or use a stored one. You can start with the single stored context approach, and then if you run into bottleneck issues you have a limited surface area where you need to make changes.

The downside is that you need to write a lot more code- you'd have your EF entities, but you'd also have business entities that have duplicate properties and potentially differing cardinality of many of the EF entities. To mitigate this, you can use frameworks like AutoMapper to simplify copying properties from the EF entities to the business entities and back again.


I'm using Blazor Server Side with DbContext.

I've actually done your second way, and it's been working alright. Be careful of untracked entities.

A DbContext that is scope lived which is an interface that implements IReadOnlyApplicationDbContext that only provides the IQueryable for the DbSets (no SaveChanges or anything like that). So all read operations are safely executed without having the problem of an update messing around with the data.

Also, all queries use "AsNoTracking" to prevent saving tracks of the last query in cache.

After that, all write/update/delete updates are made by unique DbContexts.

It became something like that:

public interface IReadOnlyApplicationDbContext
{
    DbSet<Product> Products { get; }
}

public interface IApplicationDbContext : IReadOnlyDbContext
{
    DbSet<Product> Products { set; }
}

public class ApplicationDbContext : DbContext, IApplicationDbContext
{
    DbSet<Product> Products { get; set; }
}

public abstract class ProductRepository
{
    private readonly IReadOnlyApplicationDbContext _readOnlyApplicationDbContext;
    private readonly IFactory<IApplicationDbContext> _applicationDbContextFactory;
    
    protected Repository(
        IReadOnlyApplicationDbContext readOnlyApplicationDbContext,
        IFactory<IApplicationDbContext> applicationDbContextFactory
    )
    {
        _readOnlyApplicationDbContext = readOnlyApplicationDbContext;
        _applicationDbContextFactory = _applicationDbContextFactory;
    }
    
    private IQueryable<Product> ReadOnlyQuery() => _readOnlyApplicationDbContext.AsNoTracking();
    
    public Task<IEnumerable<Products>> Get()
    {
        return ReadOnlyQuery().Where(s=>s.SKU == "... some data ...");
    }
    
    public Task Update(Product product)
    {
        using (var db = _applicationDbContextFactory.Create())
        {
            db.Entity(product).State = EntityState.Modified;
            return db.SaveChangesAsync();
        }
    }
    
    public Task Add(Product product)
    {
        using (var db = _applicationDbContextFactory.Create())
        {
            db.Products.AddAsync(product);
            return db.SaveChangesAsync();
        }
    }
}

Edit: you can also use .AddDbContextFactory


I have seem a dozen of stackoverflow threads concerning EF and multithreading. All of them have answers that explains the problem in depth but not really show you how to fix it.

EF is not thread safe, we all know that by now. But in my experience the only risk is the context creations/manipulations. There is actually a very simple fix for this where you can keep your lazy loading.

Lets say you have a WPF app and a MVC website. where the WPF app uses multihreading. You just dispose the db context in multithreading and keep it when not. Example a MVC website, the context will automaticly disposes after the view has been presented.

In the WPF application layer you use this:

ProductBLL productBLL = new ProductBLL(true);

In the MVC application layer you use this:

ProductBLL productBLL = new ProductBLL();

How your product business logic layer should look like:

public class ProductBLL : IProductBLL
{
    private ProductDAO productDAO; //Your DB layer

    public ProductBLL(): this(false)
    {

    }
    public ProductBLL(bool multiThreaded)
    {
        productDAO = new ProductDAO(multiThreaded);
    }
    public IEnumerable<Product> GetAll()
    {
        return productDAO.GetAll();
    }
    public Product GetById(int id)
    {
        return productDAO.GetById(id);
    }
    public Product Create(Product entity)
    {
        return productDAO.Create(entity);
    }
    //etc...
}

How your database logic layer should look like:

public class ProductDAO : IProductDAO
{
    private YOURDBCONTEXT db = new YOURDBCONTEXT ();
    private bool _MultiThreaded = false;

    public ProductDAO(bool multiThreaded)
    {
        _MultiThreaded = multiThreaded;
    }
    public IEnumerable<Product> GetAll()
    {
        if (_MultiThreaded)
        {
            using (YOURDBCONTEXT  db = new YOURDBCONTEXT ())
            {
                return db.Product.ToList(); //USE .Include() For extra stuff
            }
        }
        else
        {
            return db.Product.ToList();
        }                  
    }

    public Product GetById(int id)
    {
        if (_MultiThreaded)
        {
            using (YOURDBCONTEXT  db = new YOURDBCONTEXT ())
            {
                return db.Product.SingleOrDefault(x => x.ID == id); //USE .Include() For extra stuff
            }
        }
        else
        {
            return db.Product.SingleOrDefault(x => x.ID == id);
        }          
    }

    public Product Create(Product entity)
    {
        if (_MultiThreaded)
        {
            using (YOURDBCONTEXT  db = new YOURDBCONTEXT ())
            {
                db.Product.Add(entity);
                db.SaveChanges();
                return entity;
            }
        }
        else
        {
            db.Product.Add(entity);
            db.SaveChanges();
            return entity;
        }
    }

    //etc...
}