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

The LINQ expression cannot be translated when using null check for nested projections #28953

Closed
Ben555555 opened this issue Sep 1, 2022 · 4 comments

Comments

@Ben555555
Copy link

Ben555555 commented Sep 1, 2022

I'm using projection in EF Core with Linqkit to reuse expressions with Invoke(). I want to apply filters to projected models:

public class TransportModel
{
        public Guid Id { get; set; }
        public InmateModel Inmate  { get; set; }
}

public class InmateModel
{
        public Guid Id { get; set; }
        public List<Guid> InmateIds  { get; set; }
}
    public static Expression<Func<Transport, Inmate>> GetInmateProjection()
    {
        return transport => transport.Inmate;
    }

    public static Expression<Func<Inmate, InmateModel>> InmateModelProjection()
    {
        return inmate => new InmateModel()
        {
            Id = inmate.Id,
            InmateIds = inmate.Collusions.Select(x => x.InmateId).ToList()
        };
    }

    public static Expression<Func<Transport, TransportModel>> GetTransportModelProjection()
    {
        return transport => new TransportModel()
        {
            Inmate = InmateModelProjection().Invoke(GetInmateProjection().Invoke(transport)),
        };
    }

// Actual query
IQueryable<TransportModel> query = dbContext.Transports
                    .Select(GetTransportModelProjection());

This code works unless there is an Inmate which is null. Then I get the following exception:

System.InvalidOperationException: Nullable object must have a value.
at lambda_method1483(Closure , QueryContext , DbDataReader ,
ResultContext , SingleQueryResultCoordinator ) at
Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable1.Enumerator.MoveNext() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)

So I add a null check to prevent this error:

public static Expression<Func<Transport, TransportModel>> GetTransportModelProjection()
{
    return transport => transport != null ? new TransportModel()
    {
        Inmate = InmateModelProjection().Invoke(GetInmateProjection().Invoke(transport)),
    } : null;
}

The exception does not appear anymore, however I cannot filter all data anymore:

// This works
query = query.Where(transport => transport.Inmate.Id == someId);

// This does not work anymore due to null check
query = query.Where(transport => transport.Inmate.InmateIds.Contains(someId));

This is the exception I get for the second filter:

The LINQ expression could not be translated. Either rewrite the query
in a form that can be translated, or switch to client evaluation
explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable',
'ToList', or 'ToListAsync'.

Is there a bug in the translator?

The only workaround I can use so far is to flatten the models:

public static Expression<Func<Transport, TransportModel>> GetTransportModelProjection()
    {
        return transport => new TransportModel()
        {
            Inmate = InmateModelProjection().Invoke(GetInmateProjection().Invoke(transport)),
InmateIds = GetInmateProjection().Invoke(transport).Collusions.Select(x => x.Id).ToList()
        };
    }

and query like this

query = query.Where(transport => transport.InmateIds.Contains(someId));

or build the filter directly on the unprojected query:

query = dbContext.Transports.Where(transport => GetInmateProjection().Invoke(transport).InmateIds.Contains(someId));

But it would be nice to do this with nested projections.

Include provider and version information

EF Core version: 6.0.7
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 6.0
Operating system: Windows 11
IDE: Microsoft Visual Studio Enterprise 2022 (64-bit), Version 17.3.2

@ajcvickers
Copy link
Contributor

This code works unless there is an Inmate which is null.

Can you post the SQL that is generated when the code works?

@Ben555555
Copy link
Author

Ben555555 commented Sep 12, 2022

SELECT [t].[Id], [i].[Id], [c].[InmateId]
FROM [Transport] AS [t]
LEFT JOIN [Inmate] AS [i] ON [t].[InmateId] = [i].[Id]
LEFT JOIN [Collusion] AS [c] ON [i].[Id] = [c].[InmateId]
ORDER BY [t].[Id], [i].[Id], [c].[InmateId]

This is the generated SQL query. Also the "Nullable object must have a value" exception only appears when a projected property of InmateModel is not nullable (Id). Otherwise it will just create a new model where Inmate / InmateId is null and set null for each property. But this is not what should happen, the whole Inmate property should be null instead.

@ajcvickers
Copy link
Contributor

@Ben555555 This exception is expected if the type isn't nullable. Creating an instance with null values instead of not creating an instance is tracked by #22517 (comment)

@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Sep 13, 2022
@Ben555555
Copy link
Author

@Ben555555 This exception is expected if the type isn't nullable. Creating an instance with null values instead of not creating an instance is tracked by #22517 (comment)

But will this fix the null check from not being translated correctly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants