LINQ中未知的EF Poco方法

时间:2015-12-08 15:44:47

标签: entity-framework linq

要获得所有不平衡订单的清单,我有以下(简化)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获取所有数据并再次过滤?

3 个答案:

答案 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.