-
Notifications
You must be signed in to change notification settings - Fork 246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactoring Include
infrastructure to store expression info.
#83
Conversation
…Expression instead of a Body and Parameters.
@fiseni this looks good to me. |
Ok, I'm merging this one. |
That are pretty good news. Is there a plan for creating a new version / package that will be published to nuget? |
We'll publish a new major version soon. We have some more features and changes that we want to introduce (see #84). |
You can build locally to test. Just make a regular project reference or pack and put in a local nuget folder. We aren't publishing prerelease versions of the library at this time. Maybe we could but usually the time between adding enough features for a release and publishing them as a new major version is under a week or two so I'm not sure it would be worth it. |
I see. Gonna download and use a local nuget folder, as you suggested. Thank you |
I did a local build and used it for my scenario, my test are look really good. It works as expected. |
Did you targeted EF 5? |
Ok, I did some performance tests. Tested the evaluation of Evaluations count: 1.000
Evaluations count: 1.000.000
SummaryFirst of all, using the include feature by passing string is always faster, regardless if you're using EF directly or utilizing specifications. Simplified, if I run a web application on this same laptop, serving 1M web requests might take almost a minute (~ 15K req/sec). If I use specifications, then it will be a minute plus a second. |
And here is the actual test, if anyone is interested. [MemoryDiagnoser]
public class SpecIncludeBenchmark
{
private readonly int max = 1000000;
private readonly SpecificationEvaluator evaluator = SpecificationEvaluator.Default;
private readonly Specification<Store> specInclude = new StoreIncludeProductsSpec();
private readonly Specification<Store> specIncludeString = new StoreIncludeProductsAsStringSpec();
private readonly IQueryable<Store> Stores;
public SpecIncludeBenchmark()
{
Stores = new BenchmarkDbContext().Stores.AsQueryable();
}
[Benchmark]
public void EFIncludeExpression()
{
for (int i = 0; i < max; i++)
{
_ = Stores.Include(x => x.Products);
}
}
[Benchmark]
public void EFIncludeString()
{
for (int i = 0; i < max; i++)
{
_ = Stores.Include(nameof(Store.Products));
}
}
[Benchmark]
public void SpecIncludeExpression()
{
for (int i = 0; i < max; i++)
{
_ = evaluator.GetQuery(Stores, specInclude);
}
}
[Benchmark]
public void SpecIncludeString()
{
for (int i = 0; i < max; i++)
{
_ = evaluator.GetQuery(Stores, specIncludeString);
}
}
} |
This PR addresses a few limitations we have with
Include
in specifications. Since we're not able to store second level expressions in this formExpression<Func<TPreviousProperty, TProperty>>
(read more here), we're extracting the property name and concatenating it to the navigation path. This in turn limits us on what expressions we can use forInclude
. We can use only "property selector" expressions. That's almost 90% of the use cases, but anyhow, we're limiting the full potential of the EF queries, and all advancements that they can introduce in the future.In order to mitigate this, we simply have to store this information in some acceptable form, and then during the evaluation should be able to restore the initial expression. A new type
IncludeExpressionInfo
is introduced, which will hold all information to effectively describe an expression. Then, the new extension methods toIQueryable
will utilize this info to callInclude
orThenInclude
EF methods by reflection.By implementing this we will be able to support the new features of EF Core 5, e.g. filtered includes. So, this addresses issue #67 too.
Breaking changes
There are no breaking changes in the usage, the builders have the same API, and any written specification today will still work. All integration tests passed without any modification. Anyhow the
ISpecification
includes changes, no longer holds a collection ofIIncludeAggregator
, instead, it holds a collection ofIncludeExpressionInfo
. Anyone that accessed the specification properties directly, have written their own evaluators, or have written various extensions, then this change represents a breaking change.Performance considerations
Even though we don't utilize reflection heavily, still, it's being used. I don't have performance tests and can't quantify exactly the implications. If anyone can work on that would be great. I don't expect some huge performance issues, but it's worth noting the possible implications.
Implementation
There are changes in the internal infrastructure, but the core logic of this PR is the extensions to
IQueryable