I get an error in my ASP.NET Core Web API project when want to select all content:
System.InvalidOperationException: 'A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext
This is my contentService
:
private IContentRepository contentRepository;
private DbContext_CMS dbContext;
public ContentService(IContentRepository contentRepository, DbContext_CMS dbContext )
{
this.contentRepository = contentRepository;
this.dbContext = dbContext;
}
public IEnumerable<ContentInfoDTO> GetAllContent()
{
try
{
IQueryable<Content> contents = contentRepository.GetAll();
IEnumerable<ContentInfoDTO> result = contents.Select(content => new ContentInfoDTO(content)).ToList();
return result;
}
catch (Exception ex)
{
return null;
}
}
This is ContentInfoDTO.cs
:
public class ContentInfoDTO
{
public ContentInfoDTO(Content content)
{
try
{
this.Id = content.Id;
this.Title = content.Title;
this.Description = content.Description;
this.BodyHtml = content.BodyHtml;
this.ContentCategories = content.ContentCategories.Select(category => new CategoryInfoDTO(category.FkCmsCategory)).ToList(); //this line error
}
catch (Exception ex)
{
throw;
}
}
public int Id { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public string? BodyHtml { get; set; }
public ICollection<CategoryInfoDTO>? ContentCategories { get; set; }
}
This is my CategoryInfoDTO
:
public class CategoryInfoDTO
{
public CategoryInfoDTO(Category? category)
{
if (category != null)
{
this.Id = category.Id;
this.Title = category.Title;
this.Description = category.Description;
this.FkParentId = category.FkParentId;
}
}
public int Id { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public int? FkParentId { get; set; }
}
And this is my dbContext
configured:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder
.UseLazyLoadingProxies()
.UseSqlServer("connectionString");
}
}
I also fixed dependency injection, but it didn't work.
service.AddDbContext<DbContext_CMS>();
CodePudding user response:
In contentService
:
IEnumerable<ContentInfoDTO> result = contents.Select(content => new ContentInfoDTO(content)).ToList();
in the Select
method you start by getting Content
entities from the database. At the moment you start getting your first Content
, the DbContext
instance provided to you by Dependency Injection is in use.
Next, while still inside the Select
method, you take the Content
entity, create a new ContentInfoDTO
object and pass the entity into the constructor. In the constructor of ContentInfoDTO
, specifically at the line you get your error,
this.ContentCategories = content.ContentCategories.Select(category => new CategoryInfoDTO(category.FkCmsCategory)).ToList();
you access the ContentCategories
property, which is a navigation property belonging to Content
. Since the Content
entity you passed into the constructor was provided by EF Core, it is being tracked by EF Core, which means when you tried to perform an operation on ContentCategories
property, EF Core tried to perform a lazy loading of all the Category
entities related to that Content
instance. However, since we're still inside the first Select
method and the DbContext
instance is still in use there, we tried to access the same DbContext
from different places at the same time, hence the error.
You could force the retrieval of the ContentCategories
collection earlier using the Include
method and see if that solves the problem - see eager loading.
Also, I don't think you need to inject your DbContext in contentService
, since you seem to use the repository pattern and retrieve your entities from there, I assume the DbContext is already injected into your repository instance.
Edit : it should be lazy loading instead of explicit loading. Also that's assuming that the lazy loading related package is installed and configuration has been done and that the navigation property is virtual. If your situation doesn't cover any of the requirements, your error may be from something else