Group By, Count and get Total by items inside list - c#

I have an Account object like:
public class Account
{
public int Id { get; set; }
public List<Elegibilities> Elegibilities { get; set; }
}
public class Elegibilities
{
public int Id { get; set; }
public string Label { get; set; }
public bool Value { get; set;}
}
My repository returns a List of Account. List<Account>
I want to group by and count by items in Elegibilities and the output would be like:
{
Total: 30,
[
{
Id : 1,
Label: "Elegibility1",
Count: 10
},
{
Id : 2,
Label: "Elegibility2",
Count: 20
}
]
}
I'm having issues because it's a list inside another list.
Is it possible to solve in a single LINQ query?

Look's like you don't want any data related to the Accounts,
This gives you a simple flat list to work with.
var flatList = accounts.SelectMany(a => a.Elegibilities);
This could be another example if you needed to include accounts related data to the output.
var group = from a in accounts
from el in a.Elegibilities
group el by el.Id into elGroup
select new
{
Id = elGroup.Key,
Count = elGroup.Count(),
Label = elGroup.First().Label
};
In the end you can access your group totals.
var result = new { Totals = group.Count() , Elegibilities = group.ToList() };

Related

C# linq / Lambda - Adding an item to a list that summarizes all the items in the list

I have al list of Purches items
I want to add new item to my list, that sum all the items in my list
this is my code:
public class Purches
{
public int Id { get; set; }
public int Items { get; set; }
public int TotalPrice { get; set; }
}
List<Purches> purchesList = new List<Purches>() {
new Purches() {
Id = 1,
Items = 3,
TotalPrice = 220
},
new Purches() {
Id = 2,
Items = 5,
TotalPrice = 300
}
};
now, I want to add the list new item that sum the Items and the TotalPrice properties
the result will be something like that:
List<Purches> purchesList = new List<Purches>() {
new Purches() {
Id = 1,
Items = 3,
TotalPrice = 220
},
new Purches()
{
Id = 2,
Items = 5,
TotalPrice = 300
},
new Purches()
{
Id = 0,
Items = 8,
TotalPrice = 550
}
};
I have to do it via linq / Lambda in c#
I would not recommend adding a summary item of the same type. That is just likely to lead to confusion. A better solution would be to to use either a separate object for the total, or use different types with a shared interface, for example:
public class PurchaceSummary{
public List<Purches> Purchases {get;}
public TotalItemCount => Items.Sum(p => p.Items);
public TotalPrice => Items.Sum(p => p.TotalPrices);
}
Or
public interface IPurchaseLineItem{
public int Items { get; }
public int TotalPrice { get; }
}
public interface Purchase : IPurchaseLineItem{
public int Id { get; set; }
public int Items { get; set; }
public int TotalPrice { get; set; }
}
public interface PurchaseSummary : IPurchaseLineItem{
public int Items { get; set; }
public int TotalPrice { get; set; }
}
// Use the LINQ methods from the previous example to create your totals for the summary
In either case it should be immediately obvious for everyone what each value represents.
Purches totalSum = new Purches
{
Id = 0,
Items = purchesList.Sum(p => p.Items),
TotalPrices = purchesList.Sum(p => p.TotalPrices)
};
// now add it to your list if desired

LiteDB Insert list with BsonRef

Hi and thanks in advance everyone!
I have a collection of the following objects:
public class ItemsModel
{
public List<int> IdCollection { get; set; }
public string Name { get; set; }
public int Weight { get; set; }
}
List<ItemsModel> col = ...;
I want to optimally store this with LiteDb and be able to modify the records.
Each ItemsModel has a unique Name+Weight set.
In the entire col, the elements of the IdCollection are also unique.
Body example:
List<ItemsModel>:
[{
IdCollection: [1,3,5,6,...],
Name: "first name",
Weight: 10
},
{
IdCollection: [2,4,...],
Name: "second name",
Weight: 5
}]
I want to index by Id
I want to expand into two tables for easy storage in LiteDb:
[{
_id: 1,
NameAndWeight: {&ref: "names"}
},
{
_id: 2,
NameAndWeight: {&ref: "names"}
},
{
_id: 3,
NameAndWeight: {&ref: "names"}
},
...
]
[{
Name: "first name",
Weight: 10
},
{
Name: "second name",
Weight: 5
}]
For this I have to make new storage classes:
public class ItemsModel
{
[BsonId]
public int Id { get; set; }
[BsonRef("names")]
public NamesModel NameAndWeight { get; set; }
}
public class NamesModel
{
[BsonId(true)]
public ObjectId Id { get; set; }
public string Name { get; set; }
public int Weight { get; set; }
}
But next step I'm having trouble...
Tell me, can I somehow save data using Insert array and Include in one operation?
Or should I use foreach to first write the NamesModel in "names" DB, get the generated _id, then write the ItemsModel with a link to the NamesModel already written to the database?
using (var db = new LiteDatabase(_strConnection))
{
var itemsDb = db.GetCollection<ItemsModel>("items");
var namesDb = db.GetCollection<NamesModel>("names");
itemsDb.EnsureIndex(x => x.Id, true);
foreach (var group in col)
{
var name = new NamesModel(group.Name, group.Weight);
namesDb.Insert(name);
var itemDb = group.IdCollection.Select(el => new ItemsModel(el, name));
var h = itemsDb.Insert(itemDb);
}
}
it is too long(
Now I did like this:
using (var db = new LiteDatabase(_strConnection))
{
var itemsDb = db.GetCollection<ItemsModel>("items");
var namesDb = db.GetCollection<NamesModel>("names");
itemsDb.EnsureIndex(x => x.Id, true);
namesDb.EnsureIndex(x => x.Name);
var temp = col.Select(el => (el.IdCollection, new NamesModel(el.Name, el.Weight))).ToList();
namesDb.Insert(temp.Select(el => el.Item2));
var temp2 = temp.SelectMany(gr => gr.IdCollection.Select(el => new ItemsModel(el, gr.Item2)));
eventsIdDB.Insert(temp2);
}
Performed basic operations in linq to reduce the number of hits in liteDb

Grouping Deeply Nested Objects Using LINQ

I have the following classes:
public class Item
{
public int Id { get; set; }
public string Sku { get; set; }
public List<Price> Prices { get; set; }
}
public class Price
{
public int Id { get; set; }
public double Cost { get; set; }
public PriceList List { get; set; }
}
public class PriceList
{
public int Id { get; set; }
public string FileName { get; set; }
public DateTime ImportDateTime { get; set; }
}
Essentially, it's for a list of items [Item] in which each item has a list of prices [List<Price> Prices] (one-to-many), and each price has one price list [PriceList List] (one-to-one).
What I need is a list of all price lists.
It seems like what I need is a group to a "grandchild", basically, grouping by the PriceList's Id (which is under Price, which in turn is under Item in the list).
So, as an example, if I have five price lists, it should return five rows.
I achieved it doing the following long way:
List<PriceList> priceLists = new List<PriceList>();
foreach (Item item in items)
{
foreach (Price price in item.Prices)
{
PriceList list = price.List;
if (!priceLists.Any(x => x.Id == list.Id))
{
priceLists.Add(list);
}
}
}
How can it be achieved using LINQ?
UPDATE:
Here's a basic sample set:
PriceList priceList1 = new PriceList { Id = 1, FileName = "Price List 1.csv" };
PriceList priceList2 = new PriceList { Id = 2, FileName = "Price List 2.csv" };
Price price1 = new Price { Id = 1, Cost = 2.65, List = priceList1 };
Price price2 = new Price { Id = 2, Cost = 14.23, List = priceList2 };
Price price3 = new Price { Id = 3, Cost = 29.01, List = priceList1 };
Price price4 = new Price { Id = 4, Cost = 1, List = priceList2 };
Price price5 = new Price { Id = 5, Cost = 56.12, List = priceList1 };
Item item1 = new Item { Id = 1, Sku = "item1", Prices = new List<Price> { price1, price2 } };
Item item2 = new Item { Id = 2, Sku = "item2", Prices = new List<Price> { price3, price4 } };
Item item3 = new Item { Id = 3, Sku = "item3", Prices = new List<Price> { price5 } };
List<Item> itemsList = new List<Item> { item1, item2, item3 };
Let's start from the bottom upwards:
I have a list of Item (itemsList), which contains three Items.
Each Item has a list of Price.
Each Price has one PriceList.
To clarify the reasoning behind all this: The user imports a spreadsheet of prices (price list) they get periodically from their supplier, prices fluctuate and each price list has different prices for the same items. Hence the reason of having an item with many prices and each price has its price list which was used to upload it (there's more information included in the PriceList class which I omitted to keep it simple).
I hope I'm clear.
This seems to be what you want, using an extension to do Distinct by a lambda expression:
var ans = itemsList.SelectMany(item => item.Prices.Select(price => price.List)).DistinctBy(price => price.Id);
The extension is as follows:
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> src, Func<T, TKey> keyFun) {
var seenKeys = new HashSet<TKey>();
foreach (T e in src)
if (seenKeys.Add(keyFun(e)))
yield return e;
}
If you changed your approach to use a HashSet<Int> to track the seen ids you would probably be a tiny bit more efficient than LINQ.
It looks like you can combine a SelectMany (to get all the Prices lists) with a Select (to get all the associated PriceList properties):
List<PriceList> priceLists = items.SelectMany(i => i.Prices).Select(p => p.List).ToList();
Edit
The above will return all the PriceLists, but since many Items could reference the same PriceList, then you need a way to filter on the PriceList.Id field.
One way to do that would be to override the Equals and GetHashCode property of the PriceList object. If you truly consider two PriceList objects to be equal if they have the same Id, then you could do the following:
public class PriceList
{
public int Id { get; set; }
public string FileName { get; set; }
public DateTime ImportDateTime { get; set; }
public override bool Equals(object obj)
{
var other = obj as PriceList;
return Id == other?.Id;
}
public override int GetHashCode()
{
return Id;
}
}
This allows you to use the Linq method, Distinct on your prices (it will call the Equals method to distinguish between PriceLists in the results:
List<PriceList> priceLists = items
.SelectMany(i => i.Prices)
.Select(i => i.List)
.Distinct()
.ToList();

Linq, how to group data by ID and merge data within that ID

From the database I get the following values
PlanningID = GetValue<int>(dataReader["PlanningID"]),
PlanningStatus = GetValue<string>(dataReader["PlanningStatus"]),
Private = GetValue<int>(dataReader["Private"])
Social = GetValue<int>(dataReader["Social"])
PlanningID, PlanningStatus, Private
1, good, 10
1, fair, 5
1, bad, 1
I want to group these by planningID so that it looks like this
public class ClassResult
{
public int planningID { get; set; }
public List<PlanningStatus> PlanningStatus { get; set; }
}
public class PlanningStatus
{
public string PlanningStatus { get; set; }
public int Private { get; set; }
public int Social{ get; set; }
}
I tried this but the output was wrong:
IEnumerable<ClassResult> classResult =
from result in results
group result by new
{
result.PlanningID,
result.PlanningStatus
} into grouping
select new ClassResult
{
PlanningID = grouping.Key.PlanningID,
PlanningStatus = grouping.Key.PlanningStatus,
Private = grouping.First().Private,
Social = grouping.First().Social
};
return lrrResults;
To be honest I got no idea how to do this
Updated projection as advised below but still have multiple planning id's of lets say 1
var results =
from result in results
group result by result.PlanningID
into grouping
select new ClassResult
{
PlanningID = grouping.Key,
PlanningStatusType = grouping.Select(item => new PlanningStatusType
{
PlanningStatus = item.PlanningStatus,
Private = item.Private,
Social = item.Social,
}).ToList(),
LatestChange = grouping.First().LatestChange,
WeeklyChangeType = grouping.First().WeeklyChangeType,
Address = grouping.First().Address
};
In your query above you are grouping by both the PlanningID and PlanningStatus so the groups will contain 1 item each. What you want to do is as following:
Instantiate a new ClassResult as you did but setting the planningID but the .Key (which is now just a single property
Project each item of the grouping to a new object of type PlanningStatus using the .Select()
Code:
var classResult = from result in results
group result by result.PlanningID into grouping
select new ClassResult
{
PlanningID = grouping.Key,
PlanningStatus = grouping.Select(item => new PlanningStatus {
PlanningStatus = item.PlanningStatus,
Private = item.Private,
Social = item.Social
}).ToList()
};
Tested on some sample code and works:

RavenDB count index including zero values

I have a list of items {Id, Name, CategoryId} and a list of categories {Id, Name, IsActive}.
How to get a list {CategoryId, Count} including categories that have zero items.
Currently I have such index:
public class CategoryCountIndex : AbstractIndexCreationTask<Item, CategoryCountIndex.Result>
{
public class Result
{
public string CategoryId { get; set; }
public int Count { get; set; }
}
public CategoryCountIndex()
{
Map = items => from item in items
select new Result
{
CategoryId = item.CategoryId,
Count = 1
};
Reduce = results => from result in results
group result by result.CategoryId
into c
select new Result
{
CategoryId = c.Key,
Count = c.Sum(x => x.Count)
};
}
}
What is the best way to improve/change my solution in order to have categories with no items?
I removed my earlier answer as it proved to be incorrect. In this case you can actually use a MultiMap/Reduce index to solve your problem.
Try using the following index:
public class Category_Items : AbstractMultiMapIndexCreationTask<Category_Items.ReduceResult>
{
public class ReduceResult
{
public string CategoryId { get; set; }
public int Count { get; set; }
}
public Category_Items()
{
AddMap<Item>(items =>
from item in items
select new
{
CategoryId = item.CategoryId,
Count = 1
});
AddMap<Category>(categories =>
from category in categories
select new
{
CategoryId = category.Id,
Count = 0
});
Reduce = results =>
from result in results
group result by result.CategoryId into g
select new ReduceResult
{
CategoryId = g.Key,
Count = g.Sum(x => x.Count)
};
}
}
This will result in the following (three categories, but one without items):
Now you can use a Result Transformer if you want to display the Category Name.
Hope this helps!

Categories

Resources