什么时候应该使用 IEnumerable 什么时候使用 IQueryable?
When should I use IEnumerable and when IQueryable?
我试过很多次寻找不同之处,但有多个答案。
一个常见的已知差异是 IEnumerable
从内存中过滤数据,而 IQueryable
在服务器端过滤数据。
这到底是什么意思?
对内存中数据进行过滤是否仍然是服务器端的事情?
我曾尝试在多个地方寻找它们的用途,但无法用简单的文字找到它。
谢谢。
One of the common known difference is that IEnumerable filters data from within memory while IQueryable does it on the server side
你是对的,一切都发生在服务器上。但在上面的句子中,server side
表示 SQL 服务器,而不是 运行 .NET 应用程序的服务器。因此主要思想是 IQueryable
可以转换为 SQL 脚本,将在 SQL 服务器中执行,与从 SQL服务器申请内存然后执行查询in-memory.
IEnumerable<T>
表示产生一系列结果的东西。但是,它不会公开有关序列是如何生成的任何信息。
IQueryable<T>
在Expression
属性处公开关于如何生成序列的信息,形式为expression tree。然后可以轻松地将此信息映射到一组不同的指令。
如果您在 IEnumerable<T>
上调用 Enumerable.Where
,您将传递与 Func<T, bool>
兼容的编译方法。理论上,我们可以解析编译方法的 IL 来弄清楚该方法的作用,并使用它来映射到另一组指令;但这非常复杂。不可避免地,使用它的唯一方法是将所有对象从 server/provider/datasource 加载到内存中,并对每个对象应用编译方法。
如果您在 IQueryable<T>
上调用 Queryable.Where
,您将传入一个根据定义表示不同代码操作的对象 -- Expression<Func<T, bool>>
。例如:
IQueryable<Person> qry = /* ... */;
qry = qry.Where(x => x.LastName.StartsWith("A"));
编译器将 x => x.LastName.StartsWith("A")
转换为表示其各个部分的对象:
Lambda expression, returning a `bool`
with an `x` parameter of type `Person`
Call the `StartsWith` method, passing in a constant string `"A"`, on
Result of the `LastName` property, of
the `x` element
更多,调用Queryable.Where
本身也会修改底层表达式树:
Call to Queryable.Where, passing in
The object at `qry`, and
The previous lambda expression (see above)
当枚举查询时(使用 foreach
,或调用 ToList
,或类似的东西),信息可以很容易地从这个对象映射到另一种形式,例如SQL:
SELECT *
FROM Persons
WHERE LastName LIKE N'A%'
或网络请求:
http://example.com/Person?lastname[0]=a
调用 Queryable.Where
后的最终表达式树将类似于此对象图,如果可以使用构造函数以及对象和集合初始值设定项构造表达式树:
var x = new ParameterExpression {
Type = typeof(Person),
Name = "x"
};
new MethodCallExpression {
Type = typeof(IQueryable<Person>),
Arguments = new ReadOnlyCollection<Expression> {
new ConstantExpression {
Type = typeof(EnumerableQuery<Person>)
},
new UnaryExpression {
NodeType = ExpressionType.Quote,
Type = typeof(Expression<Func<Person, bool>>),
Operand = new Expression<Func<Person, bool>> {
NodeType = ExpressionType.Lambda,
Type = typeof(Func<Person, bool>),
Parameters = new ReadOnlyCollection<ParameterExpression> {
x
},
Body = new MethodCallExpression {
Type = typeof(bool),
Object = new MemberExpression {
Type = typeof(string),
Expression = x,
Member = typeof(Person).GetProperty("LastName")
},
Arguments = new ReadOnlyCollection<Expression> {
new ConstantExpression {
Type = typeof(string),
Value = "A"
}
},
Method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) })
},
ReturnType = typeof(bool)
}
}
},
Method = typeof(Queryable).GetMethod("Where", new[] { typeof(IQueryable<Person>), typeof(Expression<Func<Person, bool>>) })
}
(注意。这是使用 ExpressionTreeToString library 编写的。免责声明:我是作者。)
不是 100% 确定,但我认为这意味着使用 IQueryable,在我们使用 BE 中的 EF 和 SQLike 作为 DB 的示例中,任何 Linq 过程都用它将在 SQL 中转换并发送到数据库,它将详细说明这样的 sql 代码,然后 return 结果。
虽然 IEnumelable 缺少此功能,并且如果您将整个实体转换为 IEnumerable,您将始终在 BE 中详细说明任何过滤器。
我希望我是对的,我已经说得很清楚了,
玩得开心 session
Zev Spitz 已经很好地说明了差异,但我想补充一个极端情况下发生的示例,以突出差异。
假设您的数据在一台服务器上,而应用程序在另一台服务器上运行。这些是通过互联网连接的。
假设您的数据中有一个 table,它有 100 万行,并且您想要一个具有给定 ID 的那个 ID。
忽略您的连接方式,让变量 Table 成为您在代码中对该数据的表示。
现在,我们可以通过请求 Table.Where(x => x.Id == thatId)
.
来获取我们想要的行
如果 Table 是一个 IQueryable,那么这将被转换为对 Table 的那一行的请求,该请求将从存储数据的服务器返回服务器 运行 应用程序。
另一方面,如果我们将其视为一个 IEnumerable,例如通过将其作为 Table.AsEnumerable().Where(x => x.Id == thatId)
来处理,那么这将变成对 table 的所有行的请求,这然后将内存转移到服务器运行上的应用程序。只有在通过互联网传输所有这百万行之后,应用程序才会对数据进行排序并挑选出您真正想要的行。
我试过很多次寻找不同之处,但有多个答案。
一个常见的已知差异是 IEnumerable
从内存中过滤数据,而 IQueryable
在服务器端过滤数据。
这到底是什么意思?
对内存中数据进行过滤是否仍然是服务器端的事情?
我曾尝试在多个地方寻找它们的用途,但无法用简单的文字找到它。
谢谢。
One of the common known difference is that IEnumerable filters data from within memory while IQueryable does it on the server side
你是对的,一切都发生在服务器上。但在上面的句子中,server side
表示 SQL 服务器,而不是 运行 .NET 应用程序的服务器。因此主要思想是 IQueryable
可以转换为 SQL 脚本,将在 SQL 服务器中执行,与从 SQL服务器申请内存然后执行查询in-memory.
IEnumerable<T>
表示产生一系列结果的东西。但是,它不会公开有关序列是如何生成的任何信息。
IQueryable<T>
在Expression
属性处公开关于如何生成序列的信息,形式为expression tree。然后可以轻松地将此信息映射到一组不同的指令。
如果您在 IEnumerable<T>
上调用 Enumerable.Where
,您将传递与 Func<T, bool>
兼容的编译方法。理论上,我们可以解析编译方法的 IL 来弄清楚该方法的作用,并使用它来映射到另一组指令;但这非常复杂。不可避免地,使用它的唯一方法是将所有对象从 server/provider/datasource 加载到内存中,并对每个对象应用编译方法。
如果您在 IQueryable<T>
上调用 Queryable.Where
,您将传入一个根据定义表示不同代码操作的对象 -- Expression<Func<T, bool>>
。例如:
IQueryable<Person> qry = /* ... */;
qry = qry.Where(x => x.LastName.StartsWith("A"));
编译器将 x => x.LastName.StartsWith("A")
转换为表示其各个部分的对象:
Lambda expression, returning a `bool`
with an `x` parameter of type `Person`
Call the `StartsWith` method, passing in a constant string `"A"`, on
Result of the `LastName` property, of
the `x` element
更多,调用Queryable.Where
本身也会修改底层表达式树:
Call to Queryable.Where, passing in
The object at `qry`, and
The previous lambda expression (see above)
当枚举查询时(使用 foreach
,或调用 ToList
,或类似的东西),信息可以很容易地从这个对象映射到另一种形式,例如SQL:
SELECT *
FROM Persons
WHERE LastName LIKE N'A%'
或网络请求:
http://example.com/Person?lastname[0]=a
调用 Queryable.Where
后的最终表达式树将类似于此对象图,如果可以使用构造函数以及对象和集合初始值设定项构造表达式树:
var x = new ParameterExpression {
Type = typeof(Person),
Name = "x"
};
new MethodCallExpression {
Type = typeof(IQueryable<Person>),
Arguments = new ReadOnlyCollection<Expression> {
new ConstantExpression {
Type = typeof(EnumerableQuery<Person>)
},
new UnaryExpression {
NodeType = ExpressionType.Quote,
Type = typeof(Expression<Func<Person, bool>>),
Operand = new Expression<Func<Person, bool>> {
NodeType = ExpressionType.Lambda,
Type = typeof(Func<Person, bool>),
Parameters = new ReadOnlyCollection<ParameterExpression> {
x
},
Body = new MethodCallExpression {
Type = typeof(bool),
Object = new MemberExpression {
Type = typeof(string),
Expression = x,
Member = typeof(Person).GetProperty("LastName")
},
Arguments = new ReadOnlyCollection<Expression> {
new ConstantExpression {
Type = typeof(string),
Value = "A"
}
},
Method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) })
},
ReturnType = typeof(bool)
}
}
},
Method = typeof(Queryable).GetMethod("Where", new[] { typeof(IQueryable<Person>), typeof(Expression<Func<Person, bool>>) })
}
(注意。这是使用 ExpressionTreeToString library 编写的。免责声明:我是作者。)
不是 100% 确定,但我认为这意味着使用 IQueryable,在我们使用 BE 中的 EF 和 SQLike 作为 DB 的示例中,任何 Linq 过程都用它将在 SQL 中转换并发送到数据库,它将详细说明这样的 sql 代码,然后 return 结果。
虽然 IEnumelable 缺少此功能,并且如果您将整个实体转换为 IEnumerable,您将始终在 BE 中详细说明任何过滤器。
我希望我是对的,我已经说得很清楚了, 玩得开心 session
Zev Spitz 已经很好地说明了差异,但我想补充一个极端情况下发生的示例,以突出差异。
假设您的数据在一台服务器上,而应用程序在另一台服务器上运行。这些是通过互联网连接的。
假设您的数据中有一个 table,它有 100 万行,并且您想要一个具有给定 ID 的那个 ID。
忽略您的连接方式,让变量 Table 成为您在代码中对该数据的表示。
现在,我们可以通过请求 Table.Where(x => x.Id == thatId)
.
如果 Table 是一个 IQueryable,那么这将被转换为对 Table 的那一行的请求,该请求将从存储数据的服务器返回服务器 运行 应用程序。
另一方面,如果我们将其视为一个 IEnumerable,例如通过将其作为 Table.AsEnumerable().Where(x => x.Id == thatId)
来处理,那么这将变成对 table 的所有行的请求,这然后将内存转移到服务器运行上的应用程序。只有在通过互联网传输所有这百万行之后,应用程序才会对数据进行排序并挑选出您真正想要的行。