List by GroupBy from IEnumerable - c#

I am trying to create a list of Queues that are displayed by Queue Category. Each Queue Category is assigned an Enum value as such.
public enum QueueCategory
{
None=0,
Critical=1,
High=2,
Orphaned=3,
Missing=4
}
And for each Category, I want to then display these fields.
public class QueueInformation
{
public string Name { get; set; }
public Decimal PercentOfThreshold { get; set; }
public string Host { get; set; }
public DateTime OldestArrival { get; set; }
public QueueCategory Category { get; set; }
}
}
How would I go about linking these two pages so that QueueInformation is displayed by QueueCategory?

IEnumerable<QueueInformation> infos = ...;
foreach (var categoryGroup in infos.GroupBy(i => i.Category))
{
Console.WriteLine("Current category: {0}", categoryGroup.Key);
foreach (var queueInfo in categoryGroup)
{
Console.WriteLine(queueInfo.Name /*...*/);
}
Console.WriteLine("==========================");
}

I assume you want a source ordered by the QueueCategory:
IEnumerable<QueueInformation> list = new BindingList<QueueInformation>();
var orderedList = from l in list orderby l.Category select l;
Hope this helps

Related

How to combine two flat lists into one nested object

I have three lists which contains three same properties in each collection. I want to combine a result into one collection. Ex classes structure is as below
public class Order
{
public int ProductId { get; set; }
public int CustomerId { get; set; }
public int OrderId { get; set; }
// Few other Properties of OrderDetail
}
public class PaymentDetail
{
public int ProductId { get; set; }
public int CustomerId { get; set; }
public int OrderId { get; set; }
// Few other Properties form PaymentDetail
}
public class CouponUsageDetail
{
public int ProductId { get; set; }
public int CustomerId { get; set; }
public int OrderId { get; set; }
// Few other Properties form CouponUsageDetail
}
This type of output is coming from one API service where each class is in form of list object (JSON format) and we need to perform some operations on this. All three properties (ProductId,CustomerId, OrderId) contains same values in each collection which means all three properties are repeated in each collection. We need to perform some look ups on these collections. So in normal way what we can do with it is as - start a foreach from Order list and filter all three matching properties of PaymentDetail and CouponUsageDetail. But it would be costlier in term of performance when the data size is increased. So thought of making nesting structure upfront and avoid lookups. If we make the output nested as below this will help to avoid lookups on PaymentDetail and CouponUsageDetail.
Ex - we are receiving JOSN in below format
{"Orders":[{"ProductId":301,"CustomerId":101,"OrderId":201},{"ProductId":701,"CustomerId":501,"OrderId":601}],"PaymentDetails":[{"ProductId":301,"CustomerId":101,"OrderId":201},{"ProductId":701,"CustomerId":501,"OrderId":601}],"CouponUsageDetails":[{"ProductId":301,"CustomerId":101,"OrderId":201},{"ProductId":701,"CustomerId":501,"OrderId":601}]}
and with this output we want to form object as
public class OrderDetails
{
public int ProductId { get; set; }
public int CustomerId { get; set; }
public int OrderId { get; set; }
// Few other Properties of OrderDetail
List<PaymentDetail> PaymentDetail { get; set; }
List<CouponUsageDetail> CouponUsageDetail { get; set; }
}
Can you guide, what would be the best optimum usage of linq where we can combine all these three matching properties and make it just one nested structure better?
Thank You!
Note: I know this structure needs to be normalized but please ignore the normalization rule here as this is not in our control.
What are you describing sounds like two standard multi-key LINQ group joins. They are quite efficient (LINQ to Objects implementation uses prepared fast hash based lookups), so no further optimizations are needed:
var orderDetails = (
from o in data.Orders
join p in data.PaymentDetails
on new { o.ProductId, o.CustomerId, o.OrderId }
equals new { p.ProductId, p.CustomerId, p.OrderId }
into orderPaymentDetails
join c in data.CouponUsageDetails
on new { o.ProductId, o.CustomerId, o.OrderId }
equals new { c.ProductId, c.CustomerId, c.OrderId }
into orderCouponUsageDetails
select new OrderDetails
{
ProductId = o.ProductId,
CustomerId = o.CustomerId,
OrderId = o.OrderId,
// Few other Properties of OrderDetail
PaymentDetail = orderPaymentDetails.ToList(),
CouponUsageDetail = orderCouponUsageDetails.ToList(),
})
.ToList();
There seems to be a number of questions here combined, I'll try to work through them:
Data Models and Deserialization
With respect to generating a single structure from your API response, I would recommend using the Newtonsoft.Json libraries, available on NuGet Json.NET. They will allow you to deserialize the response from your API, into a single object, which given the sample you provided, should contain a collection of each of your models, Order, PaymentDetail, CouponUsageDetail:
public class APIResponceContainer
{
[JsonProperty("Orders")]
public List<Order> Orders { get; set; }
[JsonProperty("PaymentDetails")]
public List<PaymentDetail> PaymentDetails { get; set; }
[JsonProperty("CouponUsageDetails")]
public List<CouponUsageDetail> CouponUsageDetails { get; set; }
public APIResponceContainer()
{
Orders = new List<Order>();
PaymentDetails = new List<PaymentDetail>();
CouponUsageDetails = new List<CouponUsageDetail>();
}
}
Be aware to add the required attributes to each of your models as so:
public class Order
{
[JsonProperty("ProductId")]
public int ProductId { get; set; }
[JsonProperty("CustomerId")]
public int CustomerId { get; set; }
[JsonProperty("OrderId")]
public int OrderId { get; set; }
}
Deserialization then happens from your JSON string, as such:
StringReader stringReader = new StringReader(myJSONString);
JsonSerializer js = JsonSerializer.Create();
APIResponceContainer APIResponce = (APIResponceContainer)js.Deserialize(stringReader, typeof(APIResponceContainer));
Queries
As discussed in the comments, your data is unfortunately in terrible need of normalization. However, what I have inferred is that you would like to produce a flat structure, maintaining the "Few other Properties" and "key properties", for a combination of Order, PaymentDetail and CouponUsageDetail. You can use Linq for this, importantly I would recommend you choose yourself a "Primary Key". In other words, one property that can independently tie all the others together. In the example below, I have choose OrderID since it should be unique (?):
var flatSequence =
from order in APIResponce.Orders
join coupon in APIResponce.CouponUsageDetails on order.OrderId equals coupon.OrderId
join payment in APIResponce.PaymentDetails on order.OrderId equals payment.OrderId
select new
{
// Here extract all the properties you care about
OrderID = order.OrderId,
Customer = order.CustomerId,
Product = order.ProductId,
// All the "other Properties" ?
BankDetail = payment.PaymentOnlyProperty
};
Here I have extracted to var, however if you know the final flat structure you would like, of course determine a class of your own to receive the result.
Please comment if there is any questions.
You can use inheritance.
public class ResultCollection : Collection1
{
List<Collection2> Collection2s { get; set; }
List<Collection3> Collection3s { get; set; }
}
and then
var result = new ResultCollection {
PropId1 = Collection1.PropId1,
PropId2 = Collection1.PropId2,
...
Collection2s = Collection2,
Collection3s = Collection3
}
An automapper can be helpful here.
https://docs.automapper.org/en/stable/
I have solution, but i am not sure if it's ok for you. It depends on data format that you have on the begining.
solution:
class Program
{
static void Main(string[] args)
{
var collection1 = new Collection1() { PropId1 = 1, PropId2 = 2, PropId3 = 3 };
var list2 = new List<Collection2>()
{
new Collection2
{
PropId1 = 11,
PropId2 = 22,
PropId3 = 33
},
new Collection2
{
PropId1 = 22,
PropId2 = 33,
PropId3 = 44
}
};
var list3 = new List<Collection3>()
{
new Collection3
{
PropId1 = 111,
PropId2 = 222,
PropId3 = 333
},
new Collection3
{
PropId1 = 222,
PropId2 = 333,
PropId3 = 444
}
};
var result = new ResultCollection(collection1, list2, list3);
//or
var result2 = new ResultCollection(collection1) //but in this case you have to change your constructor
{
Collection2s = list2,
Collection3s = list3
};
Console.ReadLine();
}
}
public class Collection1
{
public int? PropId1 { get; set; }
public int? PropId2 { get; set; }
public int? PropId3 { get; set; }
}
public class Collection2
{
public int? PropId1 { get; set; }
public int? PropId2 { get; set; }
public int? PropId3 { get; set; }
}
public class Collection3
{
public int? PropId1 { get; set; }
public int? PropId2 { get; set; }
public int? PropId3 { get; set; }
}
public class ResultCollection : Collection1
{
public ResultCollection() { }
public ResultCollection(Collection1 collection, List<Collection2> list2, List<Collection3> list3)
{
foreach (PropertyInfo prop in collection.GetType().GetProperties())
{
PropertyInfo prop2 = collection.GetType().GetProperty(prop.Name);
if (prop2.CanWrite)
prop2.SetValue(this, prop.GetValue(collection, null), null);
}
Collection2s = list2;
Collection3s = list3;
}
public List<Collection2> Collection2s { get; set; }
public List<Collection3> Collection3s { get; set; }
}
But can you give an example of input data?

Group by clause not allowing grouping on class level

I have the following class but I want to use it within a LINQ group expression but I am hitting an error.
namespace importService.Model
{
[DelimitedRecord(",")]
[IgnoreEmptyLines()]
public class Import
{
public int ID { get; set; }
public string ProductType { get; set; }
public Single? Weight { get; set; }
public string ProductName { get; set; }
public string NominalCode { get; set; }
public decimal Costings { get; set; }
public DateTime DateTimeCreated { get; set; }
public string ImportedBy { get; set; }
public string OrderUser { get; set; }
public bool isActive { get; set; }
public int Status { get; set; }
public long OrderNo { get; set; }
}
}
I am trying to use it as following to group them nicely in an email
List<Import> orders = new List<Import>
orders=Filled in from select statment from dapper
orders = orders.GroupBy(g => g.OrderNo).ToList();
However, I am getting the following error
Severity Code Description Project File Line Suppression State
Error CS0029 Cannot implicitly convert type >
System.Collections.Generic.List<System.Linq.IGrouping<long,
ImportService.Model.Import>>' to
'System.Collections.Generic.List<ImportService.Model.Import>'
How do I properly construct the group by query in this case?, I am using dapper to fill my orders element so it's getting filled ok it's just not wanting to allow the group.
Aim
My Aim is so that I can send an email shot based of the order numbers in the class and show a total row at the bottom of the email for each different order number.
It should be one email one email, where each total come after the rows for each set of OrderNo
foreach (var item in orders)
{
using (var tr = table.AddRow(classAttributes: "someattributes"))
{
tr.AddCell(item.OrderNo.ToString(), "style:font-bold;");
tr.AddCell(item.ProductName.ToString(), "style:font-bold;");
if (item.Status == (int)ImportStatus.NominalInvlaid)
{
tr.AddCell("Nominal Code Invlaid");
}
tr.AddCell(item.Costings.ToString(), "style:font-bold;");
tr.AddCell("DB", "style:font-bold;");
}
This should display at the bottom of the lines above which should be group by the order no
using (var tr = table.AddRow(classAttributes: "someattributes"))
{//this should be the total of item.costings a the bottom of the above
tr.AddCell(TotalOrdervalue.ToString(), "style:font-bold;");
}
}
That's because the result of the group by isn't a list of imports, instead it's a collection of lists.
try using the code below and see if it's working
var groupedOrders = orders.GroupBy(g => g.OrderNo).ToList();
an example of for loop would be
foreach (var og in groupedOrders)
{
foreach (var item in og)
{
...your code
}
}

ASP.NET add items to List from Class

I have this class here:
public class CP_VIP_Preview_TimeSlots
{
public int id { get; set; }
[DisplayName("Time Slots")]
public string timeSlot { get; set; }
[DisplayName("Date Slots")]
public string dateSlot { get; set; }
}
public class CPVIPPreviewTimeSlots : DbContext
{
public DbSet<CP_VIP_Preview_TimeSlots> Data { get; set; }
}
Now what I am trying to do is put each item of the class into a List, I have created this list:
List<string> newList = new List<string>();
What I am looking to add to this list is a the id and timeSlot and DateSlot, however I want the timeSlot and DateSlot as a combined string. So my new list will have id and some string called timeDateSlot...I hope this make sense, how would I do this?
If you have a CP_VIP_Preview_TimeSlots called test:
newList.add(test.id.ToString());
newList.add(test.timeSlot + test.dateSlot);
However, you do not get to choose identifiers for these string.
If you really don't want to store a list of your TimeSlots class then what you need is a list of KeyValuePairs.
List<KeyValuePair<int,string>>
and in this you can add:
List<KeyValuePair<int,string>> myList = new List<KeyValuePair<int,string>>();
myList.Add(new KeyValuePair<int, string>(test.id.ToString(), test.timeSlot + test.dateSlot));
List<CP_VIP_Preview_TimeSlots> newList = new List<CP_VIP_Preview_TimeSlots>();
foreach(var x in CPVIPPreviewTimeSlots){ //whatever your db data is
newList.add(x); //add an instance to list
}
Something like this might help. Then you could display like so,
var x = newList.first();
string combined = x.timeSlot + x.dateSlot;
You can add an extra property to your class to get the correct string that is accessible anywhere:
public class CP_VIP_Preview_TimeSlots
{
public int id { get; set; }
[DisplayName("Time Slots")]
public string timeSlot { get; set; }
[DisplayName("Date Slots")]
public string dateSlot { get; set; }
public string combinedString
{
get
{
return timeSlot + " " + dateSlot;
}
}
}
This can then also be used in your other question
DropDownList1.DataSource = newList;
DropDownList1.DataTextField = "combinedString";
DropDownList1.DataValueField = "timeSlot";
DropDownList1.DataBind();
If you want to identify data using Id you can use the following code
foreach(CP_VIP_Preview_TimeSlots d in CPVIPPreviewTimeSlots.Data)
{
List<KeyValuePair<int,string>> lstIdWiseData = new List<KeyValuePair<int,string>>();
lstIdWiseData.Add(new KeyValuePair<int,string>(d.Id, string.Concat(d.timeSlot, d.dateSlot )));
}
If your id is not unique you can replace KeyValuePair<int,string> with Tuple<int,string> in the above code

Initialize some property of view object without foreach statment

I have two Collection from Item & ItemView:
Item
{ Id, Name, DisplayName, Code, Price }
ItemView
{ DisplayName, Price }
In .net is it possible Initialize ItemViews with Items without foreach statement:
foreach (var item in Items)
itemViews.Add(new ItemView
{
DispalyName = item.DisplayName,
Price = item.Price
});
It mean something like cast, so just exist propery(DisplanyName,Price) fill in itemviews;
ItemViews = (ItemViews) Items;
Possible? how? i think i need operator overloading.
Edit:
Actually problem is my classes change during time, and this change has redunduncy in codes
I find Omu.ValueInjecter package, it works for one object but how use it for a collection?
foreach (var item in this._service.GetAll())
viewItemCats.Add((ItemCatInput)new ItemCatInput().InjectFrom(item));
Item and ItemView have common properties so you can define the common properties in a base class and use inheritance:
public class ItemView
{
public string DisplayName { get; set; }
public decimal Price { get; set; }
}
public class Item : ItemView
{
public int Id { get; set; }
public string Name { get; set; }
...etc...
}
With this approach you can treat any Item as an ItemView, here are two examples:
var itemViews = myListOfItems.Cast<ItemView>();
var myItemView = (ItemView) myItem;
The problem you described is due to your design.
It certainly looks like a Model-View design problem. I would approach it this way: Have ItemView "wrap" Item and expose properties used to view the item.
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public int Code { get; set; }
public decimal Price { get; set; }
}
public class ItemView
{
public ItemView(Item item)
{
this.item = item;
}
public string DisplayName
{
get
{
// TODO: make pretty name
return this.Item.Name + " (" + this.item.Code + ")";
}
}
public decimal Price
{
get { return this.item.Price; }
set { this.item.Price = value; } // TODO: add validation
}
private Item item;
}

How to use Linq to select information from a collection into another concrete class I made?

I have the following class:
public class EntityJESummary
{
public int JEGroupingId { get; set; }
public int PartnershipId { get; set; }
public int JEId { get; set; }
public DateTime BookingDate { get; set; }
public DateTime EffectiveDate { get; set; }
public bool Allocated { get; set; }
public int JEEstate { get; set; }
public float Debit { get; set; }
public float Credit { get; set; }
public string JEComments { get; set; }
public EntityJESummary()
{
}
}
And here I'm using Linq to filter out DataRows from a source. I'm trying to fit information from this datasource into this new holder type class.
Any suggestions?
_dttMasterViewTransaction = dtsTransaction.Tables["tblTransaction"];
var datos = _dttMasterViewTransaction.AsEnumerable()
.Where(r => r["JEID"] == FundsID)
.Select(new EntityJESummary ???
Notice where I'm using r["foo"], I'm fetching data from each DataRow. I need to get specific rows and fit them into specific properties of my holder class.
Also, in the data table, there might be many rows for a single JEId, so I'd like to grab each Debit from each datarow and Sum it into the float Debit property.
Any suggestions would be very much appreciated. :)
Untested but try something similar to what you did with the Where clause:
var datos = _dttMasterViewTransaction.AsEnumerable()
.Where(r => r["JEID"] == FundsID)
.Select(r => new EntityJESummary {
JEGroupingId = r["JEGroupingId"],
PartnershipId = r["PartnershipId"],
.....
} );
You can make use of Object Initalizers.
_dttMasterViewTransaction = dtsTransaction.Tables["tblTransaction"];
var datos = _dttMasterViewTransaction.AsEnumerable()
.Where(r => r["JEID"] == FundsID).Select(r =>
new EntityJESummary() {
JEGroupingId = r["JEID"],
PartnershipId = r["PartnershipId"]
};

Categories

Resources