Implement Include/ThenInclude as adapter - #188
Conversation
fiseni
commented
Dec 19, 2021
Hey @ardalis, If you don't have any comments here, I'll merge this. In the end, we decided to be an opt-in feature, so there is no high risk of implementing it. |
…ollection) with ThenInclude reference|collection; add comments to show what constructed expression trees represent
fiseni
commented
Dec 20, 2021
Hey @devbased, The tests are passing and everything seems working fine with caching enabled. But, I'm somewhat uncomfortable with benchmark results. We're doing additional work on top of EF, and we just can't be faster than EF. What we're missing here? When you compile the lambda for the proxy delegates, perhaps it compiles recursively the inner LambdaExpression too (the query expression)? We're passing the compiled version? 🤔 That would not be good. Benchmark results for 1.000.000 iterations:
|
Ok, the issue was in the benchmark tests. For the EF direct approach, the expressions were created on each iteration, while the spec versions have the expression stored as a state. Once we fix this, then we get much more accurate and reasonable results. Actually, now we can see how much slower we are compared with direct EF usage 😞 Benchmark results for 100.000 iterations:
The test code: namespaceSpecification.Benchmark{[MemoryDiagnoser]publicclassIncludeBenchmark{privatereadonlyintmax=100000;privatereadonlySpecificationEvaluatorevaluator=SpecificationEvaluator.Default;privatereadonlySpecificationEvaluatorevaluatorCached=SpecificationEvaluator.Cached;privatereadonlySpecification<Store>specInclude=newStoreIncludeProductsSpec();privatereadonlyIQueryable<Store>Stores;publicIncludeBenchmark(){Stores=newBenchmarkDbContext().Stores.AsQueryable();}[Benchmark]publicvoidEFIncludeExpression(){Expression<Func<Store,IEnumerable<Product>>>IncludeExpr= x =>x.Products;Expression<Func<Product,CustomFields>>ThenIncludeExpr= x =>x.CustomFields;for(inti=0;i<max;i++){_=Stores.Include(IncludeExpr).ThenInclude(ThenIncludeExpr);}}[Benchmark]publicvoidSpecIncludeExpression(){for(inti=0;i<max;i++){_=evaluator.GetQuery(Stores,specInclude);}}[Benchmark]publicvoidSpecIncludeExpressionCached(){for(inti=0;i<max;i++){_=evaluatorCached.GetQuery(Stores,specInclude);}}}publicclassBenchmarkDbContext:DbContext{publicDbSet<Store>Stores{get;set;}protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptionsBuilder){base.OnConfiguring(optionsBuilder);optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Integrated Security=SSPI;Initial Catalog=SpecificationEFTestsDB;ConnectRetryCount=0");}}publicclassStoreIncludeProductsSpec:Specification<Store>{publicStoreIncludeProductsSpec(){Query.Include(x =>x.Products).ThenInclude(x =>x.CustomFields);}}publicclassStore{publicintId{get;set;}publicIEnumerable<Product>Products{get;set;}}publicclassProduct{publicintId{get;set;}publicCustomFieldsCustomFields{get;set;}}publicclassCustomFields{publicintId{get;set;}publicstringCustomText1{get;set;}publicstringCustomText2{get;set;}}} |
…reByIdIncludeCompanyAndCountryAndStoresForCompanySpec' test for more complex include scenario; add it to EF6
Closes#187.
Summary
Query should be built by ORM (EfCore and EF6 in our case). This work uses data from IncludeExpressionInfo to call public API of specified libraries instead of building query manually.
Part with EF6 is not done yet