Jamil
Jamil

Reputation: 930

Problem with EF Core after migrating from 2.2 to 3.1

In 2.2 I could write something like this:

        List<Claim> claims = new List<Claim>();
        var userRoles = await _userManager.GetRolesAsync(user);
        foreach (var role in _roleManager.Roles.Where(a => userRoles.Contains(a.Name)))
        {
            claims.AddRange(await _roleManager.GetClaimsAsync(role));
        }

        return claims;

In 3.1 it gives me this error:

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

But if I add

ToList()

to the forEach clause, it works fine(like this):

        List<Claim> claims = new List<Claim>();
        var userRoles = await _userManager.GetRolesAsync(user);
        foreach (var role in _roleManager.Roles.Where(a => userRoles.Contains(a.Name)).ToList())
        {
            claims.AddRange(await _roleManager.GetClaimsAsync(role));
        }

        return claims;

Should I change all places in my code where I used similar construct, or there is a way to make EF work normal with it?

Upvotes: 3

Views: 3394

Answers (2)

Seabizkit
Seabizkit

Reputation: 2415

becasue_roleManager.Roles.Where(a => userRoles.Contains(a.Name) creates an IEnumerator which keeps the connection open, when it try to

do await _roleManager.GetClaimsAsync(role) again.. it see that it already has a open DataReader associated

3.0 probably did a internal evaluates the LINQ expression in client side automatically kind of like an auto Tolist(), instead of IEnumerator which keeps the connection open.

To avoid this i would do below to all future and current code. That way it doesn't matter, also ensure not executing over IEnumerator statements which re-execute the entire statement.

Further this has the added benefit that you can debug the list before the loop.

Before someone says that this consume more mem, show me a test showing so... This does not use yield and also the compiler is probably smart enough to make optimization for anything it sees as not been needed.

Here they word the IEnumerator issue better but same same.

Entity Framework upgrade to 6.2.0 from 6.1.x breaks certain queries unless I enable MARS

List<Claim> claims = new List<Claim>();
var userRoles = await _userManager.GetRolesAsync(user);
//you could change this to use async and await
//var roles = await _roleManager.Roles.Where(a => userRoles.Contains(a.Name)).ToListAsync();
var roles = _roleManager.Roles.Where(a => userRoles.Contains(a.Name)).ToList();
foreach (var role in roles)
{
    claims.AddRange(await _roleManager.GetClaimsAsync(role));
}

return claims;

Upvotes: 1

Saif
Saif

Reputation: 2679

why ToList() works?

  • ToList() forces execution on select query.

why it throw expcetion without adding ToList()?

  • It will throw the exception if you dont force execution due to the query actually executed when the query variable is iterated over not when select variable is created

Updates

older version of EF before 3.0 cannot convert expression to SQL or parameter and it evaluates the LINQ expression in client side automatically however new version of EF allow expression in last select call in query, so if expression in any other part of the query cannot be converted to SQL or parameter then it throws exception

Source Microsoft Docs, can read more here

Upvotes: 1

Related Questions