Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InvalidOperationException on nullable properties in expressions #112

Open
BenjaminAbt opened this issue Jan 11, 2020 · 0 comments
Open

InvalidOperationException on nullable properties in expressions #112

BenjaminAbt opened this issue Jan 11, 2020 · 0 comments

Comments

@BenjaminAbt
Copy link

@BenjaminAbt BenjaminAbt commented Jan 11, 2020

I stumbled over the following exception and suspect the error in the LINQKit. I guess it's a bug:

"InvalidOperationException: When called from 'VisitLambda', rewriting a node of type 'System.Linq.Expressions.ParameterExpression' must return a non-null value of the same type."

I am currently trying to consolidate some of our queries with the goal of reducing the amount of SQL queries against the server.

The following example triggers the exception:

Original code

var userCount = await _dbContext.UserAccounts.AsNoTracking().CountAsync(cancellationToken);
var users = await _dbContext.UserAccounts
    .AsNoTracking().AsExpandable()
    .OrderByDescending(u => u.RegisteredOn)
    .Select(DbUserEx.AsUserProjection)
    .Skip(skip).Take(take)
    .ToListAsync(cancellationToken);

Results in two queries

Consolidation

var q = from _ in _dbContext.UserAccounts.AsNoTracking().AsExpandable()
select new
{
   TotalUserCount = _dbContext.UserAccounts.Count(),

   Users = _dbContext.UserAccounts.AsNoTracking().AsExpandable()
       .OrderByDescending(u => u.RegisteredOn)
       .Skip(skip).Take(take)
       .Select(DbUserEx.AsUserProjection)
       .ToList()
};

Would result in one query.
My guess was that I was using AsExpandable incorrectly; however, all attempts results in the same exception.

During further debugging I found that the query is executed successfully if I don't use LINQKit.

The problem

The problem seems to be the following:
DbUserEx.AsUserProjection is an expression, that uses another expression.

public static Expression<Func<UserEntity, UserProjection>> AsUserProjection
{
    get
    {
        var userAvatarExpr = AsUserAvatarProjection;

        return u => new UserProjection(u.Id, u.UserName,
            u.Avatar != null ? userAvatarExpr.Invoke(u.Avatar) : null);
    }
}

public static Expression<Func<UserEntity, UserAvatarProjection>> AsUserAvatarProjection
    => u => new UserAvatarProjection(u.UserId, u.Id, u.StorageBlobName);

What works

If I replace the line with null, it works fine

public static Expression<Func<UserEntity, UserProjection>> AsUserProjection
{
    get
    {
        var userAvatarExpr = AsUserAvatarProjection;

        return u => new UserProjection(u.Id, u.UserName, null);
    }
}

If I return the embedded projection directory without the embedded expression, it works fine too

public static Expression<Func<UserEntity, UserProjection>> AsUserProjection
{
    get
    {
        var userAvatarExpr = AsUserAvatarProjection;

        return u => new UserProjection(u.Id, u.UserName,
            u.Avatar != null ? new UserAvatarProjection(u.Avatar.UserId, u.Avatar.Id, u.Avatar.StorageBlobName) : null);
    }
}

So the problematic part is obviously that the expression might not be executed (and maybe then the expression is not visited)?

If I move the nullable check from AsUserProjection to AsUserAvatarProjection:

InvalidOperationException: Null TypeMapping in Sql Tree
Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor+SqlTypeMappingVerifyingExpressionVisitor.VisitExtension(Expression node)

Since the whole thing works in principle if I don't use LINQKit (but then I have other disadvantages), I suspect it's a bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant
You can’t perform that action at this time.