EF + LINQ:如何定义和利用变量(可能是 Func 变量)来定义 Select 语句的一部分?
EF + LINQ: How do I define and utilize a variable (maybe a Func variable) to define part of a Select statement?
在尝试使用变量之前(这有 sql 服务器进行计算):
IQueryable<MyEntity> query = _dbSet; // IQueryable<TEntity>
var results = query.Select(m => new MyViewModel
{
MyCalculation = m.Column1 * m.Column2
}).ToList();
我想做什么(从 Func 变量或某种其他类型的变量动态创建我的 select 语句的一部分以允许这样做):
IQueryable<MyEntity> query = _dbSet; // IQueryable<TEntity>
Func<MyEntity, decimal> funcVariableAttempt = m => m.Column1 * m.Column2;
var results = query.Select(m => new MyViewModel
{
MyCalculation = funcVariableAttempt.Invoke(m) // My foolish attempt does not work.
}).ToList();
当我尝试我想要的(也就是我的愚蠢尝试)时得到的错误:
LINQ to Entities does not recognize the method 'System.Decimal Invoke(MyProject.Repository.Models.MyEntity)' method, and this method cannot be translated into a store expression.
如何定义和使用变量(可能是 Func 变量)来定义 Select 语句的一部分?
如您所知,您的 funcVariableAttempt
对您的数据库没有意义,因此您必须在 linq-to-object 上下文中调用您的方法。例如,首先以 Enumerable
形式获取数据,然后调用您的方法:
var results = query.Select(m => new {
Column1= col1,
Column2= col2
}).AsEnumerable()
.Select(m => new MyViewModel
{
MyCalculation = Foo(m.Column1, m.Column2)
});
*注:代码未经测试。
您应该先调用 ToList() 并对内存中的结果执行 Select() 。
var results = query.ToList()
.Select(m => new MyViewModel {
MyCalculation = Foo(m.Column1, m.Column2)
});
您正在尝试将 Select 作为查询的一部分执行。您应该只使用常规函数进行映射计算。 Lambda 函数对 LINQ 很有用,但在这种情况下不需要它们。
这是一个完全有效的问题,我之前偶然发现并找到了适合我的解决方案。
您的 Func<MyEntity, decimal>
的问题在于它是一个委托,并且 O/R 映射器必须能够访问内部表达式(在您的情况下是两个属性的乘积)。但是这些信息被编译到委托中并永远隐藏。
但是,如果您从 Expression<Func<MyEntity, decimal>> customCalculation
开始,事情看起来更有希望,因为您拥有作为表达式树的内部逻辑。
问题是:您不能调用表达式。 customCalculation(m)
无法编译。
编译器会让你写
m => new MyViewModel { MyCalculation = customCalculation.Compile()(m) }
,但这不会被大多数 O/R 映射器理解。
但您看到它至少以某种方式包含 customCalculation
lambda 表达式以及它与周围表达式的关系。
从这里到原始工作版本中的表达式树涉及一些表达式操作:
我们必须用要成为 Compile()
d 的 lambda 的 body 替换 customCalculation.Compile()(m)
,但替换了 lambda 的参数与委托调用的相应表达式。因此,如果 customCalculation
是 x => x.Column1 * x.Column2
,则 customCalculation.Compile()(m)
必须替换为 m.Column1 * m.Column2
这样做并不简单,因为 lambda 本身必须从编译器生成的闭包实例中的字段中挖掘出来 class。
我已经发布了这个表达式操纵器的实现 。希望对您有所帮助。
有了它,您应该能够:
var customCalculation = (Expression<Func<MyEntity, decimal>>)(x => x.Column1 * x.Column2);
var selector = Express.Prepare((Expression<Func<MyEntity, MyViewModel>>)(m => new MyViewModel { MyCalculation = customCalculation.Compile()(m) }));
var result = query.Select(selector).ToList();
在尝试使用变量之前(这有 sql 服务器进行计算):
IQueryable<MyEntity> query = _dbSet; // IQueryable<TEntity>
var results = query.Select(m => new MyViewModel
{
MyCalculation = m.Column1 * m.Column2
}).ToList();
我想做什么(从 Func 变量或某种其他类型的变量动态创建我的 select 语句的一部分以允许这样做):
IQueryable<MyEntity> query = _dbSet; // IQueryable<TEntity>
Func<MyEntity, decimal> funcVariableAttempt = m => m.Column1 * m.Column2;
var results = query.Select(m => new MyViewModel
{
MyCalculation = funcVariableAttempt.Invoke(m) // My foolish attempt does not work.
}).ToList();
当我尝试我想要的(也就是我的愚蠢尝试)时得到的错误:
LINQ to Entities does not recognize the method 'System.Decimal Invoke(MyProject.Repository.Models.MyEntity)' method, and this method cannot be translated into a store expression.
如何定义和使用变量(可能是 Func 变量)来定义 Select 语句的一部分?
如您所知,您的 funcVariableAttempt
对您的数据库没有意义,因此您必须在 linq-to-object 上下文中调用您的方法。例如,首先以 Enumerable
形式获取数据,然后调用您的方法:
var results = query.Select(m => new {
Column1= col1,
Column2= col2
}).AsEnumerable()
.Select(m => new MyViewModel
{
MyCalculation = Foo(m.Column1, m.Column2)
});
*注:代码未经测试。
您应该先调用 ToList() 并对内存中的结果执行 Select() 。
var results = query.ToList()
.Select(m => new MyViewModel {
MyCalculation = Foo(m.Column1, m.Column2)
});
您正在尝试将 Select 作为查询的一部分执行。您应该只使用常规函数进行映射计算。 Lambda 函数对 LINQ 很有用,但在这种情况下不需要它们。
这是一个完全有效的问题,我之前偶然发现并找到了适合我的解决方案。
您的 Func<MyEntity, decimal>
的问题在于它是一个委托,并且 O/R 映射器必须能够访问内部表达式(在您的情况下是两个属性的乘积)。但是这些信息被编译到委托中并永远隐藏。
但是,如果您从 Expression<Func<MyEntity, decimal>> customCalculation
开始,事情看起来更有希望,因为您拥有作为表达式树的内部逻辑。
问题是:您不能调用表达式。 customCalculation(m)
无法编译。
编译器会让你写
m => new MyViewModel { MyCalculation = customCalculation.Compile()(m) }
,但这不会被大多数 O/R 映射器理解。
但您看到它至少以某种方式包含 customCalculation
lambda 表达式以及它与周围表达式的关系。
从这里到原始工作版本中的表达式树涉及一些表达式操作:
我们必须用要成为 Compile()
d 的 lambda 的 body 替换 customCalculation.Compile()(m)
,但替换了 lambda 的参数与委托调用的相应表达式。因此,如果 customCalculation
是 x => x.Column1 * x.Column2
,则 customCalculation.Compile()(m)
必须替换为 m.Column1 * m.Column2
这样做并不简单,因为 lambda 本身必须从编译器生成的闭包实例中的字段中挖掘出来 class。
我已经发布了这个表达式操纵器的实现
有了它,您应该能够:
var customCalculation = (Expression<Func<MyEntity, decimal>>)(x => x.Column1 * x.Column2);
var selector = Express.Prepare((Expression<Func<MyEntity, MyViewModel>>)(m => new MyViewModel { MyCalculation = customCalculation.Compile()(m) }));
var result = query.Select(selector).ToList();