How to combine two flat lists into one nested object

隐身守侯 提交于 2020-07-03 11:51:31

问题


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.


回答1:


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();



回答2:


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.




回答3:


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/




回答4:


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?



来源:https://stackoverflow.com/questions/62101354/how-to-combine-two-flat-lists-into-one-nested-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!