EfCore.WhereIn
1.0.2
dotnet add package EfCore.WhereIn --version 1.0.2
NuGet\Install-Package EfCore.WhereIn -Version 1.0.2
<PackageReference Include="EfCore.WhereIn" Version="1.0.2" />
<PackageVersion Include="EfCore.WhereIn" Version="1.0.2" />
<PackageReference Include="EfCore.WhereIn" />
paket add EfCore.WhereIn --version 1.0.2
#r "nuget: EfCore.WhereIn, 1.0.2"
#:package EfCore.WhereIn@1.0.2
#addin nuget:?package=EfCore.WhereIn&version=1.0.2
#tool nuget:?package=EfCore.WhereIn&version=1.0.2
EfCore.WhereIn
EfCore.WhereIn provides two extension classes:
WhereInExtensions: StandardWhereInandWhereNotInmethods (enforce SQL Server parameter limit).WhereInBatchedExtensions:WhereInBatchedmethod (supports arbitrarily large collections via batching, may impact performance).
Installation
Install via NuGet Package Manager:
# .NET CLI
dotnet add package EfCore.WhereIn
# Or with Package Manager
PM> Install-Package EfCore.WhereIn
Usage
Add the namespace:
using EfCore.WhereIn;
Use the extension methods in your LINQ queries:
var ids = new[] { 1, 2, 3 };
var result = dbContext.Entities.WhereIn(e => e.Id, ids).ToList();
// Or for NOT IN
var excluded = dbContext.Entities.WhereNotIn(e => e.Id, ids).ToList();
// For arbitrarily large collections (batching, may impact performance)
var largeIds = Enumerable.Range(1, 5000).ToArray();
var largeResult = dbContext.Entities.WhereInBatched(e => e.Id, largeIds).ToList();
SQL Output Example
SELECT ... FROM [Entities] WHERE [Id] IN (1, 2, 3)
How It Works
EF Core (with SQL Server) normally translates Where(x => values.Contains(x.Id)) to use OPENJSON, which can be less efficient and harder to optimize. This library uses expression trees and HashSet<T> to force EF Core to generate inline IN (...) SQL, ensuring better performance and compatibility.
- Without EfCore.WhereIn:
WHERE [Id] IN (SELECT [value] FROM OPENJSON(@__ids)) - With EfCore.WhereIn:
WHERE [Id] IN (1, 2, 3) - With EfCore.WhereInBatched:
WHERE [Id] IN (1, 2, ..., 2100) OR [Id] IN (2101, ..., 4200) OR ...
API Reference
IQueryable<T> WhereIn<T, TValue>(this IQueryable<T>, Expression<Func<T, TValue>>, IEnumerable<TValue>)IQueryable<T> WhereNotIn<T, TValue>(this IQueryable<T>, Expression<Func<T, TValue>>, IEnumerable<TValue>)IQueryable<T> WhereInBatched<T, TValue>(this IQueryable<T>, Expression<Func<T, TValue>>, IEnumerable<TValue>)
Compatibility
| EF Core | SQL Server | .NET Target Frameworks |
|---|---|---|
| 5.0+ | 2016+ | netstandard2.0, net8.0, net10.0 |
Testing
- Includes xUnit tests verifying:
- SQL contains
IN (...) - SQL does not contain
OPENJSON WhereIn,WhereNotIn, andWhereInBatchedreturn correct results
- SQL contains
Running Tests
To run tests locally:
cd tests/EfCore.WhereIn.Tests
# .NET CLI
dotnet test
Contributing
Contributions, issues, and feature requests are welcome! Please open an issue or submit a pull request.
License
This project is licensed under the MIT License.
More Information
Further Reading
- EF Core 10.0: Improved translation for parameterized collection
- EF Core Issue #32365: Inline IN clause for small collections
- EF Core Issue #34347: Always inline IN clause
Why not just use Contains?
EF Core's default translation for Contains with SQL Server uses OPENJSON, which can be less efficient and less compatible with some SQL Server versions and query plans. This library ensures you always get the most efficient inline IN (...) clause, and with batching support, you can handle arbitrarily large collections (with a performance tradeoff).
Example: Inline IN vs. OPENJSON
| Approach | SQL Output Example |
|---|---|
| Default Contains | WHERE [Id] IN (SELECT [value] FROM OPENJSON(...)) |
| EfCore.WhereIn | WHERE [Id] IN (1, 2, 3) |
| EfCore.WhereInBatched | WHERE [Id] IN (1, ..., 2100) OR [Id] IN (2101, ...) |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.