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?

7
  • RoleManager instances are not thread-safe. Multiple CreateAsync tasks are fighting for the same connection. You should do this in a traditional serial way. Commented Dec 27, 2014 at 12:45
  • i'll make the creation of roles in a synchronous way? Commented Dec 27, 2014 at 12:46
  • Yes, that's what I suggest. Commented Dec 27, 2014 at 12:48
  • thanks for the suggestion. i'll wait for others' comment (if there's still any :D) Commented Dec 27, 2014 at 12:50
  • 2
    If you only have one instance of RoleManager, you wont be able to concurrently create multiple users. Is that an issue? Can you simply iterate them using foreach and await each one? Commented Dec 27, 2014 at 13:37

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.