转载自:https://ilmatte.wordpress.com/2013/01/06/entity-framework-joining-in-memory-data-with-dbset/

The argument of this post is relevant to people using Entity Framework and needing to filter data coming from a Database with a list of in-memory data.

In this article I will try to start summarizing what is well explained in a good article by Michael Hompus, adapting his example to Entity Framework Code First and adding a second issue for distracted programmers.
If you want his clear explanation I suggest you to go for his post:

http://blog.hompus.nl/2010/08/26/joining-an-iqueryable-with-an-ienumerable/

I will start from Michael’s article with the difference that my example will use Entity Framework Code First.
I will try to underline the 2 issues involved with this topic.

It could happen that you want to filter some data bases on a list of values and you want to filter them while querying, in order to avoid loading unuseful data in memory.

In my example I suppose that you already know Entity Framework Code First.
I explicitly invoke a DatabaseInitializer to be sure to create a freshly new database.
I previously created a DbContext with a single Dbset of Customer entities:

public class CustomerContext : DbContext
{
public DbSet Customers { get; set; }
}
and I created the Customer entity:

public class Customer
{
public int Id { get; set; }

public string Name { get; set; }

public string Surname { get; set; }
}
I created a Console application to test the Linq queries I want to analyze.
I want to filter Customers base on their Ids. I want only three of them:

private static void MainMethod()
{
try
{
var customerIds = new List {1,5,7};
using (var context = new CustomerContext())
{
var initializer = new DropCreateDatabaseAlways();
initializer.InitializeDatabase(context);

var customers = from customer in context.Customers
join customerId in customerIds
on customer.Id equals customerId
select customer;
var result = customers.ToList();
}
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}
and this is the resulting query:

SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Surname] AS [Surname]
FROM [dbo].[Customers] AS [Extent1]
INNER JOIN (SELECT
[UnionAll1].[C1] AS [C1]
FROM (SELECT
cast(1 as bigint) AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
UNION ALL
SELECT
cast(5 as bigint) AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable2]) AS [UnionAll1]
UNION ALL
SELECT
cast(7 as bigint) AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2] ON [Extent1].[Id] = [UnionAll2].[C1]
As you can see, a UNION ALL statement is issued for any item in the collection.
It’s time for the first issue:

1) Sql Server has a maximum limit of depth for nested subqueries: 32 (http://msdn.microsoft.com/en-us/library/ms189575%28v=sql.105%29.aspx)
Then, if your in-memory collection gets too big you will get the following exception:

System.Data.EntityCommandExecutionException: An error occurred while executing the command definition. See the inner exception for details. —> System.Data.SqlClient.SqlException: Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries.

As Michael, I will use Enumerable.Range to create a list of the desired length, modifying the MainMethod as in the following snippet:

private static void MainMethod()
{
try
{
var customerIds = Enumerable.Range(1, 50);
using (var context = new CustomerContext())
{
var initializer = new DropCreateDatabaseAlways();
initializer.InitializeDatabase(context);

var customers = from customer in context.Customers
join customerId in customerIds
on customer.Id equals customerId
select customer;
var result = customers.ToList();
}
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}
If you run you’re console application now you will get the exception.

If you had to write the desired SQL on your own you probably would have opted
for a simple: where …. in (…).

This would avoid us incurring in the max limit of nested statement.
If you want to obtain such a result as generated SQL you should modify your Linq
query to use the Contains method as in the following version of: MainMethod:

private static void MainMethod()
{
try
{
var customerIds = Enumerable.Range(1, 50);
using (var context = new CustomerContext())
{
var initializer = new DropCreateDatabaseAlways();
initializer.InitializeDatabase(context);

var customers = from customer in context.Customers
where customerIds.Contains(customer.Id)
select customer;
var result = customers.ToList();
}
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}
Now the resulting query, easier to read than the previous one, is the following:

{SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Surname] AS [Surname]
FROM [dbo].[Customers] AS [Extent1]
WHERE [Extent1].[Id] IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50)}
Obviously there’s something strange if you’re forced to filter with a very big in-memory collection but that’s it.

2)
It’s now time for the second issue. Go back for a while to the original version of our method. I will show it again here:

private static void MainMethod()
{
try
{
var customerIds = new List {1,5,7};
using (var context = new CustomerContext())
{
var initializer = new DropCreateDatabaseAlways();
initializer.InitializeDatabase(context);

var customers = from customer in context.Customers
join customerId in customerIds
on customer.Id equals customerId
select customer;
var result = customers.ToList();
}
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}
The query seems very obvious for people used to SQL but we must always know what kind of collection we are using.
Let’s rewrite the previous query with method chaining syntax like in the following snippet:

private static void MainMethod()
{
try
{
var customerIds = new List {1,5,7};
using (var context = new CustomerContext())
{
var initializer = new DropCreateDatabaseAlways();
initializer.InitializeDatabase(context);

var customers = context.Customers
.Join(customerIds,
customer => customer.Id,
customerId => customerId,
(customer, customerId) => customer)
.Select(customer => customer);
var result = customers.ToList();
}
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}
If we run our console application we will obtain the same query with both versions of our method.
The same query we saw at the beginning:

SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Surname] AS [Surname]
FROM [dbo].[Customers] AS [Extent1]
INNER JOIN (SELECT
[UnionAll1].[C1] AS [C1]
FROM (SELECT
cast(1 as bigint) AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
UNION ALL
SELECT
cast(5 as bigint) AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable2]) AS [UnionAll1]
UNION ALL
SELECT
cast(7 as bigint) AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2] ON [Extent1].[Id] = [UnionAll2].[C1]
The method syntax is more explicit about what’s happening: the IQueryable.Join method is being invoked.
This means that Linq To Entities IQueryable Provider plays its role in generating the resulting SQL: converting the Linq join into a SQL inner join.

The query syntax implies very specific roles to the joined collection depending on their position in the query: the left one is called: the outer collection and the right one: the inner collection.
If we inadvertently revert the order of our 2 lists, happening to put the in-memory list to the left side like in the following snippet:

private static void MainMethod()
{
try
{
var customerIds = new List {1,5,7};
using (var context = new CustomerContext())
{
var initializer = new DropCreateDatabaseAlways();
initializer.InitializeDatabase(context);

var customers = from customerId in customerIds
join customer in context.Customers
on customerId equals customer.Id
select customer;
var result = customers.ToList();
}
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
}
the method invoked will be: IEnumerable.Join and the SQL sent to the Database will be the following (you can see it with Sql Server Profiler):

SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Surname] AS [Surname]
FROM [dbo].[Customers] AS [Extent1]
As you can see, our filter condition simply disappeared: no join nor where…in condition but still the ‘result’ variable will contain only the desired results.

If the left operand in a join statement is of type IEnumberable, the Enumerable.Join extension method will be chosen during method overload resolution.
This means that the whole Customers table will be loaded in memory and then filtered via Linq To Objects…and this is not what we want.

So we definitely need to pay attention when joining in-memory collections with IQueryable collections and remember to always put the IQueryable to the left side.

Entity Framework: Joining in memory data with DbSet的更多相关文章

  1. The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer' registered in the application config file for the ADO.NET provider with invariant name

    可以强迫部署EntityFramework.SqlServer.dll这个文件到输出目录 找到1个老外的帖子,戳这里(本人测试无效,大家有可能试一下..) 解决方案以下: 在EF的上下文代码CS文件( ...

  2. Entity Framework mvc Code First data migration

    1. Code First 可以先在代码里写好数据模型,自动生成DB.下一次启动的时候会根据__MigrationHistory判断 数据库是否和模型一致. 详情参考:http://blogs.msd ...

  3. Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio » 读取关系数据

    Reading related data¶ 9 of 9 people found this helpful The Contoso University sample web application ...

  4. EntityFrame Work:No Entity Framework provider found for the ADO.NET provider with invariant name 'System.Data.SqlClient'

    今天试着学习了Entity Frame Work遇到的问题是 The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlP ...

  5. 让EF飞一会儿:如何用Entity Framework 6 连接Sqlite数据库

    获取Sqlite 1.可以用NuGet程序包来获取,它也会自动下载EF6 2.在Sqlite官网上下载对应的版本:http://system.data.sqlite.org/index.html/do ...

  6. 如何用Entity Framework 6 连接Sqlite数据库[转]

    获取Sqlite 1.可以用NuGet程序包来获取,它也会自动下载EF6 2.在Sqlite官网上下载对应的版本:http://system.data.sqlite.org/index.html/do ...

  7. Entity Framework Core 2.0 入门简介

    不多说废话了, 直接切入正题. EF Core支持情况 EF Core的数据库Providers: 此外还即将支持CosmosDB和 Oracle. EFCore 2.0新的东西: 查询: EF.Fu ...

  8. Entity Framework Core 2.0 入门

    该文章比较基础, 不多说废话了, 直接切入正题. 该文分以下几点: 创建Model和数据库 使用Model与数据库交互 查询和保存关联数据 EF Core支持情况 EF Core的数据库Provide ...

  9. Lerning Entity Framework 6 ------ Defining Relationships

    There are three types of relationships in database. They are: One-to-Many One-to-One Many-to-Many Th ...

随机推荐

  1. CentOS 一个网卡设置多个IP

    方法1:少量IP手动绑定: (这里以绑定IP到eth0为例,其它网卡的话修改相应的文件名即可) 1.复制ifcfg-eth0的网卡配置文件并改名为ifcfg-eth0:0 [root@akinlau ...

  2. 更新页面缓存OutputCache

    为什么要使用OutputCache 我认为OutputCache是最简单的缓存技术了,它针对的是页面级别的,简单的一条指令就可以达到缓存的效果,有效的减轻服务器的压力和减少带宽,对于网站一些不会频繁更 ...

  3. ASP.NET 5系列教程 (三):view components介绍

    在ASP.NET MVC 6中,view components (VCs) 功能类似于虚拟视图,但是功能更加强大. VCs兼顾了视图和控制器的优点,你可以把VCs 看作一个Mini 控制器.它负责控制 ...

  4. C#课外实践——校园二手平台(技术篇3)

    说明:生活中,又有谁,能真正摆脱周围环境的束缚,而追随自己的内心呢? ListView的简单用法. 最后展示几张效果图吧 主窗体 登录窗体,虽然没有角色 选择,但已经隐藏在代码里了. 选择购买窗体,这 ...

  5. Nagios学习笔记四:基于NRPE监控远程Linux主机

    1.NRPE简介 Nagios监控远程主机的方法有多种,其方式包括SNMP.NRPE.SSH和NCSA等.这里介绍其通过NRPE监控远程Linux主机的方式. NRPE(Nagios Remote P ...

  6. atitit.ajax bp dwr 3.的注解方式配置使用流程总结 VO9o.....

    atitit.ajax bp dwr 3.的注解方式配置使用流程总结 VO9o..... 1. 安装配置 1 1.1. 下载  dwr.jar 1M 1 1.2. 配置注解方式..web.xml 1 ...

  7. paip.提升效率--gui 的选择--swing最佳实践swt awt

    paip.提升效率--gui 的选择--swing最佳实践swt awt ////////////////弹出消息框. ////////////////myeclipse swing 开发最佳实践.. ...

  8. Oracle数据库建表+添加数据练习

    SQL脚本: --建表 --student表+注释 create table student( sno ) not null, sname ) not null, ssex ) not null, s ...

  9. iOS-OC内存管理

    目标 1.[理解]内存管理 2.[掌握]第一个MRC程序 3.[掌握]内存管理的原则 4.[理解]野指针与僵尸对象 5.[理解]单个对象的内存管理 6.[理解]多个对象的内存管理 7.[掌握]set方 ...

  10. iOS开发---iPhone SDK 包含哪些东西?

    第一部分: 在使用Intel芯片的Macintosh计算机开发iOS应用程序所需的全部接口.工具以及资源全都包含于iPhone SDK. 苹果公司将大部分系统接口发布在框架这种特殊的数据包.一个框架就 ...