Skip to content

Instantly share code, notes, and snippets.

@joakimriedel
Last active August 10, 2021 23:38
Show Gist options
  • Save joakimriedel/f716b518572117f64aeb31eaa39ea850 to your computer and use it in GitHub Desktop.
Save joakimriedel/f716b518572117f64aeb31eaa39ea850 to your computer and use it in GitHub Desktop.
EF Core bug with null check in nested projection
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace EfCoreBug
{
class Program
{
static async Task Main(string[] args)
{
using (var context = new ClientContext())
{
//context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var query1 = context.Ads.Select(dtoAd => new AdDto()
{
Offers = dtoAd.Offers.Select(dtoOffer => new
{
__LastAction = dtoOffer.Actions.OrderByDescending(a => a.TimeCreatedUtc).FirstOrDefault(),
Id = dtoOffer.Id
})
.Select(dtoLet => new OfferDto()
{
Id = dtoLet.Id,
LastAction = dtoLet.__LastAction == null ? null : new OfferActionDto()
{
Action = dtoLet.__LastAction.Action,
TimeCreatedUtc = dtoLet.__LastAction.TimeCreatedUtc,
AnyEstablished = dtoLet.__LastAction.Offer.Actions.Any(a => a.Action == OfferActions.Established)
}
})
.ToList()
});
var query2 = context.Offers.Select(dtoOffer => new
{
__LastAction = dtoOffer.Actions.OrderByDescending(a => a.TimeCreatedUtc).FirstOrDefault(),
Id = dtoOffer.Id
})
.Select(dtoLet => new OfferDto()
{
Id = dtoLet.Id,
LastAction = dtoLet.__LastAction == null ? null : new OfferActionDto()
{
Action = dtoLet.__LastAction.Action,
TimeCreatedUtc = dtoLet.__LastAction.TimeCreatedUtc,
// IF THIS LINE IS COMMENTED OUT, QUERY WILL WORK IN BOTH SQL AND INMEMORY
AnyEstablished = dtoLet.__LastAction.Offer.Actions.Any(a => a.Action == OfferActions.Established)
}
});
// INMEMORY: System.ArgumentNullException: 'Value cannot be null.'
// SQL (without QueryClientEvaluationWarning): System.InvalidOperationException: 'No mapping to a relational type can be found for the CLR type 'object'.'
// SQL (with QueryClientEvaluationWarning): System.InvalidOperationException: 'Error generated for warning 'Microsoft.EntityFrameworkCore.Query.QueryClientEvaluationWarning: The LINQ expression 'where (Convert([a].Action, Int32) == 1)' could not be translated and will be evaluated locally.'.
//await query1.ToListAsync();
// INMEMORY: System.ArgumentNullException: 'Value cannot be null.'
// SQL (without QueryClientEvaluationWarning): System.ArgumentNullException: 'Value cannot be null.'
// SQL (with QueryClientEvaluationWarning): System.InvalidOperationException: 'Error generated for warning 'Microsoft.EntityFrameworkCore.Query.QueryClientEvaluationWarning: The LINQ expression 'where (Convert([a].Action, Int32) == 1)' could not be translated and will be evaluated locally.'.
await query2.ToListAsync();
}
}
public class Ad
{
public int Id { get; set; }
// navigation properties
public ICollection<Offer> Offers { get; set; }
}
public class AdDto
{
public ICollection<OfferDto> Offers { get; set; }
}
public class Offer
{
public int Id { get; set; }
[Required]
public Ad Ad { get; set; }
public int AdId { get; set; }
// navigation properties
public ICollection<OfferAction> Actions { get; set; }
}
public enum OfferActions : int
{
Established = 1
}
public class OfferAction
{
public int Id { get; set; }
[Required]
public Offer Offer { get; set; }
public int OfferId { get; set; }
[Required]
public DateTime TimeCreatedUtc { get; set; }
[Required]
public OfferActions Action { get; set; }
}
public class OfferDto
{
public int Id { get; set; }
public OfferActionDto LastAction { get; set; }
}
public class OfferActionDto
{
public DateTime TimeCreatedUtc { get; set; }
public OfferActions Action { get; set; }
public bool AnyEstablished { get; set; }
}
class ClientContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// TO USE SQL
optionsBuilder
.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=EfCoreBug;Trusted_Connection=True;MultipleActiveResultSets=true;Connect Timeout=30")
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
// TO USE INMEMORY
//optionsBuilder
// .UseInMemoryDatabase(Guid.NewGuid().ToString())
// .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Ad>().HasData(new Ad
{
Id = 1
});
builder.Entity<Offer>().HasData(new Offer
{
Id = 1,
AdId = 1
});
builder.Entity<OfferAction>().HasData(new OfferAction()
{
Id = 1,
OfferId = 1,
Action = Program.OfferActions.Established,
TimeCreatedUtc = DateTime.UtcNow
});
}
public DbSet<Ad> Ads { get; set; }
public DbSet<Offer> Offers { get; set; }
public DbSet<OfferAction> OfferActions { get; set; }
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="7.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.1.2" />
</ItemGroup>
</Project>
@gumberss
Copy link

Thanks so much for answer me :)
Have a great day :D!

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