Reputation: 88
I need to create an XElement that appears in my document a dynamic number of times based on the combination of Placement_ID and Fill_ID. If the record in my object list has the same Placement_ID as another record, I want to append another fill XElement with a different Fll_ID underneath the prior one. Example below:
<envelope ack="entity" transaction="multi">
<auth>
<user>TM_DEV</user>
<session>session1</session>
</auth>
<Trading op="createOrder, pretradeCpl, sendToTrading,createPlacement, createFill" id="os1">
<order op="create" id="test1234">
<scenario id="sample" />
<execBroker>BEAR</execBroker>
<ticker>MSFT</ticker>
<trader>TM_DEV</trader>
<instruction>LIM</instruction>
<limitPrice>85</limitPrice>
<transType>BUYL</transType>
<orderDuration>GTC</orderDuration>
<tradeDate>
</tradeDate>
<settleDate>
</settleDate>
<allocation id="" op="create">
<acctCd>tstacc00006</acctCd>
<targetQty>300</targetQty>
<specialInst />
</allocation>
<placement id="Place1" op="create">
<execBroker>BEAR</execBroker>
<placeQty>300</placeQty>
<fill id="fill1" op="create">
<fillQty>150</fillQty>
<fillPrice>43</fillPrice>
</fill>
<fill id="fill2" op="create">
<fillQty>150</fillQty>
<fillPrice>44</fillPrice>
</fill>
</placement>
</order>
</Trading>
The code below that I have written so far queries a list object and produces a new order for each row. I need to be able to group my list by Placement_ID and if their are two rows with the same Placement_ID then I want it to write multiple Fill elements. Otherwise, if there are no duplicate Placement_IDs, then there would be 1 order, 1 placement and 1 fill for each row of data in the list.
XDocument doc = new XDocument(
new XDeclaration("1.0", "UTF-8", "yes"),
new XElement("envelope",
new XAttribute("ack", "entity"),
new XAttribute("transaction", "multi"),
new XElement("auth",
new XElement("user", "TM_DEV"),
new XElement("session", "session1")),
new XElement("trading",
new XAttribute("op", "createOrder, pretradeCpl, sendToTrading, createPlacement, createFill"),
new XAttribute("id", "os1"),
from t in trades.ToList()
select new XElement("order",
new XAttribute("op", "create"),
new XAttribute("id", DateTime.Now.ToString("yyMMddHHmmssfff") + t.OrderId),
new XElement("Scenario",
new XAttribute("id", "sample")),
new XElement("execBroker", t.ExecBrkID),
new XElement("ticker", t.Ticker),
new XElement("sedol", t.Sedol),
new XElement("Trader", "TM_DEV"),
new XElement("transType", t.TransType),
new XElement("orderDuration", "GTC"),
new XElement("tradeDate", ""),
new XElement("settleDate", ""),
new XElement("allocation",
new XAttribute("id", ""),
new XAttribute("op", "create"),
new XElement("acctCd", t.Account),
new XElement("targetQty", t.TargetQty),
new XElement("specialInst", "who to settle with")),
new XElement("placement",
new XAttribute("op", "create"),
new XAttribute("id", t.PlacementId),
new XElement("execBroker", t.ExecBrkID),
new XElement("placeQty", t.ExecQty),
new XElement("fill",
new XAttribute("id", t.FillId),
new XAttribute("op", "create"),
new XElement("fillQty", t.ExecQty),
new XElement("fillPrice", t.ExecPrice)))))));
I've broken down the example in my update using your suggestions. Now I get an error of "Incorrectly formatted document".
using (XmlWriter xw = XmlWriter.Create(s, xws)){
XElement order = new XElement("order");
var groupedOrders = trades.GroupBy(o => o.OrderId);
foreach (var groupie in groupedOrders)
{
int n = 1;
XElement root = new XElement("placement",
from g in groupie
select new XElement("t",
new XAttribute("op", "create"),
new XAttribute("id", g.Ticker)));
foreach (var fill in groupie)
{
XElement newFillElement = new XElement("fill" + n,
new XAttribute("id", fill.PlacementId),
new XAttribute("op", "create"),
new XElement("fillQty", fill.ExecQty),
new XElement("fillPrice", fill.ExecPrice));
root.Add(newFillElement);
n++;
}
order.Add(root);
}
XDocument doc = new XDocument(
new XDeclaration("1.0", "UTF-8", "yes"), order);
doc.Save(xw);
}
Upvotes: 1
Views: 1230
Reputation: 13173
Could you create the relationship OUTSIDE of xml building and THEN rebuild the xml from that? It just seems from you are saying that may be easier. You could potentially make a generic list that is all the data you want in your xml of elements and attributes. Dump the xml into that, and then export that out. Add the dynamic expanding thing that would be a set of rules. Use Linq to XML to query the xml into the generic list.
I am not going to do something as intense as your example as you just need to understand a concept. You may be able to do counts and other operations as well directly in 'descendents' method but I generally prefer to but the contents to a list first and then I know what I have
XML for example:
<Orders>
<Person Name="Brett">
<Order Name="thing"/>
</Person>
<Person Name="Joe">
<Order Name="thing"/>
<Order Name="thing2" />
</Person>
</Orders>
C# Example:
using System;
using System.Linq;
using System.Xml.Linq;
namespace TestLinqXml
{
public class Program
{
private static void Main(string[] args)
{
// load original doc
var doc = XDocument.Load(@"C:\TestCode\TestLinqXml\TestLinqXml\XML\Orders.xml");
// query it with Linq using lambda patterns.
var list = doc.Descendants("Person")
.Select(x => new
// expand descendents out into a class I make up below by declarining new and defining object
{
Name = x.Attribute("Name").Value,
OrderCount = x.Descendants("Order").Count()
}
);
// recreate the new doc
XDocument newdoc = new XDocument(
new XDeclaration("1.0", "UTF-8", "yes"),
new XElement("person",
list.Select(n =>
new XElement(n.Name,
new XAttribute("OrderCount", n.OrderCount)
))
)
);
// save it
newdoc.Save(@"C:\TestCode\TestLinqXml\TestLinqXml\XML\output.xml");
}
}
}
Upvotes: 0
Reputation: 813
At first blush it looks like you are trying to do too much in a single pass. I think that creating the base document is good, but if you need to conditionally add fills then you need a different strategy.
I recommend that you use LINQ to group your orders by Placement_ID before looping over them. This will ensure that the group elements are of the same Placement_ID - and let you add the appropriate number of fills
.
Some pseudo code:
var groupedOrders = listOfOrders.GroupBy(o => o.Placement_ID);
foreach (var group in groupedOrders)
{
//Create document, but don't add any fills
XDocument doc = new XDocument(...)
int n = 1;
foreach (var fill in group)
{
//Same structure as before, just a separate element
XElement newFillElement = new XElement("fill" + n, ...);
//Add as last element to existing doc
doc.Add(newFillElement);
n++
}
}
References:
101 LINQ Examples, LINQ - Grouping Operators
Adding, Updating, and Deleting Data in XML
Upvotes: 1