Entity Framework Core – Contains Check Via Temp Tables

In this article:

pg
Pawel Gerr is architect and consultant at Thinktecture. He focuses on backends with .NET Core and knows Entity Framework inside out.

Motivation

One of the main queries in my projects is to select multiple database records having a collection of some kind of identifiers. Most of the time it works very well using the method Contains().

				
					List<Guid> ids = ...;

var customers = dbContext.Customers.Where(c => ids.Contains(c.Id));
				
			

The query above is translated by the Entity Framework Core (EF Core) to IN clause: WHERE Id IN ('id_1', ... 'id_n')

Most of the time using an IN clause is not a problem but when having a big collection of ids then the transmission of a huge SQL statement and parsing of it will have negative impact on the performance. This is just one use case of many.

In the second use case we have a collection of tuples, say a collection of (customerId, productId). In this case the method Contains can’t help us, we have to use a JOIN but joining a query with an in-memory-collection (like a List<>) will lead to downloading all data from database because the JOIN will be done in memory.

				
					List<(Guid customerId, Guid productId)> tuples = ...;

// can cause performance issues
var orderItems = ctx.OrderItems.Join(tuples,
                                     i => new { i.Order.CustomerId, i.ProductId },
                                     t => new {
                                                CustomerId = t.customerId, 
                                                ProductId = t.productId
                                              },
                                     (i, t) => i);
				
			

In both cases it would be better to Bulk Insert the ids into a temp table to perform a JOIN with it.

Solution

Use the following links to try out the demos:

Before using a temp table with EF Core we have to introduce it to our DbContext using the extension method ConfigureTempTable with desired column type(s).
The extension method returns an instance of QueryTypeBuilder for further configuration but most of the time there is no need for that.

Remarks: For different column types we have to make multiple calls of ConfigureTempTable. For example one temp table with Guid, the other with Guid? (i.e. nullable Guid), the third with a string, etc.

				
					public class DemoDbContext : DbContext
{
   ...

   protected override void OnModelCreating(ModelBuilder modelBuilder)
   {
      ...

      modelBuilder.ConfigureTempTable<Guid>();        // for use case 1
      modelBuilder.ConfigureTempTable<Guid, Guid>();  // for use case 2
   }
}

				
			

After introduction of the temp tables we now may use them as often as needed.

The solution for 1st use case:

				
					List<Guid> ids = ...;

var tempTableQuery = await ctx.BulkInsertTempTableAsync(ids);
var customers = await ctx.Customers
                         .Join(tempTableQuery, c => c.Id, t => t.Column1, (c, t) => c)
                         .ToListAsync();
				
			

Remarks: Each call of BulkInsertTempTableAsync is using a different temp table name by default so there are no conflicts when using this method multiple times. For more control over temp table creation and the bulk insert via SqlBulkCopy provide an instance of type SqlBulkInsertOptions as the 2nd parameter.

The solution for the 2nd use case:

				
					List<(Guid customerId, Guid productId)> tuples = ...;

var tempTableQuery = await ctx.BulkInsertTempTableAsync(tuples);
var orderItems = await ctx.OrderItems.Join(tempTableQuery,
                                           i => new { i.Order.CustomerId, i.ProductId },
                                           t => new {
                                                      CustomerId = t.Column1, 
                                                      ProductId = t.Column2
                                                    },
                                           (i, t) => i)
                          .ToListAsync();
				
			

The NuGet package will be released in the near future, at the moment I’m gathering feedback.

Free
Newsletter

Current articles, screencasts and interviews by our experts

Don’t miss any content on Angular, .NET Core, Blazor, Azure, and Kubernetes and sign up for our free monthly dev newsletter.

EN Newsletter Anmeldung (#7)
Related Articles
.NET
pg
While basic value objects solve primitive obsession, complex domain requirements need sophisticated modeling techniques. This article explores advanced patterns using Thinktecture.Runtime.Extensions to tackle real-world scenarios: open-ended dates for employment contracts, composite file identifiers across storage systems, recurring anniversaries without year components, and geographical jurisdictions using discriminated unions.
19.10.2025
.NET
pg
Domain models often involve concepts that exist in multiple distinct states or variations. Traditional approaches using enums and nullable properties can lead to invalid states and scattered logic. This article explores how discriminated unions provide a structured, type-safe way to model domain variants in .NET, aligning perfectly with Domain-Driven Design principles while enforcing invariants at the type level.
06.10.2025
.NET
pg
Learn how to seamlessly integrate Smart Enums with essential .NET frameworks and libraries. This article covers practical solutions for JSON serialization, ASP.NET Core model binding for both Minimal APIs and MVC controllers, and Entity Framework Core persistence using value converters. Discover how Thinktecture.Runtime.Extensions provides dedicated packages to eliminate integration friction and maintain type safety across your application stack.
21.09.2025