How to concatenate two IEnumerable's and project them into a grouping - c#

I have two IEnumerable<StatusGroup> where StatusGroup like
public class StatusGroup
{
public DateTime ExamDate{ get; set; }
public int SuccessCount{ get; set; }
public int FailCount{ get; set; }
}
I want to merge the two IEnumerable, so the result will have just one record for each date and the sum of counters properties should be calculated
for example
List1={Date= 01/01/2016 , Success=10, Fail=0}
List2={Date= 01/01/2016 , Success=0, Fail=3}
the result should be
List2={Date= 01/01/2016 , Success=10, Fail=3}

You can do Concat/Union, and then group by Date. Code will look approximately like this:
var list3 = List1.Concat(List2)
.GroupBy(x => x.Date)
.Select(grouping =>
new StatusGroup
{
Date = grouping.Key,
SuccessCount = grouping.Sum(x => x.SuccessCount),
FailCount = grouping.Sum(x => x.FailCount)
});

Related

Displaying equivalent to SQL pivot table using Linq against Entity Framework

I've been struggling for a few days to display something like Pivoting a dynamic table on SQL, using Linq. I don't actually want to create a pivoted table, I just want to display the results as if it was a pivoted table.
I have two entities:
Category:
public int Id { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public ICollection<StaticEvent> StaticEvents { get; set; }
StaticEvent:
public int Id { get; set; }
public int CategoryId {get; set; }
public Category Category { get; set; }
public DateTimeOffset Time {get; set; }
[MaxLength(500)]
public string Comment {get; set;}
I'm trying to generate a table showing the COUNT of STATIC EVENTS per CATEGORY PER YEARMONTH. So the rows would be the YEARMONTH, the columns would be the Categories and the values would be the COUNT.
I got to the point where I can count the STATIC EVENTS per YEARMONTH:
var query = _context.Categories
.SelectMany(c => c.StaticEvents )
.GroupBy(c =>
new {
Year = c.Time.Year,
Month = c.Time.Month
})
.Select( x=> new {YearMonth = x.Key, Count = x.Count()})
.ToList();
foreach (var x in query)
{Console.WriteLine($"Month = {x.YearMonth.Year},{x.YearMonth.Month}, , Count = " + x.Count);}
but I'm lost about what to do from here.
If Categories is unknown values which are stored in database, it is not possible to do that via LINQ without dynamic query creation.
There is query which do the job when you known categories on the compile time. Since you have not specified EF version, I have emulated Count with Sum:
var query = _context.Categories
.SelectMany(c => c.StaticEvents)
.GroupBy(c =>
new {
Year = c.Time.Year,
Month = c.Time.Month
})
.Select(x => new {
YearMonth = x.Key,
Cat1 = x.Sum(z => z.CategoryId == 1 ? 1 : 0),
Cat2 = x.Sum(z => z.CategoryId == 2 ? 1 : 0),
Cat3 = x.Sum(z => z.CategoryId == 3 ? 1 : 0),
})
.ToList();

How to calculate sum for specific property without grouping main list data

I'm fetching Invoices from database and I want to return all invoices without grouping them!
I don't want to group them since If there are 100 invoices I want to return all of them 100, considering that I want to get Sum of Amount.
So it is perfectly fine to repeat same Total for multiple invoices if their sum is the same, so basically I want to calculate sum of Amount of each invoice item and group by CompanyId, PackageId, BankId, PayMethod only if it's possible?
-- Read code comments --
var result = await _dbContext.Invoices
.Where(p => p.CheckDate >= startDate && p.CheckDate <= endDate)
.Select(p => new DemoDto()
{
CompanyId = p.CompanyId,
Title = p.Title,
Price = p.Price
Total = p.Sum(p => p.Amount).ToString(), // Can I sum here and group by fields I mentioned above? without grouping all data set because I want to keep all 100 records if I received all 100 from database
})
.ToListAsync();
This query obliviously doesn't work because it says
Invoice does not contain definition for Sum and no accessible method..
DemoDto looks like this:
public class DemoDto
{
public string CompanyId {get;set;}
public string Title {get;set;}
public decimal Price {get;set;}
public decimal Amount {get;set;}
}
Invoice class looks like this:
public class Invoice
{
public string CompanyId { get; set; }
public int PackageId {get; set;}
public int BankId {get;set;}
public int PayMethod {get;set;}
public string Title { get; set; }
public decimal Price { get; set; }
public decimal Amount { get; set; }
}
what I'm missing here?
How can I achieve this?
Thanks guys
Cheers
Fetch all the invoices from the database:
var invoices = await _dbContext.Invoices
.Where(p => p.CheckDate >= startDate && p.CheckDate <= endDate)
.ToListAsync();
Group the in-memory results using Linq-To-Object:
var result = invoices?
.GroupBy(p => new { p.CompanyId, p.PackageId, p.BankId, p.PayMethod })
.SelectMany(x => x.Select(y =>
new DemoDto
{
CompanyId = y.CompanyId,
Title = y.Title,
Price = y.Price,
Total = x.Sum(z => z.Price)
}))
.ToList();
If you want to perform the grouping in the database for some reason, you should execute a raw SQL query or a stored procedure rather than relying on the ORM to generate some magic (and most probably inefficient) query for you.

how to get unique values in linq query

i want to get latest 10 unique records whose last 12 characters should be unique.
sample data
json data
[{"timestamp":"2017-03-20T05:27:01.688Z","dataFrame":"ACnrAAAAAAAAAAA=","fcnt":165,"port":3,"rssi":-85,"snr":7,"sf_used":12,"id":1489987621688,"decrypted":true},{"timestamp":"2017-03-20T05:27:41.675Z","dataFrame":"ACntAAAAAAAAAAA=","fcnt":169,"port":3,"rssi":-85,"snr":9,"sf_used":12,"id":1489987661675,"decrypted":true},..
AGMDAQo1/wSsCPU=
AGMEAQo1/wSsCPU=
AGMFAQo1/wSsCPU=
AGMGAQo1/wSsCPU=
AGMHAQo1/wSsCPU=
ASHAAQo2FgSsBxc=
getting output like this , but it should only be one because last 12 characters are same.
AGMDAQo1/wSsCPU=,
AGMEAQo1/wSsCPU=,
AGMFAQo1/wSsCPU=
desired output
AGMDAQo1/wSsCPU=
ASHAAQo2FgSsBxc=
code
var Pirs = Newtonsoft.Json.JsonConvert.DeserializeObject<List<AssetDetail>>(responseString);
var items = Pirs.Where(a => !a.dataFrame.EndsWith("AAAAAAAAAAA="))
.GroupBy(a => a.dataFrame)
.Select(g => g.First())
.OrderByDescending(a => a.timestamp)
.Take(10);
model
public class AssetDetail
{
public long id { get; set; }
public DateTime timestamp { get; set; }
public string dataFrame { get; set; }
public long fcnt { get; set; }
public int port { get; set; }
public int rssi { get; set; }
public string snr { get; set; }
public string sf_used { get; set; }
public bool decrypted { get; set; }
}
You could use a.dataFrame.Substring(a.dataFrame.Length - 12) inside the GroupBy function to group by the AssetDetails that have the same 12 characters at the end on their dataFrame property.
var Pirs = Newtonsoft.Json.JsonConvert.DeserializeObject<List<AssetDetail>>(responseString);
var items = Pirs.Where(a => !a.dataFrame.EndsWith("AAAAAAAAAAA="))
.GroupBy(a => a.dataFrame.Substring(a.dataFrame.Length - 12))
.Select(g => g.First())
.OrderByDescending(a => a.timestamp)
.Take(10);
There is no need to use the Distinct() function if you are using GroupBy()
Use Distinct():
Newtonsoft.Json.JsonConvert.DeserializeObject<List<AssetDetail>>(responseString);
var items = Pirs.Where(a => !a.dataFrame.EndsWith("AAAAAAAAAAA="))
.GroupBy(a => a.dataFrame)
.Select(g => g.First())
.Distinct()
.OrderByDescending(a => a.timestamp)
.Take(10);
Hope it helps!

Group by and Select with Models

I have a list of an object defined by a model, and I want to group by a specific field. When grouped, some fields will need to be summed together. I want to group by ItemName, and the Amount will be all summed together.
My model is:
public class Item{
public string ItemName { get; set; }
public string ItemColor { get; set; }
public string SomethingElse { get; set; }
public decimal Amount { get; set; }
}
My current attempt is:
List<Item> fullListOfItems = GetItemsFromDatabase();
List<Item> groupedList = fullListOfItems
.GroupBy( l => l.ItemName )
.Select(i =>
new Item()
{
ItemName = i.Key.ItemName,
ItemColor = i.Key.ItemColor,
SomethingElse = i.Key.SomethingElse,
Amount = i.Sum(k=>k.Amount)
}
);
I'm getting an error after each key statement, saying that the grouping model does not contain a defintion for ItemName.
Any help would be appreciated. Thanks.
your code should look like this
List<Item> groupedList = fullListOfItems
.GroupBy(l => l.ItemName)
.Select(i =>
new Item()
{
ItemName = i.Key,
ItemColor = i.First().ItemColor,
SomethingElse = i.First().SomethingElse,
Amount = i.Sum(k => k.Amount)
}
).ToList();
instead of i.Key.ItemColor you have a List of Items and you have to pick one value (e.g. First()) - Key is the value of ItemName of each group

Aggregates in Nest (ElasticSearch) on child/nested objects

I have a catalog of products that I want to calculate aggregates on. This is simple enough for top level properties such as brand name, manufacturer, etc. The trouble comes with trying to calculate range counts on prices because we sell in multiple currencies, and when determining these counts I only want to query on one currency at a time. Here is a sample of my product object mapping:
public class Product
{
public int ID { get; set;}
public string Name { get; set; }
public IList<Price> Prices { get; set; }
}
public class Price
{
public int CurrencyID { get; set; }
public decimal Cost { get; set; }
}
Here is an example of a query for all products with a price below 100:
var cheapProducts = client.Search<Product>(s => s
.From(0)
.Size(1000)
.Query(q => q
.Range(r => r
.LowerOrEquals(100)
.OnField(f => f.Prices.FirstOrDefault().Cost))));
The ElasticSearch request that this generates is:
{
"from": 0,
"size": 1000,
"query": {
"range" : {
"prices.cost": {
"lte": "100"
}
}
}
}
This returns all products with at least one price below 100 in any currency, as you would expect. What I've been unable to do is to run this query against just prices in a given currency. For example, adding this filter to the query only removes products that don't have a price in currency 1:
var cheapProducts = client.Search<Product>(s => s
.From(0)
.Size(1000)
.Filter(f => f
.Term(t => t
.Prices.FirstOrDefault().CurrencyID, 1))
.Query(q => q
.Range(r => r
.LowerOrEquals(100)
.OnField(f => f.Prices.FirstOrDefault().Cost))));
I've tried treating the Prices list as both a nested object and a child object, but ElasticSearch doesn't appear to be indexing the prices in that way because I get an error of "AggregationExecutionException[[nested] nested path [prices] is not nested]" and similar for HasChild queries. Is it possible to generate queries and aggregates in this way?
First you need to map the nested type:
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
[ElasticProperty(Type = FieldType.Nested)]
public IList<Price> Prices { get; set; }
}
After that, try execute this query:
var cheapProducts = client.Search<Product>(s => s
.From(0)
.Size(1000)
.Query(x => x.Term(p => p
.Prices.First().CurrencyID, 1) && x.Range(r => r
.LowerOrEquals(100)
.OnField(f => f.Prices.FirstOrDefault().Cost))));

Categories

Resources