我有一个(简化的)付款数据库架构:
ID /姓名/付款/ ParentPaymentId
当付款失败并且我“重试”时,我会创建新的付款并将其父级设置为先前“失败”的付款。这意味着我可以看到每笔付款的历史记录和之前的尝试。如果后续付款失败,则会创建另一笔付款,并再次将ParentPaymentId设置为上次尝试付款(依此类推)
当试图显示付款清单(一些成功/一些需要n次重试)时,我想查看最“当前”的付款,子付款需要在一个次要的heirachy中。
e.g。 数据库
ID / Name / Payment / ParentPaymentId
1 Jan 100 NULL
2 Feb 100 NULL
3 Mar 100 NULL
4 Mar(r) 100 3
5 Mar(r) 100 4
6 Mar(r) 100 5
7 Mar(r) 100 6
8 Apr 100 NULL
从上面你可以看到,ID 7是最近尝试向March付款计费的最近尝试
我如何获得此列表<>数组(已经在EF c#中)就像这样的对象图。
[
{
"Id":"1",
"Name":"Jan",
"Payment":"100"
},
{
"Id":"2",
"Name":"Feb",
"Payment":"100"
},
{
"Id":"7",
"Name":"Mar(r)",
"Payment":"100",
"Payments":[
{
"Id":"6",
"Name":"Mar(r)",
"Payment":"100"
},
{
"Id":"5",
"Name":"Mar(r)",
"Payment":"100"
},
{
"Id":"4",
"Name":"Mar(r)",
"Payment":"100"
},
{
"Id":"3",
"Name":"Mar",
"Payment":"100"
}
]
},
{
"Id":"8",
"Name":"Apr",
"Payment":"100"
}
]
答案 0 :(得分:0)
使用您的示例,您有这些付款(最后一个数字是父付款。-1表示没有父母):
1,Jan,100,-1
2,Feb,100,-1
3,Mar(r),100,-1
4,Mar(r),100,3
5,Mar(r),100,4
6,Mar(r),100,5
7,Mar(r),100,6
8,Apr,100,-1
首先创建一个由付款ID键入的字典,其中包含父ID和最初设置为-1的子ID。你最终得到:
1, {-1, -1}
2, {-1, -1}
3, {-1, -1}
4, {3, -1}
5, {4, -1}
6, {5, -1}
7, {6, -1}
8, {-1, -1}
现在,迭代字典的键,并为每个具有父ID的条目,查找该父节点并将其子ID链接到当前条目。你最终得到:
1, {-1, -1}
2, {-1, -1}
3, {-1, 4}
4, {3, 5}
5, {4, 6}
6, {5, 7}
7, {6, -1}
8, {-1, -1}
最后,再次浏览一下这本词典。子项为-1的任何条目都是已完成的付款,您可以向后关注父链接以获取历史记录。
答案 1 :(得分:0)
当问题是"我如何进行一些奇怪的旋转来重建过去某个时候已知的简单关系?",答案是"修复设计所以这种关系以简单明了的方式表达出来。我认为这是一个X / Y问题。
付款表:
ID / Name / Payment / PaymentGroupID
PaymentGroup表:
ID / InitialPaymentID
付款失败时,请创建PaymentGroup记录。 ID是自动编号的。将新记录中的InitialPaymentID设置为失败的付款ID,并将该付款的PaymentGroupID设置为新的PaymentGroup.ID。
这是一个替代解决方案,在一个表中:
ID / Name / Payment / InitialPaymentID / ParentPaymentID
ParentPaymentID就是以前的样子。 InitialPaymentID是失败的付款的ID。这给你一些明智的选择。我选择在项目3上将InitialPaymentID设置为3,即失败的那个,因为乍一看我觉得这很有用。但我可能会在开发过程中改变这个决定。
ID / Name / Payment InitialPaymentID / ParentPaymentId
1 Jan 100 NULL NULL
2 Feb 100 NULL NULL
3 Mar 100 3 NULL
4 Mar(r) 100 3 3
5 Mar(r) 100 3 4
6 Mar(r) 100 3 5
7 Mar(r) 100 3 6
8 Apr 100 NULL NULL
我正在使用您的"简化架构"方法:当然,在真实数据库中,这些表将包含许多其他列。但这是它的本质。
答案 2 :(得分:0)
我定义了一些类来表示数据,因为你没有:
public class PaymentClass {
public int ID;
public string Name;
public double Payment;
public int? ParentPaymentId;
}
public class PaymentWithChildren {
public int ID;
public string Name;
public double Payment;
public int? ParentPaymentId;
public PaymentClass[] Payments;
}
如果您设置映射以按ID检索付款,则可以创建帮助程序以返回父项:
public static class PaymentHelpers {
public static Dictionary<int, PaymentClass> PaymentMap;
public static void SetupPaymentMap(PaymentClass[] db) {
PaymentMap = db.ToDictionary(p => p.ID);
}
public static PaymentClass PaymentForID(int anID) => PaymentMap.TryGetValue(anID, out var p) ? p : null;
public static IEnumerable<PaymentClass> Parents(PaymentClass[] db, int id) {
var p = PaymentForID(id);
while (p.ParentPaymentId.HasValue) {
p = PaymentForID(p.ParentPaymentId.Value);
yield return p;
}
}
}
现在您可以使用一些LINQ调用setup / helper方法:
PaymentHelpers.SetupPaymentMap(db);
var parents = db.Where(p => !db.Any(p2 => p2.ParentPaymentId == p.ID));
var parentsWithChildren = parents.Select(p => new PaymentWithChildren {
ID = p.ID,
Name = p.Name,
Payment = p.Payment,
Payments = PaymentHelpers.Parents(db, p.ID).ToArray()
}).ToList();