3

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 way to translate these null checks correctly?

Edit: 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.

Snaketec
  • 471
  • 2
  • 14
  • Shouldn't null check be in `InmateModelProjection()`, e.g. `return inmate => inmate == null ? null : new ...`? – Ivan Stoev Sep 01 '22 at 09:43
  • Anyway, won't help. Looks like EFC translator defect/bug, hence no solution. Use some of the found workarounds. – Ivan Stoev Sep 01 '22 at 09:59
  • Yes it will result in the same exception. I also noticed that the NullReferenceException only appears when the properties are non nullable. If every property is nullable it will create an empty instance of that class, which should actually be null. I report this in the efcore github. – Snaketec Sep 12 '22 at 10:33

0 Answers0