开发者

Refactor LINQ ... multiplying property and child property from a list

I am still coming to terms with LINQ and I am trying to refactor the following foreach loop into it's LINQ equivalent. Here's the foreach loop that I am trying to convert;

var NetTotal = 0M;

foreach (var cheque in ListOfCheques)
{
    var exchangeRate = (from exc in cheque.ExchangeRates
                        where exc.Type == EnumExchangeRate.ForCheque
                        select exc).FirstOrDefault();

    NetTotal = exchangeRate != null ? NetTotal + cheque.NetAmount * exchangeRate.Rate : NetTotal + cheque.NetAmount;
}

return NetTotal ;

and the LINQ code that I've come up with;

var NetTotal = (from cheque in ListOfCheques
                join exc in ListOfCheques.SelectMany(b => b.ExchangeRates) on cheque.ID equals exrate.Cheque.ID into chequeWithRate
                where income.ExchangeRates.Select(x => x.Type).Equals(EnumExchangeRate.ForCheque)
                from ur in chequeWithRate.DefaultIfEmpty()
                select ur).Sum(x => x.Cheque.NetAmount * x.Rate);

return Ne开发者_JAVA百科tTotal;

The important points that I am struggling with;

  1. It's possible that the "ExchangeRates" list within the Cheque class does not exist, i.e. it does not need an exchange rate.
  2. If there is no exchange rate found it should default to 1. How can I set that... I was hoping to set it within DefaultIfEmpty(1).

Any help is greatly appreciated.


Like any refactoring, just chip away piece by piece. Firstly, rather than foreach just use a Sum() like so.

return ListOfCheques.Sum(c =>
{
    var exchangeRate = (from exc in c.ExchangeRates
        where exc.Type == EnumExchangeRate.ForCheque
        select exc).FirstOrDefault(); 
    return c.NetAmount * (exchangeRate ?? new ExchangeRate(){ Rate = 1 }).Rate;
});

(it'd be nice if the default value for the ExchangeRate.Rate property was 1)

I'd rewrite the exchangeRate function to a simple format. Are you sure you want FirstOrDefault and not SingleOrDefault?

var exchangeRate = c.ExchangeRates.
    FirstOrDefault(ex => ex.Type == EnumExchangeRate.ForCheque);

and then this can be swapped into the first statement, leaving the resulting final product below.

A one liner if you want it to be!

return ListOfCheques.Sum(c => c.NetAmount * 
    (c.ExchangeRates.FirstOrDefault(ex => ex.Type == EnumExchangeRate.ForCheque)
        ?? new ExchangeRate() { Rate = 1 }).Rate);

edit

Clarification on the ?? new ExchangeRate()

Rather than doing the != null ? (amount * rate) : (rate) I prefer to coalesce the ExchangeRate object with a new such object with Rate = 1. I think this provides a smoother and cleaner piece of code. I'd strongly suggest you make the default Rate be 1.0, and then you can simply coalesce with a new ExchangeRate(), without needing to set the Rate property.

To set a default value for the Rate in a new ExchangeRate object, just put the initializer inside the constructor

class ExchangeRate
{
    public ExchangeRate()
    {
        this.Rate = 1.0;
    }    
    // other stuff
}


How about this?

var query =
    from cheque in ListOfCheques
    let rate =
        cheque.ExchangeRates
            .Where(exc => exc.Type == EnumExchangeRate.ForCheque)
            .Select(exc => exc.Rate)
            .DefaultIfEmpty(1.0M)
            .First()
    select rate * cheque.NetAmount;

var NetTotal = query.Sum();

Your LINQ query example given in your question has "extra" stuff that you didn't explain so I've only included the stuff from your foreach loop.


Do you need this?

           var query = from cheque in ListOfCheques
                        let excRates = cheque.ExchangeRates ?? Enumerable.Empty()
                        let rate = excRates.Where(x => x.Type == Something).Select(x => x.Rate).FirstOrDefault() ?? 1
                        select cheque.NetAmount * rate;

            var netTotal = query.Sum();

If Rate is nulllable, you can either adept that in the let statement by making it nullable (e.g. Select(x => new int?(x.Rate)) or remove ?? 1 and adept it in your select. which will make:

           var query = from cheque in ListOfCheques
                        let excRates = cheque.ExchangeRates ?? Enumerable.Empty()
                        let rate = excRates.Where(x => x.Type == Something).Select(x => x.Rate).FirstOrDefault()
                        select cheque.NetAmount * (rate != 0 ? rate : 1);
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜