3

I have already seen this, but I am experiencing another problem.

I have this service class for managing ASP.NET identity roles:

public class RoleService : IRoleService
{
    private readonly RoleManager<ApplicationRole> _roleManager;

    public RoleService(RoleManager<ApplicationRole> roleManager)
    {
        this._roleManager = roleManager;
    }

    public async Task<IdentityResult> CreateAsync(ApplicationRole role)
    {
        return await this._roleManager.CreateAsync(role);
    }
}

As suggested by this question, I use the CreateAsync method like this to avoid using LINQ foreach:

private async Task PopulateRoles()
{
     var roles = new[] { "A", "B", "C", "D" };

     // Used LINQ foreach previously but I coded this way instead to follow the related questions's answer
     var tasks = roles.Select(role =>
                           this._roleService.CreateAsync(new ApplicationRole(role)))
                      .ToList();

     await Task.WhenAll(tasks);
}

However, this results in an error when await this.PopulateRoles() is executed.

Entity Framework: There is already an open DataReader associated with this Command which must be closed first.

Searching for this error only leads me to a suggestion of adding a ToList() in my Select LINQ. How can I fix it?

Community
  • 1
  • 1
raberana
  • 11,739
  • 18
  • 69
  • 95

1 Answers1

4

The problem lays with the RoleManager<T> which internally is given a single DbContext as we can see here:

public class RoleStore<TRole, TContext, TKey> : 
        IQueryableRoleStore<TRole>, 
        IRoleClaimStore<TRole>
        where TRole : IdentityRole<TKey>
        where TKey : IEquatable<TKey>
        where TContext : DbContext
{
    public RoleStore(TContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
        Context = context;
    }
}

DbContext itself can't handle concurrent calls. The alternative would be to execute each of the calls inside a foreach and await each one:

private async Task PopulateRoles()
{
     var roles = new[] { "A", "B", "C", "D" };

     foreach (var role in roles)
     {
         await _roleService.CreateAsync(new ApplicationRole(role));
     }
}

This way, though you don't have the benefit of applying all roles concurrently, you do still take advantage of the async nature of the IO call, instead of a blocking synchronous call.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321