Preserving Order In Sequence of Choices (LINQ To XSD)
Asked Answered
S

2

7

Given the following XML example we could imagine a schema defining Root as containing a sequence of unbound number of choices between Type1 and Type2.

<Root>
    <Type1 />
    <Type2 />
    <Type2 />
    <Type1 />
</Root>

I am testing out migrating from the XSD.exe tool which although adds type-safety has a lot of little annoyances. The XSD tool in this case just creates within Root an array of type System.Object and you have to figure out what type of objects (Type1 or Type2) are in there. Its not completely elegant, but at least you preserve order.

The problem is when LINQ to XSD creates the objects, it defines Root as having two independent lists of Type1 and Type2. This is great in that it is type-safe, but I now appear to lose the order of the elements. I built LINQ to XSD from the source on codeplex.

Using LINQ to XSD, how can I preserve the order of these elements?

Skimmia answered 28/10, 2009 at 15:50 Comment(4)
Well, you've already presented the only two options. Either you get a weakly typed collection that preserves order, or you get a strongly-typed collection per type. Pretend you're not using any XML at all - how would you write a pure-code object that has a single strongly-typed collection with multiple types in it?Mitosis
My question is how to preserve the order of the elements in this scenario using Linq to XSD. I realize to have a collection of mixed types they'd need to be of System.Object (or whatever parent they have in common). I'm willing to give up strongly typed objects in this scenario w/Linq to XSD to preserve order. I was hoping there was a way to force it to do so. In my use case, order matters, so I couldn't use Linq to XSD although I'd really like to as it has many advantages over XSD.exeSkimmia
You could do that with inheritance. If both Type1 and Type2 had a common base class, you could have an IList<BaseType>. Now you have a single, strongly typed list, and order is preserved. When iterating through the list, just check the type of the current object. foreach(BaseType el in elements) { if (el is Type1) ... else if (el is Type2) ... }Saccular
Piggy backing off the other comments, it looks like you answered your own question. To get the order you must use a base class of some sort, be it object or one of your creation, and do type checking at run-time. I suggest you add an answer then accept it. :-) You get a badge for that if I'm not mistaken.Uranyl
D
2

How about creating a wrapper around Choice? Limiting the types that it accesses like this:

class Choice
{
    private object _value;

    public ChoiceEnum CurrentType { get; private set; }

    public Type1 Type1Value
    {
        get { return (Type1) _value; }
        set { _value = value; CurrentType = ChoiceEnum.Type1; }
    }

    public Type2 Type2Value
    {
        get { return (Type2) _value; }
        set { _value = value; CurrentType = ChoiceEnum.Type2; }
    }
}

This is a simplified version, and you will have to add more validation (if _value is of correct type, what is the current type of _value, etc).

Then, you can filter it with LINQ:

var q1 = from v in root.Sequence
         where v.CurrentType == ChoiceEnum.Type1
         select v.Type1;

Or you can create methods in Root that will wrap the queries.

Disposition answered 24/12, 2009 at 22:24 Comment(1)
Good suggestion. This should work, but does dictate the design of the XSD itself which is unfortunate, and sometimes impossible (industry standard schemas for example). A lot of the schemas we work with on our team though are created by us, so we may be able to use this technique.Skimmia
I
1

Linq2Xsd only trips up on sequences when there's an xsd:choice element.

Fortunately I was able to remove the xsd:choice for the Amazon XSD I am using (I just wasn't using MerchantOrderID), which allowed the sequence to properly be preserved in the ToString() for the xml.

            <xsd:choice>                                <--- removed line
                <xsd:element ref="AmazonOrderID"/>
                <xsd:element ref="MerchantOrderID"/>    <--- removed line
            </xsd:choice>                               <--- removed line

            <xsd:element name="ActionType" minOccurs="0" maxOccurs="1">
                <xsd:simpleType>
                    <xsd:restriction base="xsd:string">
                        <xsd:enumeration value="Refund"/>
                        <xsd:enumeration value="Cancel"/>
                    </xsd:restriction>
                </xsd:simpleType>
            </xsd:element> 

The generated code then correctly has this in the constructor which preserves the order

contentModel = new SequenceContentModelEntity(
               new NamedContentModelEntity(XName.Get("AmazonOrderID", "")), 
               new NamedContentModelEntity(XName.Get("ActionType", "")), 
               new NamedContentModelEntity(XName.Get("CODCollectionMethod", "")), 
               new NamedContentModelEntity(XName.Get("AdjustedItem", "")));

You may also be able to do this manually by subclassing it youself, but I'm not sure how this would work with an xsd:choice. This is described here but I haven't tested it.

Isabelleisac answered 12/3, 2014 at 18:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.