Implement Include/ThenInclude as adapter - #188

Merged
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter
Dec 21, 2021
Merged

Implement Include/ThenInclude as adapter#188
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter

Conversation

@devbased

@devbaseddevbased commented Dec 19, 2021

Copy link
Copy Markdown
Contributor

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

@fiseni

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression4.600 s0.0849 s0.1441 s502000.00003 GB
SpecIncludeExpression5.619 s0.1122 s0.1292 s585000.00003 GB
SpecIncludeExpressionCached3.642 s0.0724 s0.1015 s415000.00002 GB

@fiseni
fiseni self-requested a review December 20, 2021 14:34
@fiseni

fiseni commented Dec 20, 2021

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression273.9 ms1.10 ms0.86 ms31500.0000189 MB
SpecIncludeExpression642.3 ms7.26 ms6.79 ms69000.0000417 MB
SpecIncludeExpressionCached377.7 ms7.44 ms6.96 ms44000.0000266 MB

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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rethinking Include implementation

2 participants

@devbased@fiseni
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Implement Include/ThenInclude as adapter - #188

Merged
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter
Dec 21, 2021
Merged

Implement Include/ThenInclude as adapter#188
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter

Conversation

@devbased

@devbaseddevbased commented Dec 19, 2021

Copy link
Copy Markdown
Contributor

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

@fiseni

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression4.600 s0.0849 s0.1441 s502000.00003 GB
SpecIncludeExpression5.619 s0.1122 s0.1292 s585000.00003 GB
SpecIncludeExpressionCached3.642 s0.0724 s0.1015 s415000.00002 GB

@fiseni
fiseni self-requested a review December 20, 2021 14:34
@fiseni

fiseni commented Dec 20, 2021

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression273.9 ms1.10 ms0.86 ms31500.0000189 MB
SpecIncludeExpression642.3 ms7.26 ms6.79 ms69000.0000417 MB
SpecIncludeExpressionCached377.7 ms7.44 ms6.96 ms44000.0000266 MB

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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rethinking Include implementation

2 participants

@devbased@fiseni
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implement Include/ThenInclude as adapter - #188

Merged
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter
Dec 21, 2021
Merged

Implement Include/ThenInclude as adapter#188
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter

Conversation

@devbased

@devbaseddevbased commented Dec 19, 2021

Copy link
Copy Markdown
Contributor

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

@fiseni

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression4.600 s0.0849 s0.1441 s502000.00003 GB
SpecIncludeExpression5.619 s0.1122 s0.1292 s585000.00003 GB
SpecIncludeExpressionCached3.642 s0.0724 s0.1015 s415000.00002 GB

@fiseni
fiseni self-requested a review December 20, 2021 14:34
@fiseni

fiseni commented Dec 20, 2021

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression273.9 ms1.10 ms0.86 ms31500.0000189 MB
SpecIncludeExpression642.3 ms7.26 ms6.79 ms69000.0000417 MB
SpecIncludeExpressionCached377.7 ms7.44 ms6.96 ms44000.0000266 MB

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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rethinking Include implementation

2 participants

@devbased@fiseni
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implement Include/ThenInclude as adapter - #188

Merged
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter
Dec 21, 2021
Merged

Implement Include/ThenInclude as adapter#188
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter

Conversation

@devbased

@devbaseddevbased commented Dec 19, 2021

Copy link
Copy Markdown
Contributor

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

@fiseni

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression4.600 s0.0849 s0.1441 s502000.00003 GB
SpecIncludeExpression5.619 s0.1122 s0.1292 s585000.00003 GB
SpecIncludeExpressionCached3.642 s0.0724 s0.1015 s415000.00002 GB

@fiseni
fiseni self-requested a review December 20, 2021 14:34
@fiseni

fiseni commented Dec 20, 2021

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression273.9 ms1.10 ms0.86 ms31500.0000189 MB
SpecIncludeExpression642.3 ms7.26 ms6.79 ms69000.0000417 MB
SpecIncludeExpressionCached377.7 ms7.44 ms6.96 ms44000.0000266 MB

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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rethinking Include implementation

2 participants

@devbased@fiseni
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Implement Include/ThenInclude as adapter - #188

Merged
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter
Dec 21, 2021
Merged

Implement Include/ThenInclude as adapter#188
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter

Conversation

@devbased

@devbaseddevbased commented Dec 19, 2021

Copy link
Copy Markdown
Contributor

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

@fiseni

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression4.600 s0.0849 s0.1441 s502000.00003 GB
SpecIncludeExpression5.619 s0.1122 s0.1292 s585000.00003 GB
SpecIncludeExpressionCached3.642 s0.0724 s0.1015 s415000.00002 GB

@fiseni
fiseni self-requested a review December 20, 2021 14:34
@fiseni

fiseni commented Dec 20, 2021

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression273.9 ms1.10 ms0.86 ms31500.0000189 MB
SpecIncludeExpression642.3 ms7.26 ms6.79 ms69000.0000417 MB
SpecIncludeExpressionCached377.7 ms7.44 ms6.96 ms44000.0000266 MB

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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rethinking Include implementation

2 participants

@devbased@fiseni
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implement Include/ThenInclude as adapter - #188

Merged
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter
Dec 21, 2021
Merged

Implement Include/ThenInclude as adapter#188
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter

Conversation

@devbased

@devbaseddevbased commented Dec 19, 2021

Copy link
Copy Markdown
Contributor

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

@fiseni

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression4.600 s0.0849 s0.1441 s502000.00003 GB
SpecIncludeExpression5.619 s0.1122 s0.1292 s585000.00003 GB
SpecIncludeExpressionCached3.642 s0.0724 s0.1015 s415000.00002 GB

@fiseni
fiseni self-requested a review December 20, 2021 14:34
@fiseni

fiseni commented Dec 20, 2021

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression273.9 ms1.10 ms0.86 ms31500.0000189 MB
SpecIncludeExpression642.3 ms7.26 ms6.79 ms69000.0000417 MB
SpecIncludeExpressionCached377.7 ms7.44 ms6.96 ms44000.0000266 MB

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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rethinking Include implementation

2 participants

@devbased@fiseni
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Implement Include/ThenInclude as adapter - #188

Merged
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter
Dec 21, 2021
Merged

Implement Include/ThenInclude as adapter#188
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter

Conversation

@devbased

@devbaseddevbased commented Dec 19, 2021

Copy link
Copy Markdown
Contributor

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

@fiseni

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression4.600 s0.0849 s0.1441 s502000.00003 GB
SpecIncludeExpression5.619 s0.1122 s0.1292 s585000.00003 GB
SpecIncludeExpressionCached3.642 s0.0724 s0.1015 s415000.00002 GB

@fiseni
fiseni self-requested a review December 20, 2021 14:34
@fiseni

fiseni commented Dec 20, 2021

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression273.9 ms1.10 ms0.86 ms31500.0000189 MB
SpecIncludeExpression642.3 ms7.26 ms6.79 ms69000.0000417 MB
SpecIncludeExpressionCached377.7 ms7.44 ms6.96 ms44000.0000266 MB

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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rethinking Include implementation

2 participants

@devbased@fiseni
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Implement Include/ThenInclude as adapter - #188

Merged
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter
Dec 21, 2021
Merged

Implement Include/ThenInclude as adapter#188
fiseni merged 6 commits into
ardalis:mainfrom
devbased:include-adapter

Conversation

@devbased

@devbaseddevbased commented Dec 19, 2021

Copy link
Copy Markdown
Contributor

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

@fiseni

Copy link
Copy Markdown
Collaborator

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

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression4.600 s0.0849 s0.1441 s502000.00003 GB
SpecIncludeExpression5.619 s0.1122 s0.1292 s585000.00003 GB
SpecIncludeExpressionCached3.642 s0.0724 s0.1015 s415000.00002 GB

@fiseni
fiseni self-requested a review December 20, 2021 14:34
@fiseni

fiseni commented Dec 20, 2021

Copy link
Copy Markdown
Collaborator

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:

MethodMeanErrorStdDevGen 0Allocated
EFIncludeExpression273.9 ms1.10 ms0.86 ms31500.0000189 MB
SpecIncludeExpression642.3 ms7.26 ms6.79 ms69000.0000417 MB
SpecIncludeExpressionCached377.7 ms7.44 ms6.96 ms44000.0000266 MB

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
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rethinking Include implementation

2 participants

@devbased@fiseni