Skip to content

Instantly share code, notes, and snippets.

@joakimriedel
Created September 7, 2018 09:09
Show Gist options
  • Save joakimriedel/caffa6a544ab3e7a5d22d63273df9d77 to your computer and use it in GitHub Desktop.
Save joakimriedel/caffa6a544ab3e7a5d22d63273df9d77 to your computer and use it in GitHub Desktop.
EF Core bug with AsQueryable and expression filter
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 EfCoreBug2
{
class Program
{
static async Task Main(string[] args)
{
using (var context = new ClientContext())
{
//context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var query1 = context.Offers
.Select(dtoOffer => new OfferDto
{
AcceptedActions = dtoOffer.Actions
.Where(a => a.Action == OfferActions.Accepted)
.OrderBy(a => a.TimeCreatedUtc)
.Take(1)
.Select(dtoOfferAction => new OfferActionDto()
{
CompanyName = dtoOfferAction.Company.Name
})
.ToList()
}
);
var query2 = context.Offers
.Select(dtoOffer => new OfferDto
{
AcceptedActions = dtoOffer.Actions
.AsQueryable()
.Where(OfferAction.Accepted)
.OrderBy(a => a.TimeCreatedUtc)
.Take(1)
.Select(dtoOfferAction => new OfferActionDto()
{
CompanyName = dtoOfferAction.Company.Name
})
.ToList()
}
);
// THIS QUERY WORKS (WITHOUT ASQUERYABLE)
await query1.ToListAsync();
// THIS QUERY DOES NOT WORK IN SQL PROVIDER
// SQL: Microsoft.EntityFrameworkCore.Query.QueryClientEvaluationWarning: The LINQ expression 'join Company a.Company in value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[EfCoreBug2.Program+Company]) on Property([a], "CompanyId") equals Property([a.Company], "Id")' could not be translated and will be evaluated locally.'. This exception can be suppressed or logged by passing event ID 'RelationalEventId.QueryClientEvaluationWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.
// BUT WORKS WITH INMEMORY
await query2.ToListAsync();
}
}
public class Company
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
// Navigation properties
public ICollection<Offer> Offers { get; set; }
public ICollection<OfferAction> OfferActions { get; set; }
}
public class Offer
{
public int Id { get; set; }
[Required]
public Company Company { get; set; }
public int CompanyId { get; set; }
// navigation properties
public ICollection<OfferAction> Actions { get; set; }
}
public enum OfferActions : int
{
Accepted = 1
}
public class OfferAction
{
public static Expression<Func<OfferAction, bool>> Accepted => a => a.Action == OfferActions.Accepted;
public int Id { get; set; }
[Required]
public Offer Offer { get; set; }
public int OfferId { get; set; }
[Required]
public Company Company { get; set; }
public int CompanyId { get; set; }
[Required]
public DateTime TimeCreatedUtc { get; set; }
[Required]
public OfferActions Action { get; set; }
}
public class OfferDto
{
public int Id { get; set; }
public ICollection<OfferActionDto> AcceptedActions { get; set; }
}
public class OfferActionDto
{
public string CompanyName { get; set; }
}
class ClientContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// TO USE SQL
optionsBuilder
.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=EfCoreBug2;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<Company>().HasData(new Company
{
Id = 1,
Name = "Seller"
});
builder.Entity<Company>().HasData(new Company
{
Id = 2,
Name = "Buyer"
});
builder.Entity<Offer>().HasData(new Offer
{
Id = 1,
CompanyId = 2
});
builder.Entity<OfferAction>()
.HasOne(a => a.Company)
.WithMany(bg => bg.OfferActions)
.HasForeignKey(a => a.CompanyId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<OfferAction>().HasData(new OfferAction()
{
Id = 1,
OfferId = 1,
CompanyId = 2,
Action = Program.OfferActions.Accepted,
TimeCreatedUtc = DateTime.UtcNow
});
}
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="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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment