Serialize a 1:1 reference relationship as attribute of source
-
2012年2月13日 下午 04:39
Hello,
I am preparing a reimplementation of an already defined XML-based language with the VsVmSDK in Visual Studio. After some tweaking of the XML Serialization properties in the DSL explorer I was able to serialize to and deserialize from existing XML files (which were written using the old XML schema).
Unfortunately I still have a problem with 1:1 reference relationships between domain classes. My old XML schema uses an attribute on the source domain class to reference a target domain class using the name of the target (which is unique). Is there a way to achieve this with the DSL-implementation?
Example:
<data> <controllers> <controller name="KB" version="01.42" /> </controllers> <items> <item name="Test" controller="KB" ...> ... </item> </items> </data>
In the DSL-model I established a reference relationship between the domain classes for "item" (source) and "controller" (target) and set the multiplicity to "0..1". If I omit the ItemReferencesController element on Item the XML produced by the DSL looks like this:
<data> <controllers> <controller name="KB" version="01.42" /> </controllers> <items> <item name="Test" ...> <controllerMoniker name="KB" /> ... </item> </items> </data>Thanks for any help or ideas for possible solutions.
所有回覆
-
2012年2月21日 下午 05:21擁有者
It is possible but you'll need to write code to customise the serializers, which can be tedious.
One possible approach is as follows:
1) add a custom-storage domain property to the source element to store the id of the linked element;
2) customising the source element serializer so it doesn't save the reference relationship;
3) add custom post-load processing to fix up the links.
There are more comments in the code below.
Regards,
Duncan
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.VisualStudio.Modeling; using DslModeling = global::Microsoft.VisualStudio.Modeling; namespace Company.SerializeOneOneAsAttributeLanguage { // Example of how to serialize a 1-1 reference relationship in an attribute. // The domain model is based on the default simple domain model. // The domain model is called "SerializeOneOneAsAttributeLanguage". // A new element called Host has been added, which is embedded under the model root. // There is a 1-1 reference relationship between ExampleElement and Host. // Other domain model changes: // 1. Add a domain property to ExampleElement to store the id of the linked element. // I added a domain property called "HostId" to ExampleElement and // set "Kind"="CustomStorage", "IsBrowsable=False", and the getter // and setter access modifiers to "private". // 2. Mark the ExampleElement as having a custom serializer // (DslExplorer\Xml Serialization Behavior\Class Data\ExampleElement: "IsCustom=True") // In an ideal world, we'd use the existing moniker resolution mechanism for this. // Here, we're solving the problem just for this special case by providing custom // storage for the id of the linked host, and then creating the links in custom // code once all of the elements have been loaded. partial class ExampleElement { // Id of the linked host. Only used during loading. internal string LinkedHostId { get; private set; } // Will be called when saving to get the id to persist. internal string GetHostIdValue() { return this.Host == null ? null : this.Host.Name; } // Will be called on loading to tell us which item to link to. internal void SetHostIdValue(string hostId) { this.LinkedHostId = hostId; } } // Marking the element as having a custom serializer produce generated code that // expects a whole series of methods called "CustomXXX". The only ones we need to // change is "CustomWriteElement". partial class ExampleElementSerializer { // Code in this method is just copied from the generated code. private void CustomWriteElements(DslModeling::SerializationContext serializationContext, DslModeling::ModelElement element, global::System.Xml.XmlWriter writer) { // Always call the base class so any extensions are serialized base.WriteElements(serializationContext, element, writer); ExampleElement instance = element as ExampleElement; global::System.Diagnostics.Debug.Assert(instance != null, "Expecting an instance of ExampleElement!"); // Write child model elements (which are always serialized as nested XML elements). if (!serializationContext.Result.Failed) CustomWriteChildElements(serializationContext, instance, writer); } // This method is also copied from the generated code. However, I commented // out the serialization of the relationship to Host. /// <summary> /// Serialize all child model elements. /// </summary> /// <param name="serializationContext">Serialization context.</param> /// <param name="element">ExampleElement instance to be serialized.</param> /// <param name="writer">XmlWriter to write serialized data to.</param> [global::System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Generated code.")] [global::System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Generated code.")] private static void CustomWriteChildElements(DslModeling::SerializationContext serializationContext, ExampleElement element, global::System.Xml.XmlWriter writer) { // ExampleElementReferencesTargets global::System.Collections.ObjectModel.ReadOnlyCollection<ExampleElementReferencesTargets> allExampleElementReferencesTargetsInstances = ExampleElementReferencesTargets.GetLinksToTargets(element); if (!serializationContext.Result.Failed && allExampleElementReferencesTargetsInstances.Count > 0) { DslModeling::DomainRelationshipXmlSerializer relSerializer = serializationContext.Directory.GetSerializer(ExampleElementReferencesTargets.DomainClassId) as DslModeling::DomainRelationshipXmlSerializer; global::System.Diagnostics.Debug.Assert(relSerializer != null, "Cannot find serializer for ExampleElementReferencesTargets!"); global::System.Type typeofExampleElementReferencesTargets = typeof(ExampleElementReferencesTargets); foreach (ExampleElementReferencesTargets eachExampleElementReferencesTargetsInstance in allExampleElementReferencesTargetsInstances) { if (serializationContext.Result.Failed) break; if (eachExampleElementReferencesTargetsInstance.GetType() != typeofExampleElementReferencesTargets) { // Derived relationships will be serialized in full-form. DslModeling::DomainClassXmlSerializer derivedRelSerializer = serializationContext.Directory.GetSerializer(eachExampleElementReferencesTargetsInstance.GetDomainClass().Id); global::System.Diagnostics.Debug.Assert(derivedRelSerializer != null, "Cannot find serializer for " + eachExampleElementReferencesTargetsInstance.GetDomainClass().Name + "!"); derivedRelSerializer.Write(serializationContext, eachExampleElementReferencesTargetsInstance, writer); } else { // No need to serialize the relationship itself, just serialize the role-player directly. DslModeling::ModelElement targetElement = eachExampleElementReferencesTargetsInstance.Target; DslModeling::DomainClassXmlSerializer targetSerializer = serializationContext.Directory.GetSerializer(targetElement.GetDomainClass().Id); global::System.Diagnostics.Debug.Assert(targetSerializer != null, "Cannot find serializer for " + targetElement.GetDomainClass().Name + "!"); targetSerializer.WriteMoniker(serializationContext, targetElement, writer, element, relSerializer); } } } //// ExampleElementReferencesHost //ExampleElementReferencesHost theExampleElementReferencesHostInstance = ExampleElementReferencesHost.GetLinkToHost(element); //if (!serializationContext.Result.Failed && theExampleElementReferencesHostInstance != null) //{ // DslModeling::DomainRelationshipXmlSerializer relSerializer = serializationContext.Directory.GetSerializer(ExampleElementReferencesHost.DomainClassId) as DslModeling::DomainRelationshipXmlSerializer; // global::System.Diagnostics.Debug.Assert(relSerializer != null, "Cannot find serializer for ExampleElementReferencesHost!"); // global::System.Type typeofExampleElementReferencesHost = typeof(ExampleElementReferencesHost); // if (theExampleElementReferencesHostInstance.GetType() != typeofExampleElementReferencesHost) // { // Derived relationships will be serialized in full-form. // DslModeling::DomainClassXmlSerializer derivedRelSerializer = serializationContext.Directory.GetSerializer(theExampleElementReferencesHostInstance.GetDomainClass().Id); // global::System.Diagnostics.Debug.Assert(derivedRelSerializer != null, "Cannot find serializer for " + theExampleElementReferencesHostInstance.GetDomainClass().Name + "!"); // derivedRelSerializer.Write(serializationContext, theExampleElementReferencesHostInstance, writer); // } // else // { // No need to serialize the relationship itself, just serialize the role-player directly. // DslModeling::ModelElement targetElement = theExampleElementReferencesHostInstance.Host; // DslModeling::DomainClassXmlSerializer targetSerializer = serializationContext.Directory.GetSerializer(targetElement.GetDomainClass().Id); // global::System.Diagnostics.Debug.Assert(targetSerializer != null, "Cannot find serializer for " + targetElement.GetDomainClass().Name + "!"); // targetSerializer.WriteMoniker(serializationContext, targetElement, writer, element, relSerializer); // } //} } #region Pass-through methods - just call the DefaultXXX method. private void CustomConstructor() { this.DefaultConstructor(); } private void CustomRead(DslModeling::SerializationContext serializationContext, DslModeling::ModelElement element, global::System.Xml.XmlReader reader) { this.DefaultRead(serializationContext, element, reader); } private string CustomXmlTagName { get { return this.DefaultXmlTagName; } } private string CustomMonikerTagName { get { return this.DefaultMonikerTagName; } } private string CustomMonikerAttributeName { get { return this.DefaultMonikerAttributeName; } } private void CustomReadPropertiesFromAttributes(DslModeling::SerializationContext serializationContext, DslModeling::ModelElement element, global::System.Xml.XmlReader reader) { this.DefaultReadPropertiesFromAttributes(serializationContext, element, reader); } private DslModeling::ModelElement CustomTryCreateInstance(DslModeling::SerializationContext serializationContext, global::System.Xml.XmlReader reader, DslModeling::Partition partition) { return this.DefaultTryCreateInstance(serializationContext, reader, partition); } private DslModeling::ModelElement CustomCreateInstance(DslModeling::SerializationContext serializationContext, global::System.Xml.XmlReader reader, DslModeling::Partition partition) { return this.DefaultCreateInstance(serializationContext, reader, partition); } private DslModeling::Moniker CustomTryCreateMonikerInstance(DslModeling::SerializationContext serializationContext, global::System.Xml.XmlReader reader, DslModeling::ModelElement sourceRolePlayer, global::System.Guid relDomainClassId, DslModeling::Partition partition) { return DefaultTryCreateMonikerInstance(serializationContext, reader, sourceRolePlayer, relDomainClassId, partition); } private DslModeling::Moniker CustomCreateMonikerInstance(DslModeling::SerializationContext serializationContext, global::System.Xml.XmlReader reader, DslModeling::ModelElement sourceRolePlayer, global::System.Guid relDomainClassId, DslModeling::Partition partition) { return this.DefaultCreateMonikerInstance(serializationContext, reader, sourceRolePlayer, relDomainClassId, partition); } private void CustomWriteMoniker(DslModeling::SerializationContext serializationContext, DslModeling::ModelElement element, global::System.Xml.XmlWriter writer, DslModeling::ModelElement sourceRolePlayer, DslModeling::DomainRelationshipXmlSerializer relSerializer) { this.DefaultWriteMoniker(serializationContext, element, writer, sourceRolePlayer, relSerializer); } private void CustomWrite(DslModeling::SerializationContext serializationContext, DslModeling::ModelElement element, global::System.Xml.XmlWriter writer, DslModeling::RootElementSettings rootElementSettings) { this.DefaultWrite(serializationContext, element, writer, rootElementSettings); } private void CustomReadElements(DslModeling::SerializationContext serializationContext, DslModeling::ModelElement element, global::System.Xml.XmlReader reader) { this.DefaultReadElements(serializationContext, element, reader); } private void CustomWritePropertiesAsAttributes(DslModeling::SerializationContext serializationContext, DslModeling::ModelElement element, global::System.Xml.XmlWriter writer) { this.DefaultWritePropertiesAsAttributes(serializationContext, element, writer); } private string CustomCalculateQualifiedName(DslModeling::DomainXmlSerializerDirectory directory, DslModeling::ModelElement element) { return this.DefaultCalculateQualifiedName(directory, element); } private string CustomGetMonikerQualifier(DslModeling::DomainXmlSerializerDirectory directory, DslModeling::ModelElement element) { return this.DefaultGetMonikerQualifier(directory, element); } #endregion } partial class SerializeOneOneAsAttributeLanguageSerializationHelper { // Here we are changing the loader helper to fix up the relationships // between the ExampleElements and Hosts. // We're doing this here so that the ExampleElement and Host instances // will have already been created. protected override void ReadRootElement(Microsoft.VisualStudio.Modeling.SerializationContext serializationContext, Microsoft.VisualStudio.Modeling.ModelElement rootElement, System.Xml.XmlReader reader, Microsoft.VisualStudio.Modeling.ISchemaResolver schemaResolver) { base.ReadRootElement(serializationContext, rootElement, reader, schemaResolver); if (rootElement is ExampleModel && !serializationContext.Result.Failed) { FixUpHostLinks((ExampleModel)rootElement); } } private static void FixUpHostLinks(ExampleModel rootElement) { // Find the linked host elements by name and create a link in the model. // We're still inside the serialization transaction at this point. Store store = rootElement.Store; foreach (ExampleElement el in rootElement.Elements) { if (el.LinkedHostId != null) { Host host = rootElement.Hosts.FirstOrDefault(h => StringComparer.InvariantCultureIgnoreCase.Compare(el.LinkedHostId, h.Name) == 0); if (host != null) { el.Host = host; } } } } } }- 已標示為解答 Blair McGMicrosoft Employee, Owner 2012年3月2日 下午 01:50
-
2012年3月2日 下午 02:10
Hello,
sorry for my late feedback, I was quite busy with some other tasks. Thank you for your detailed answer. I already tried another approach involving custom code. I like the idea of adding an hidden domain property to store my custom reference to the target domain class. This approach requires less modifications to the original code (compared to my approach).
Is it planned for future versions of this toolset to add the discussed feature for 1:1 reference relationships?
Thanks again!
-
2012年3月9日 下午 12:57擁有者No problem. No, there are no plans to add this a feature at present.

