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.List
1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable
1 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.