質問

I have been trying for days now to solve the following problem in WCF RIA for lightswitch using linq:

the entity is:

moveDate Direction moveQuantity moveSignedQty

moveSignedQty is positive for In and negative for Out Direction

What I need to do is create a WCF Service in the form

tDate OBalance CBalance

CBalance is the sum of moveSignedQty for a particular moveDate OBalance is the sum of mpveSignedQt for the previous moveDate; it is zero if there in no previous day value.

My approach below did not work:

     Dim close = From c In Me.Context.StockMovements
                        Order By c.DateOfMovement
                        Group New With {c} By _
                        tranDate = CDate(c.DateOfMovement) _
                        Into g = Group
                        Let cBal = g.Sum(Function(s) s.c.SignedQuantity_KG)
                        Let fDate = g.OrderBy(Function(d) d.c.DateOfMovement).FirstOrDefault
                      Select New accStockBalance With {
                          .TransactionDate = tranDate, _
                          .ClosingBalance = cBal}

    Dim sBal = close.GroupBy(Function(d) d.TransactionDate).Select( _
                                                   Function(b)
                                                       Dim subb = b.OrderBy(Function(t) t.TransactionDate)
                                                       Return subb.Select( _
                                                           Function(s, i) New With {
                                                               .TransactionDate = s.TransactionDate, _
                                                               .ClosingBalance = subb.ElementAt(i).ClosingBalance, _
                                                               .OpeningBalance = If(i = 0, 0, subb.ElementAt(i - 1).ClosingBalance)})
                                                   End Function)

Example:

moveDate Direction moveQuantity moveSignedQty
13/02/2013 In 30 30
13/02/2013 Out 4 -4
13/02/2013 Out 10 -10
14/02/2013 Out 4 -4
14/02/2013 Out 4 -4
14/02/2013 In 7 7
15/02/2013 In 15 15

Expected result:

tDate OBalance cBalance
13/02/2013 0 16
14/02/2013 16 15
15/02/2013 15 30

The last bit sBal threw up an error that lambda statements cannot be converted to expression trees.

Kindly guide me. I have read several Q and A in this and other forums help please.

Please forgive my terrible formatting couldnt figure out how to format the example to table format

役に立ちましたか?

解決

The function below is composed of 2 statements.

Function(b)
  Dim subb = ...
  Return ...

This kind of function can't be used in LINQ query. Moreover, the operators ElementAt and Select using index arguments aren't supported by EntityFramework.

I show you here a solution. It is written in C#. I do not think you'll have difficulty translating the code in VB. Furthermore, I do not use the fluent LINQ syntax that I do not find very understandable. Finally, for pedagogical concern I avoid using anonymous types.

The first thing to do is actually write a query that sums the movements by date. This request must allow to obtain a list of object (MovementSumItem class) each containing the date and the sum of the movements of the day.

class MovementSumItem
{
    public DateTime Date { get; set; }
    public int? TotalQty { get; set; }
}

The TotalQty property is declared as nullable. I'll explain why later.

I do not understand why your close query is so complicated!?! You just need to use the GroupBy operator once. And there is no interest in using the OrderBy operator.

IQueryable<MovementSumItem> movementSumItemsQuery =
  context.StockMovements
      .GroupBy(
          // group key selector: the key is just the date
          m => m.MoveDate,
          // project each group to a MovementSumItem   
          (groupKey, items) => new MovementSumItem {
              // date is the key of the group
              Date = groupKey,
              // items group sum
              TotalQty = items.Sum(i => i.SignedQty),
          });

Now, we must be able to determine for each item which is the previous item. And this of course without first execute the query below.

Here is the logical expression I propose:

Func<MovementSumItem,MovementSumItem> previousItemSelector = item =>
    movementSumItemsQuery           // from all items
    .Where(b => b.Date < item.Date) // consider only those corresponding to a previous date
    .OrderByDescending(b => b.Date) // ordering them from newest to oldest
    .FirstOrDefault();              // take the first (so, the newest) or null if collection is empty

This expression does not use any index notion. It is therefore compatible with Entity Framework.

Combining this expression with the query above, we can write the complete query. This final request must allow to obtain a list of object (BalanceItem class) each containing the date, the opening balance (sum of movements from previous item) and the closing balance (sum of movements from current item).

class BalanceItem
{
    public DateTime Date { get; set; }
    public int OpeningBalance { get; set; }
    public int ClosingBalance { get; set; }
}

Logically, the final query can be written:

IQueryable<BalanceItem> balanceItemsQuery =
    movementSumItemsQuery
    .Select(
        item => new BalanceItem() {
            Date = item.Date,
            OpeningBalance = previousItemSelector(item).TotalQty ?? 0,
            ClosingBalance = item.TotalQty ?? 0
        });

Unfortunately Entity Framework does not support the invocation of the function previousItemSelector. So we must integrate the expression in the query.

IQueryable<BalanceItem> balanceItemsQuery =
    movementSumItemsQuery
    .Select(
        item => new BalanceItem()
        {
            Date = item.Date,
            OpeningBalance = movementSumItemsQuery
                .Where(b => b.Date < item.Date)
                .OrderByDescending(b => b.Date)
                .FirstOrDefault().TotalQty ?? 0,
            ClosingBalance = item.TotalQty ?? 0
        });

Finally, to run the query, simply use (for example) the ToList operator.

List<BalanceItem> result = balanceItemsQuery.ToList();

Moreover, balanceItemsQuery being IQueryable, you can specify the query by adding, for example, a filter on the date:

IQueryable<BalanceItem> balanceItemsOfTheYearQuery = balanceItemsQuery
    .Where(x => x.Date.Year == 2014);

Finally, you can verify that the query executes well via a single SQL query using the ToString function of the Query object.

Console.WriteLine(balanceItemsQuery.ToString());

Why TotalQty is declared nullable?

Otherwise, the OpeningBalance value expression should be written:

 OpeningBalance = movementSumItemsQuery
     .Where(b => b.Date < item.Date)
     .OrderByDescending(b => b.Date)
     .FirstOrDefault() == null ? 0 : movementSumItemsQuery
         .Where(b => b.Date < item.Date)
         .OrderByDescending(b => b.Date)
         .FirstOrDefault().TotalQty

However, the comparison .FirstOrDefault() == null is not supported by Entity Framework.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top