LINQ - SelectMany from multiple properties in same object - c#

Assume you have the following simple objects:
class Order
{
public Customer[] Customers { get; set; }
}
class Customer
{
public SaleLine[] SaleLines { get; set; }
}
class SaleLine
{
public Tax[] MerchTax { get; set; }
public Tax[] ShipTax { get; set; }
}
class Tax
{
public decimal Rate { get; set; }
public decimal Total { get; set; }
}
With these objects, I want to be able to get a list of all the unique tax rates used on the entire order, including both merchandise and shipping tax rates.
The following LINQ query will get me the list I need, but only for merchandise taxes:
var TaxRates = MyOrder.Customers
.SelectMany(customer => customer.SaleLines)
.SelectMany(saleline => saleline.MerchTax)
.GroupBy(tax => tax.Rate)
.Select(tax => tax.First().Rate
How can I get a list that contains list of unique tax rates that contains both merchandise and shipping rates?

It sounds like you want this:
var TaxRates = MyOrder.Customers
.SelectMany(customer => customer.SaleLines)
.SelectMany(saleline => saleline.MerchTax.Concat(saleline.ShipTax))
.GroupBy(tax => tax.Rate)
.Select(group => group.Key);
Basically the change is the call to Concat which will concatenate two sequences together.

It can also be used like this;
var TaxRates = MyOrder.Customers
.ToList()
.SelectMany(x => new[] { x.SaleLines, x.MerchTax })
.GroupBy(tax => tax.Rate)
.Select(group => group.Key);

Related

Sum up in a list of list properties

I have below structure where I would like to sum up a list of properties
public class Space
{
public AirBalance AirBalance { get; set; } = new();
}
public class AirBalance
{
public bool AllowVariableExhaust { get; set; }
public SpaceVentilationAirflow VentilationAirflow { get; set; } = new();
}
public class SpaceVentilationAirflow
{
public List<A621VentilationAirflow> A621VentilationAirflows { get; set; } = new();
}
public class A621VentilationAirflow
{
public double BreathingZoneVentilation { get; set; }
}
I am trying to sum up all spaces of A621VentilationAirflow's BreathingZoneVentilation, and I have a value of 1115.05 for breathing zone ventilation. When I sum up using the below code, it always gives me the same value even If I have two spaces and a list of A621VentilationAirflow objects that exist.
Spaces?.Sum(a => a?.AirBalance?.VentilationAirflow?.A621VentilationAirflows.Sum(a => a.BreathingZoneVentilation))
Could anyone please let me know where I am doing wrong with the above code? Many thanks in advance
I feel like you need to change your code to:
Spaces?.Sum(a => a?.AirBalance?.VentilationAirflow?.A621VentilationAirflows.Sum(b => b.BreathingZoneVentilation))
Also, using projection (Select / SelectMany) you can get a more readable (imo) query:
var y = spaces
.Select(c => c.AirBalance)
.Select(c => c.VentilationAirflow)
.SelectMany(c => c.A621VentilationAirflows)
.Sum(c => c.BreathingZoneVentilation);
https://dotnetfiddle.net/RBX9aX

Where clause on list property

I have two classes :
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<Product> Product { get; set; }
}
public class Product
{
public string ProductNumber { get; set; }
public string ProductColor { get; set; }
}
I want to create a clause where on property Product (Product.ProductColor == "") I do :
c.Where(x => x.Product.????? == "11").Select(x => x).ToList();
How do this ?
I assume you want to find customers, that have a product with Number 11. If so, you can use function Any:
var result = c
.Where(x => x.Product.Any(p => p.ProductNumber == "11"))
.ToList();
The code filters only those customers, that have at least one product that satisfies condition ProductNumber == "11"
Or if you want to find customers that have specific color then use different expression:
var result = c
.Where(x => x.Product.Any(p => p.ProductColor == "Color"))
.ToList();
Since Product (which really should be named Products) is also a collection, you'd have to drill down into that collection. For example, if you want all Customers from a list of customers where any product color is "11", it might looks like this:
customers.Where(c => c.Product.Any(p => p.ProductColor == "11"))

How can I sum the values of a member in a generic list based on the value of another member?

I've got this generic list:
private List<ItemsForMonthYear> _itemsForMonthYearList;
...which stores instantiations of this class:
public class ItemsForMonthYear
{
public String ItemDescription { get; set; }
public String monthYr { get; set; }
public int TotalPackages { get; set; }
public Decimal TotalPurchases { get; set; }
public Decimal AveragePrice { get; set; }
public Double PercentOfTotal { get; set; }
}
I need to calculate the totals for all the TotalPackages in the list for each month (separately). I've got a start with this:
private int GetGrandTotalPackagesForMonth(String YYYYMM)
{
return _itemsForMonthYearList.Sum(x => x.TotalPackages);
}
This, though, gets the Grand TotalPackages for all months; I just want (one at a time) the Grand Totals for a given month. I was hoping I could use "Where" something like this:
return _itemsForMonthYearList.Sum(x => x.TotalPackages).Where(x => x.monthYr == YYYYMM);
...but that is not available.
How can I restrict a summation to the specified month?
You should apply the filter first and then sum TotalPackages like:
return _itemsForMonthYearList.Where(x => x.monthYr == YYYYMM).Sum(x => x.TotalPackages);
Although not related, but if you want you can get the sum for each month in a single query using GroupBy like:
var sumForeachMonth = _itemsForMonthYearList.GroupBy(x => x.monthYr)
.Select(grp => new
{
Month = grp.Key,
Sum = grp.Sum(r => r.TotalPackages)
});
You have Where clause available for such operations
myList.Where(x => x. monthYr == "Nov-15").Sum(x => x.TotalPackages);

LINQ to Entities, Where Any In

How to write 'Where Any In' in LINQ to Entity?
Here is my model :
class Chair
{
public int Id { get; set; }
public int TableId { get; set; }
public Table Table { get; set; }
}
class Table
{
public int Id { get; set; }
public ICollection<Chair> Chairs { get; set; }
public ICollection<Category> Categories { get; set; }
public Table()
{
Chairs = new List<Chair>();
Categories = new List<Category>();
}
}
class Category
{
public int Id { get; set; }
public ICollection<Table> Tables { get; set; }
}
I also got a simple list of Category :
List<Category> myCategories = new List<Category>(c,d,e);
I want to get only that Chairs that belongs to Table that got one of the Category from myCategories List. Thats what im trying to do :
var result =
ctx.Chairs.Where(x => x.Table.Categories.Any(y => myCategories.Any(z => z.Id == y.Id))).ToList();
I think its ok but what i get is error :
"Unable to create a constant value of type 'ConsoleApplication1.Category'. Only primitive types or enumeration types are supported in this context"
Try to compare with in-memory categories Ids collection, instead of categories collection.
var myCategoriesIds = myCategories.Select(c => c.Id).ToArray();
var result =
context.Chairs
.Where(
x => x.Table.Categories.Any(
y => myCategoriesIds.Contains(y.Id)))
.ToList();
this is because ctx.Chairs is a collection that is in database, you should retrieve that collection first in order to compare it with in-memory data:
var result = ctx
.Chairs
.AsEnumerable() // retrieve data
.Where(x =>
x.Table.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)))
.ToList();
EDIT: that wouldn't be the correct thing to do if you have a lot of entities on database, what you can do is to split it into two queries:
var tables = ctx.Tables
.Where(x =>
x.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)));
var result = ctx.Chairs
.Where(x =>
tables.Any(t=> t.Id == x.TableId))
.ToList();
You can select Ids from myCategories and use it last statement.
var CategoryIds = myCategories.Select(ct => ct.Id);
var result = ctx.Chairs.Where(x => x.Table.Categories.Any(y => CategoryIds.Any(z => z == y.Id))).ToList();

RavenDB index to return most recent entry

I am new to RavenDB and I am trying to query the document model below with the index below. The index is almost working as desired, except now I need to only include the most recent status for a date in the total. For example, a client could have multiple import statuses for a date, but only the last status should be counted in the resulting totals.
public class Client
{
public int Id { get; set; }
public string Name { get; set; }
public IList<ImportStatusMessage> ImportStatuses { get; set; }
}
public class ImportStatusMessage
{
public DateTime TimeStamp { get; set; }
public ImportStatus Status { get; set; }
}
public enum ImportStatus
{
Complete,
Running,
Failed,
Waiting,
NoReport
}
I am using the following index:
public class Client_ImportSummaryByDate : AbstractIndexCreationTask<Client, ImportSummary>
{
public Client_ImportSummaryByDate()
{
Map = clients => from client in clients
from status in client.ImportStatuses
select new
{
status.Status,
Date = status.TimeStamp.Date,
Count = 1
};
Reduce = results => from result in results
group result by new { result.Status, result.Date }
into g
select new
{
g.Key.Status,
g.Key.Date,
Count = g.Sum(x => x.Count)
};
}
}
public class ImportSummary
{
public ImportStatus Status { get; set; }
public DateTime Date { get; set; }
public int Count { get; set; }
}
Can this be accomplished with an index? Do I need a different approach to solve this problem?
Instead of:
from status in client.ImportStatuses
Consider:
let status = client.ImportStatuses.Last()
If they might be out of order in the list, you could do:
let status = client.ImportStatuses.OrderBy(x => x.TimeStamp).Last()
You could also use First instead of Last if they were so ordered that way.
Any of these would index just a single status per client. If instead you mean that you want multiple status, but only the last on any given date, you could do:
Map = clients => clients.SelectMany(x => x.ImportStatuses, (x, y) => new {x.Id, y.Status, y.TimeStamp})
.GroupBy(x => new {x.Id, x.TimeStamp.Date})
.Select(g => g.OrderBy(x => x.TimeStamp).Last())
.Select(x => new
{
x.Status,
x.TimeStamp.Date,
Count = 1
});
All of this would be in the map part of the index, since the list is self-contained in each document.

Categories