问题
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