[C#]DataContractSerializer wil niet alle Types serializen

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • afraca
  • Registratie: April 2009
  • Laatst online: 21-03 16:17

afraca

Open Source!

Topicstarter
Zoals gezien in mijn eerdere post, die enigszins idioot was, maak ik gebruik van de DataContractSerializer voor het serializen van objecten naar xml die geen parameterless constructors kennen. Zoals in de documentatie van DataContractSerializer staat geef je bij de klassen aan dat je ze wilt serializen, dit doe ik in verband met compability met een binary serializer met het [Serializable] attribuut. Wanneer ik een object met meerdere objecten in zich wil gaan serializen, krijg ik de volgende error:

code:
1
2
3
System.Runtime.Serialization.SerializationException: Type 'System.Collections.Generic.List`1[[<namespace-censuur>.Road, <censuur>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' with data contract name 'ArrayOfRoad:http://schemas.datacontract.org/2004/07/<namespace-censuur>' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
   at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiTypeAtTopLevel(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle originalDeclaredTypeHandle, Type graphType)


Ik construeer de DataContractSerializer als volgt:
disclaimer: ranzige code

C#:
1
2
3
4
5
6
7
List<Type> types = new List<Type>();
            foreach (Type type in Global.GetSerializableTypes())
            {
                types.Add(type);
            }
            
            Serializer.dcformatter = new DataContractSerializer(typeof(object), types);


De global method is dit:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
//get classes with serializable attribute: http://stackoverflow.com/questions/607178/c-sharp-how-enumerate-all-classes-with-custom-class-attribute
        public static IEnumerable<Type> GetSerializableTypes()
        {
            foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
            {
                if (type.GetCustomAttributes(typeof(SerializableAttribute), true).Length > 0)
                {
                    yield return type;
                    
                }
            }
        }


Zoals je kunt zien ben ik het pad op gegaan van de laatste suggestie die de exception geeft:
or by adding them to the list of known types passed to DataContractSerializer.
Ik geef hem dus netjes de lijst mee met types die ik wil dat hij kan serializen, maar hij slikt het toch niet. De lijst die ik meegeef bij construction bevat de juiste types. Wat zie ik ditmaal gruwelijk over het hoofd? Ik weet hoe ik moet debuggen, maar met deze error kan ik gewoon niet gek veel.

Voor de volledigheid, ik vermoed dat het zit in mijn interpretatie van de constructor overload:
Initializes a new instance of the DataContractSerializer class to serialize or deserialize an object of the specified type, and a collection of known types that may be present in the object graph.
Ik geef hem als eerste parameter typeof(object) mee, maar dat is niet wat hij bij het serializen als "root" meekrijgt....

[ Voor 6% gewijzigd door afraca op 24-01-2012 16:22 ]

IMDB vote history | Next-gen OS, audio en video player, search engine en Movie DB


Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 29-04 19:36
Er is nogal veel over te vinden. Maar wellicht mis jij net wat kennis waardoor je de link niet legt oid :?

Waar heb je al op gezocht? Heb je uberhaupt al gegoogled? Heb je voorbeelden gezocht naar hoe je de DataContractSerializer moet gebruiken?

Je mist bepaalde attributen op je class definities. Hint: De DataContractSerializer werd geïntroduceerd met WCF.

Acties:
  • 0 Henk 'm!

  • afraca
  • Registratie: April 2009
  • Laatst online: 21-03 16:17

afraca

Open Source!

Topicstarter
Uiteraard heb ik stad en land, maar dan de virtuele variant, afgezocht naar het juiste gebruik van deze klasse. Zoals gezegd werk ik met zowel een Binary serializer als de DataContractSerializer. Laatstgenoemde werd inderdaad geïntroduceerd met WCF, maar is niet beperkt tot gebruik met contracts, uit de voorbeeldcode op MSDN:

C#:
1
2
// You must apply a DataContractAttribute or SerializableAttribute
    // to a class to have it serialized by the DataContractSerializer.


Dus mijn klasse-attribuut is juist.

Ook zien wij bijvoorbeeld bij een ondertussen wat oude vergelijking tussen XML en DCS ( @ hier ):
If you are doing general serialization, it is up to you, but I would weigh out the advantages and disadvantages. I would still prefer the DataContractSerializer for the same reasons I prefer it for Wcf.
Wat ik uiteraard gelezen heb is:
Opt-in rather than opt-out properties to serialize. This mean you specify what you want serialize
Maar dat is nu niet relevant, want hij heeft helemaal geen zin in serializen.

Daarnaast een aantal SO-links gelezen, met quotes als:
Well, I was reading up a bit more on this today, and here I found that if your classes used by the DataContractSerializer are marked with [Serializable], then by default they will be serialized much like the old-style SOAP formatter - EVERY single field regardless of visibility is included. Could that be of help to you maybe? – marc_s Apr 25 '09 at 19:59

[ Voor 16% gewijzigd door afraca op 24-01-2012 18:08 ]

IMDB vote history | Next-gen OS, audio en video player, search engine en Movie DB


Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 29-04 19:36
Ok, I stand corrected. :) Nouja, achteraf had ik een owja moment, maar heb al bijna 2 jaar niet meer direct die serializer gebruikt.

Normaal krijg je een KnownTypes error omdat je subclasses gebruikt welke niet zijn toegevoegd aan je knowntypes collectie.

Wellicht omdat deze niet gemarkeerd zijn als serializable en daardoor uitgesloten worden in je "Global Method" ??

vb:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
[Serializable]
public class DataContainer {
      public BaseObject Data;
}

[Serializable]
public class BaseObject { }

public class SpecificObject : BaseObject {}

....

var container = new DataContainer() { Data = new SpecificObject(); };


De/Serializatie van container faalt omdat SpecificObject onbekend is.

[ Voor 8% gewijzigd door D-Raven op 24-01-2012 18:25 ]


Acties:
  • 0 Henk 'm!

  • afraca
  • Registratie: April 2009
  • Laatst online: 21-03 16:17

afraca

Open Source!

Topicstarter
De klasse die ik wil serializen bevat aardig wat nesting, dus maar even eenvoudig beginnen, en verder "uitbouwen" naar wat ik uiteindelijk wil.

Het gaat hierbij om deze code:
C#:
1
2
Bezier[] test = new Bezier[] { new LinearBezier(-1450, -500, -550, -500) };
                        Serializer.dcformatter.WriteObject(stream, test);


Dit geeft de eerdergenoemde error
code:
1
Additional information: Type 'Traffic_Simulation.Bezier[]' with data contract name 'ArrayOfBezier:http://schemas.datacontract.org/2004/07/Traffic_Simulation' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.



Proberen we echter eenvoudiger:
C#:
1
2
LinearBezier test = new LinearBezier(-1450, -500, -550, -500);
                        Serializer.dcformatter.WriteObject(stream, test);


Dan gaat het prima.... Bezier betreft een abstract klasse, die verder niet erft van een andere klasse.

[ Voor 18% gewijzigd door afraca op 25-01-2012 17:04 ]

IMDB vote history | Next-gen OS, audio en video player, search engine en Movie DB


Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 29-04 19:36
Je moet typeof(Bezier[]) ook toevoegen aan de knowntypes collectie.
niet correct, tenminste, zou je niet hoeven moeten doen.
Edit:

Ben ff een voorbeeld in elkaar aan het gooien. Want het word nu toch wel vaag :P

Edit2: Zie een werkend voorbeeld op Pastebin.

http://pastebin.com/SBysCBHY

[ Voor 73% gewijzigd door D-Raven op 25-01-2012 17:22 ]


Acties:
  • 0 Henk 'm!

  • afraca
  • Registratie: April 2009
  • Laatst online: 21-03 16:17

afraca

Open Source!

Topicstarter
Ik moet bekennen dat ik nieuw ben in System.Reflection, en heb nog niet echt een feeling waar ik mee werk (het blijft programmeren voor hobby). Het zit me niet helemaal lekker dat ik pas runtime met behulp van Reflection die types kan gaan voeren aan m'n DataContractSerializer constructor, al zie ik nu niet echt een alternatief .... Wat je opmerking inhoud is dat ik dus elke datastructuur die mogelijk is met de klassen die [Serializable] als attribuut hebben, óf ga hardcoden in de lijst die ik in de constructor meegeef, of op een vreemde manier dynamisch moet gaan genereren.

Beide opties geven overhead, omdat je natuurlijk oneindig veel datastructuren kan maken met behulp van nested Lists<T>'s enzo.... dus de enige optie zou zijn handmatig de code doorlopen van het object dat ik wil serializen, en gaan kijken waar er bijvoorbeeld in klasse A een object als Dictionary<Type, double> zit? Maar dan gaat de maintainability hard onderuit, omdat, als ik de klasse A die ik wil gaan serializen ga uitbreiden met HashSet<C> ik dan aan de lijst die ik aan mijn serializer constructor meegeef typeof(HashSet<C>) mag gaan toevoegen....

edit: Thanks voor je antwoord, en dat je even de moeite neemt voorbeeld te zetten op pastebin d:)b

[ Voor 4% gewijzigd door afraca op 25-01-2012 17:50 ]

IMDB vote history | Next-gen OS, audio en video player, search engine en Movie DB


Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 29-04 19:36
Valt wel mee. Zowiezo kun je natuurlijk ook gewoon een normale serializer gebruiken. Dan heb je die KnownTypes problemen niet.

Maar wat je kunt doen is al je Model klasses in een aparte assembly stoppen. Vervolgens gewoonweg alle types in die assembly als known types toevoegen aan de serializer. Klaar. Of alle je model types in een eigen namespace, en daarbinnen zoeken. Whatever, mogelijkheden genoeg iig.

.Net types hoef je niet zelf toe te voegen, dus als je List<T> oid gebruikt dan is dat prima, zolang T maar bekend is.
Dat gezegd: Er schijnt wel een issue te zijn met HashTables en Dictionairy's. Zie hier en hier

[ Voor 7% gewijzigd door D-Raven op 25-01-2012 18:25 ]


Acties:
  • 0 Henk 'm!

  • Asator
  • Registratie: December 2009
  • Laatst online: 12-02-2024
Wat is er mis mee om de klassen zelf bij te laten houden welke knowntypes er zijn voor die klasse?

Dat kan op deze manier:

C#:
1
2
3
4
5
    [Serializable]
    [KnownType(typeof(LinearBezier))]
    public class Bezier
    {
    }


Of op deze manier als je werkt met generics.

C#:
1
2
3
4
5
6
7
8
9
10
11
    [Serializable]
    [KnownType("GetKnownType")]
    public class Bezier
    {
        private static Type[] GetKnownType()
        {
            Type[] t = new Type[1];
            t[0] = typeof(LinearBezier<T>);
            return t;
        }
    }


Zie ook: MSDN: Data Contract Known Types

Of mis ik hier iets?

Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 29-04 19:36
* blaat*

[ Voor 96% gewijzigd door D-Raven op 25-01-2012 19:55 ]


Acties:
  • 0 Henk 'm!

  • Asator
  • Registratie: December 2009
  • Laatst online: 12-02-2024
De DataContractSerializer zoekt wel degelijk naar deze attributen, dat heeft niks te maken met WCF. Ik heb het zelfs nog getest in een console applicatie.

http://pastebin.com/LfAAfAVK

Ik raad je aan om die link die ik eerder gepost heb door te lezen.

Acties:
  • 0 Henk 'm!

  • afraca
  • Registratie: April 2009
  • Laatst online: 21-03 16:17

afraca

Open Source!

Topicstarter
Er zijn voor zover ik weet 3 "native" serializers te gebruiken: binary, xml en datacontract, en met externe libraries nog bijvoorbeeld een aangepaste binary. Binary willen we ook gaan gebruiken, maar we willen een bepaald object ook editable maken voor de buitenwereld, daarom deze aanpak (al mag er nog wat getweakt worden, momenteel is het een draak van een output). We werken inderdaad niet met WCF, en momenteel is de huidige aanpak, @runtime met Reflection de knowtypes bepalen acceptabel, al heeft het wel een hacky smaakje :/

Zoals de laatste zin aangaf gaat het nu wel goed, al moest ik wel degelijk

C#:
1
typeof(List<myclass>) 


gebruiken.

@Asator: tijdens het typen van deze post zijn er 3 posts bijgekomen, en zal ik de nette implementatie zoals door jou beschreven dan maar oppakken. Het feest is helaas nog niet helemaal voorbij:
<b:value>System.Runtime.Serialization.SerializationException: Object graph for type 'Traffic_Simulation.RoadLane' contains cycles and cannot be serialized if reference tracking is disabled.

IMDB vote history | Next-gen OS, audio en video player, search engine en Movie DB


Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 29-04 19:36
Echt, wat heb jij voor een object model :+

Acties:
  • 0 Henk 'm!

  • afraca
  • Registratie: April 2009
  • Laatst online: 21-03 16:17

afraca

Open Source!

Topicstarter
Zoals ondertussen duidelijk is geworden uit code fragmenten en de quote hierboven beschrijven we voor een applicatie die traffic simulation doet, wegen met behulp van bezier curves :+ Een weg bestaat uit een aantal eigenschappen als kleur en breedte, en een Bezier array met daarin lineaire bezier curves, of circular en/of quadratic beziers...

De te serializen roadlist op z'n beurt is dan eenvoudigweg een List<Road>

[ Voor 10% gewijzigd door afraca op 25-01-2012 20:04 ]

IMDB vote history | Next-gen OS, audio en video player, search engine en Movie DB


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Met de foutmelding die je krijgt moet je genoeg informatie kunnen vinden, bijvoorbeeld dit: http://smehrozalam.wordpr...-and-circular-references/

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • afraca
  • Registratie: April 2009
  • Laatst online: 21-03 16:17

afraca

Open Source!

Topicstarter
True, dat was geen probleem, ik was even afwezig, dus kon het zojuist pas proberen.

Als wij bijvoorbeeld hier kijken:
MSDN: Interoperable Object References

Zie je in het voorbeeld dat je attributen kan aanduiden als reference als volgt:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class X

{

SomeClass someInstance = new SomeClass();

[DataMember(IsReference=true)]

public SomeClass A = someInstance;

[DataMember(IsReference=true)]

public SomeClass B = someInstance;

}

public class SomeClass

{

}


Maar dat is helemaal niet mogelijk. Datamember heeft in z'n named parameters helemaal geen isreference, isreference vinden we bij [DataContract] , wat je moet gebruiken voor je klasse. Nu is het echter zo dat er dan ook de eis komt dat de base class, waar je klasse van erft, dan ook zo is aangeduid, maar dat geeft een aantal nare bij-effecten.....

Stel ik heb een base class A , en child classes B en C, waar er in klasse C instances van zichzelf zitten. Serializen hiervan geeft problemen tenzij je dus het attribuut [DataContract(IsReference)] aan C meegeeft, maar de eis is dan dus dat A dat ook krijgt. Dan krijg je opeens:
'Traffic_Simulation.Road.SortPoint' is not marked with OptionalFieldAttribute, thus indicating that it must be serialized. However, 'Traffic_Simulation.Road' derives from a class marked with DataContractAttribute and an IsReference setting of 'True'. It is not possible to have required data members on IsReference classes. Either decorate 'Traffic_Simulation.Road.SortPoint' with OptionalFieldAttribute, or disable the IsReference setting on the appropriate parent class.
Ik wil helemaal niet dat dat field optional dat attribuut meekrijgt in klasse B, alleen maar dat dat probleem van die cyclics opgelost gaat worden..... Het serializen in deze context is nog beetje magie voor mij, vandaar dat ik niet echt kan inschatten wat de consequenties zijn als ik in klasse B dan bij dat veld de OptionalFieldAttribute neerzet....

edit: de fout in het voorbeeld op MSDN staat ook bij de community content eronder:
In the provided example, the reference preserving attribute parameter should be set on the data contract and not the data member.
edit2: De schoonheid komt al naar voren bij wat spelen. Met een class die 30 properties kent blijf je maar bezig met die optionalfield..... Hier moet toch een schonere oplossing zijn?

[ Voor 8% gewijzigd door afraca op 26-01-2012 14:29 ]

IMDB vote history | Next-gen OS, audio en video player, search engine en Movie DB


Acties:
  • 0 Henk 'm!

  • afraca
  • Registratie: April 2009
  • Laatst online: 21-03 16:17

afraca

Open Source!

Topicstarter
Wanneer ik nou een List<Buildings> wil gaan serializen, gaat dat prima. Klasse Building:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 [Serializable]
    [KnownType(typeof(Building))]
    public class Building : SimulationObject
    {
        [OptionalField] private GraphicsPath drawgraphicspath;
        [OptionalField] private GraphicsPath graphicspath;
        [NonSerialized] private SolidBrush solidbrush;

        public Building(Color buildingcolor, params double[] coordinates)
        {
            solidbrush = new SolidBrush(buildingcolor);

            graphicspath = new GraphicsPath();
            for (int i = 0; i < coordinates.Length - 3; i += 2)
                graphicspath.AddLine(new PointD(coordinates[i], coordinates[i + 1]), new PointD(coordinates[i + 2], coordinates[i + 3]));
        }
    }


Let op, het serializen van een List<Building> gaat prima, maar bij het deserializen:
System.ArgumentException: Parameter is not valid.
at System.Drawing.Drawing2D.GraphicsPath.get_PathPoints()
at ReadGraphicsPathFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
Als ik hier kijk:
MSDN: GraphicsPath.PathPoints Property (System.Drawing.Drawing2D)

Ik kan niet echt iets specifieks vinden. De getter van GraphicsPath.PathPoints heeft geen params, en ik kan moeilijk zien hoe de deserializer daar wel iets in stopt.

edit: Bij binary serialization gaat hij terecht klagen dat GraphicsPath niet serializable is. Komisch dat wel bij binary serialization, en niet bij de DataContractSerializer een probleem is, al is er inderdaad wel wat voor te zeggen.

edit2: nevermind, ik zet ze gewoon op nonserializable. Dat maakt voor het serializen niet uit, maar voor het deserializen zit je dan met gekke objecten waardoor het drawen gewoon crasht.

[ Voor 12% gewijzigd door afraca op 27-01-2012 20:35 ]

IMDB vote history | Next-gen OS, audio en video player, search engine en Movie DB

Pagina: 1