This project has moved and is read-only. For the latest updates, please go here.

Using persitency with ModelObjects containing complex properties

Apr 11, 2014 at 11:06 AM
Hi,
I am currently trying to persist my business layer thanks to the NShape persistency mechanism but can't afford to find how to persist an IModelObject containing ("deeply") complex properties.

I already found that discussion about a way to manage "simple" complex properties (i.e. property containing only value type fields) so it does not fully answer my question.

I will illustrate it with an example so it will be easier to understand:
Let's say we have to build a network architecture composed of devices that are connected thanks to different links. Each Device is represented by a planar shape and each Link is represented by a linear shape. The business logic can be easily implemented through model objects associated to the shapes.
But let's take a more complex scenario in which each Device has several inputs and outputs. Each input/output can be connected to another device thanks to a link and network data are exchanged on this link.
So if I now want to implement my business logic, I basically need a Device class that contains a list of Ports that each contains a Data to be produced/consummed.
So now how can I implement the persistency of my business layer knowing that Device, Port and Data are complex objects.

A little code example:
public class Device : ModelObjectBase
{
    public List<Port> Ports { get; set; }
    // ...
}

public class Port
{
    public bool Connected { get; set; }
    public Direction PortDirection {get; set; }
    public Data AssociatedData { get; set; }
    // ...
}

public class Data
{
    public string Name { get; set; }
    public NetworkType DataType { get; set; }
    public string Description { get; set; }
    // ...
}
So my question is How do I implement my GetProperties() method in the Device class ?
Apr 11, 2014 at 2:38 PM
Edited Apr 11, 2014 at 2:39 PM
After a bit of testing, I found out that the IEntity.SaveInnerObjects() method of a ModelObjectBase is not called when saving a project. Is there any workaround ?
Apr 14, 2014 at 8:31 AM
Edited Apr 14, 2014 at 8:33 AM
Did you add a InnerObjectPropertyDefinition to your model object?

You have to 'override' the static method GetPropertyDefinitions for defining the fields and inner objects to persist.
Here is an example:
public static new IEnumerable<EntityPropertyDefinition> GetPropertyDefinitions(int version) {
    // Return all property definitions defined by the base type
    foreach (EntityPropertyDefinition pi in ShapeBase.GetPropertyDefinitions(version))
        yield return pi;
    
    // Define properties for fields defined by this type
    yield return new EntityFieldDefinition("Field1", typeof(int));
    yield return new EntityFieldDefinition("Field2", typeof(string));
    
    // Define properties for inner objects defined by this type
    yield return new EntityInnerObjectsDefinition("Ports", "MyLibrary.Ports", 
        new string[] { 
            "Connected", 
            "PortDirection", 
            "Data.Name", 
            "Data.DataType", 
            "Data.Description" 
        }, 
        new Type[] { 
            typeof(bool), 
            typeof(int), 
            typeof(string), 
            typeof(int), 
            typeof(string) 
        }
    );
    ...
}
In this example, I assume that each port has exactly one data assigned. In case a port can have a list of data, you will have to serialize the associated data as string and save this string as "AssociatedData". Inner objects containing nested inner objects are not supported.
Marked as answer by roux1max on 4/14/2014 at 1:30 AM
Apr 14, 2014 at 8:43 AM
After digging into the code, I found out that the XmlStore does not call the SaveInnerObjects method of model objects.
This bug will be corrected with the next release. Until then, you can replace the "WriteModelObject" method in XmlStore.cs by this version:
        private void WriteModelObject(IStoreCache cache, IModelObject modelObject, RepositoryWriter writer) {
            IEntityType modelObjectEntityType = cache.FindEntityTypeByName(modelObject.Type.FullName);
            string modelObjectTag = GetElementTag(modelObjectEntityType);
            XmlOpenElement(modelObjectTag);
            writer.Reset(modelObjectEntityType.PropertyDefinitions);
            writer.Prepare(modelObject);
            if (modelObject.Id == null) modelObject.AssignId(Guid.NewGuid());
            writer.WriteId(modelObject.Id);
            modelObject.SaveFields(writer, modelObjectEntityType.RepositoryVersion);
            foreach (EntityPropertyDefinition pi in modelObjectEntityType.PropertyDefinitions)
                if (pi is EntityInnerObjectsDefinition)
                    ((IEntity)modelObject).SaveInnerObjects(pi.Name, repositoryWriter, modelObjectEntityType.RepositoryVersion);
            writer.Finish();
            // ToDo: Save model object's children
            if (cache is IRepository) {
                foreach (IModelObject child in ((IRepository)cache).GetModelObjects(modelObject))
                    WriteModelObject(cache, child, writer);
            } else throw new NotImplementedException();
            XmlCloseElement();
        }
Marked as answer by roux1max on 4/14/2014 at 1:30 AM
Apr 14, 2014 at 9:30 AM
Thank you, this is exactly what I needed.
Apr 14, 2014 at 10:07 AM
Be careful about the code snippet you gave me, when you call the SaveInnerObjects() method you provide the repositoryWriter as parameter instead of the writer from the incoming parameters which seems to throw an error about an index. Just replacing repositoryWriter by writer works fine with me.
Apr 14, 2014 at 3:10 PM
Edited Apr 14, 2014 at 3:12 PM
That's correct. It must be "writer", not "repositoryWriter".
Thanks for the feedback.