要获得所有不平衡订单的清单,我有以下(简化)Linq
var UnbalancedOrders = db.Orders.Where(x => x.TenantId == Transaction.TenantId && x.State == OrderStates.Placed && x.Balance() > 0);
Balance()是订单模型上计算尚未支付多少到期金额的方法。
当我运行代码时,我得到了:
LINQ to Entities does not recognize the method 'Double Balance()' method, and this method cannot be translated into a store expression.
我知道这不会这样,但有没有一种方法可以在Linq语句中实际使用该方法?
更新
public class Order : BasketBase, IModel, IMultiTenant
{
...
public double TotalPayments()
{
return Payments.Sum(x => x.Amount);
}
public double Balance()
{
return (TotalAmmount() - TotalPayments());
}
}
从这个SO-ISSUE我首先想到的是,只使用可以转换为SQL的部分将其转换为一个列表,然后将不可转换的部分应用于SQL。
var UnbalancedOrders = db.Orders.Where(x => x.TenantId == Transaction.TenantId && x.State == OrderStates.Placed).ToList().Where(x => x.Balance() > 0);
这似乎有效,但看起来并不是最佳做法:从DB获取所有数据并再次过滤?
答案 0 :(得分:1)
您可以使用LINQ语法中的保留字let
,如下所示:
from order in db.Orders
let totalPayments = order.Payments.Sum(x => x.Amount)
let totalAmmount = order.Payments.Sum(x => x.Amount) //duplicated totalPayment because IDK this formula
let balance = totalAmmount() - totalPayments
where order.TenantId == Transaction.TenantId && x.State == OrderStates.Placed && balance > 0
select order
如果您不想复制Balance公式,可以使用DTO和AutoMapper的组合,如下所示:
public class Program
{
private static void Main(string[] args)
{
Mapper.Initialize(configuration => configuration.AddProfile<OrderProfile>());
using (var ctx = new Ctx())
{
ctx.Database.CreateIfNotExists();
ctx.Orders.Add(new Order { Value = 100, OtherAdditionalValue = 1 });
ctx.SaveChanges();
ctx.Database.Log = Console.WriteLine;
// this way works, but fetch all database first
var orders = ctx.Orders.ToList().Where(x => x.Balance > 100);
//this way works, but fetch only charges that has balance > 100
var chargingsDtos = ctx.Orders.ProjectTo<OrderDTO>().Where(x => x.Balance > 100).ToList();
}
}
}
public class Ctx : DbContext
{
public DbSet<Order> Orders { get; set; }
}
public class Order
{
private static readonly Func<Order, decimal> CompiledBalance = BalanceExpression.Compile();
public long Id { get; set; }
public decimal Value { get; set; }
public decimal OtherAdditionalValue { get; set; }
public decimal Balance => CompiledBalance(this);
public static Expression<Func<Order, decimal>> BalanceExpression
=> c => c.Value + c.OtherAdditionalValue;
}
public class OrderProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Order, OrderDTO>()
.ForMember(c => c.Balance, de => de.MapFrom(Order.BalanceExpression));
}
}
public class OrderDTO
{
public long Id { get; set; }
public decimal Balance { get; set; }
}
SQL查询输出是这样的:
Opened connection at 09/12/2015 16:44:36 -03:00
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Value] AS [Value],
[Extent1].[OtherAdditionalValue] AS [OtherAdditionalValue]
FROM [dbo].[Orders] AS [Extent1]
-- Executing at 09/12/2015 16:44:36 -03:00
-- Completed in 0 ms with result: SqlDataReader
Closed connection at 09/12/2015 16:44:36 -03:00
Opened connection at 09/12/2015 16:44:36 -03:00
SELECT
1 AS [C1],
[Extent1].[Id] AS [Id],
[Extent1].[Value] + [Extent1].[OtherAdditionalValue] AS [C2]
FROM [dbo].[Orders] AS [Extent1]
WHERE ([Extent1].[Value] + [Extent1].[OtherAdditionalValue]) > cast(100 as decimal(18))
-- Executing at 09/12/2015 16:44:36 -03:00
-- Completed in 1 ms with result: SqlDataReader
Closed connection at 09/12/2015 16:44:36 -03:00
如您所见,行var orders = ctx.Orders.ToList().Where(x => x.Balance > 100);
获取所有数据库,而其他行使用AutoMapper中的Projection包含SQL中的Where子句。
使用此功能,您仍然可以在模型中使用Balance属性,但现在您拥有包含表达式的BalanceExpression以获取Balance值,如果您更改它,它将在任何地方都有效。
答案 1 :(得分:0)
我选择的最终解决方案:
var UnbalancedOrders = db.Orders.Where(x => x.TenantId == Transaction.TenantId && x.State == OrderStates.Placed).ToList().Where(x => x.Balance() > 0);
仅供参考一些背景信息:
db.Orders.Where(x => x.TenantId == Transaction.TenantId && x.State == OrderStates.Placed).ToList()
Linq是EF并将被翻译为SQL。
.Where(x => x.Balance() > 0)
Linq是实体,因此可以使用POCO的方法。
CONTRA:
ToList()将整个集合下载到内存中,然后对列表进行第二次过滤。因此有很多MEM和CPU使用。
PRO: 此解决方案独立于Balance()方法/公式中的任何更改。因此,如果Balance()发生变化,则不会引入错误。
答案 2 :(得分:-1)
首先创建一个VM并将计算出的Balance()保存在其中,以便完成所有计算。 LINQ不适用于计算值。然后使用您的LINQ语句
//The proposed VM. add fields as required
public class OrderVM
{
public int OrderID {get;set;} //Guessing this field as you hv not shown it
public double TotalPayments{get;set;}
public double Balance{get;set;}
}
// **** Add this below in another class
:
:
:
//Note the calculated field is not a part of the LINQ I have removed it.
var allOrders = db.orders.ToList(var UnbalancedOrders = db.Orders.Where(x => x.TenantId == Transaction.TenantId && x.State == OrderStates.Placed ) //filter
List<OrderVM> listOrderVM= new List<OrderVM>();
foreach (var item in allOrders)
{
OrderVM o = new OrderVM;
o.OrderID = Item.OrderID
// add more fields if u need them for the op.
o.Balance = Item.Balance();
o.TotalPayments = Item.TotalPayments();
//You could do an IF here and add only those that are greater than zero...
//But I am showing you how to use LINQ when you have a calculation.
listOrderVM.Add(o);
}
var selectedOrders = listOrderVM.ToList(x=>x.Balance>0);
//selectedOrders will have all orders with balance > 0
//Now LINQ will work.