Saving entities from xml

Posts   
 
    
netclectic avatar
netclectic
User
Posts: 255
Joined: 28-Jan-2004
# Posted on: 08-Mar-2004 14:34:23   

I was looking for a way of taking an xml string originally generated from an enitity and getting it back in to be saved in as generic a way as possible for use from a webservice. Initially i was stumped becuase EntityBase2 was defined abstract and can't be instantiated, but i came up with this:

    public class EntityManager
    {
        public EntityManager()
        {
        }
        
        public static bool SaveEntity(EntityBase2 ent)
        {
            boDataAccessAdapter adapter = new boDataAccessAdapter();
            return adapter.SaveEntity(ent, false, null, false);         
        }
        
        public static bool SaveEntity(string xml)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xml);       
            XmlAttribute assAttr = (XmlAttribute)doc.DocumentElement.Attributes.GetNamedItem("Assembly");
            XmlAttribute typAttr = (XmlAttribute)doc.DocumentElement.Attributes.GetNamedItem("Type");
        
            ObjectHandle hdlEnt;
            hdlEnt = Activator.CreateInstance(assAttr.Value, typAttr.Value);
            EntityBase2 ent = (EntityBase2)hdlEnt.Unwrap();
            ent.ReadXml(xml);
        
            return SaveEntity(ent);
        }
    }

Obviously, it needs a bit of work to better deal with refetching and predicates etc., but I was wondering if anybody else had done something similar, differently? Is there a better way of achieving this?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 08-Mar-2004 14:42:31   

I do this in ReadXml to re-instantiate an entity reference (e.g. myOrder.Customer)


// get type and assembly for entity instance.
string entityAssemblyName = entityNode.Attributes["Assembly"].Value;
string entityTypeName = entityNode.Attributes["Type"].Value;
// load assembly
Assembly entityAssembly = Assembly.Load(entityAssemblyName);
// create instance
EntityBase2 referencedEntity = (EntityBase2)entityAssembly.CreateInstance(entityTypeName);

Frans Bouma | Lead developer LLBLGen Pro
netclectic avatar
netclectic
User
Posts: 255
Joined: 28-Jan-2004
# Posted on: 08-Mar-2004 15:28:19   

Hhmmm... that's a bit neater, so i end up with a SaveEntity like this:

        public static bool SaveEntity(string xml)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xml);       
            XmlAttribute assAttr = (XmlAttribute)doc.DocumentElement.Attributes.GetNamedItem("Assembly");
            XmlAttribute typAttr = (XmlAttribute)doc.DocumentElement.Attributes.GetNamedItem("Type");
        
            Assembly entityAssembly = Assembly.Load(assAttr.Value);
            EntityBase2 ent = (EntityBase2)entityAssembly.CreateInstance(typAttr.Value);            
            ent.ReadXml(xml);
        
            return SaveEntity(ent);
        }

Does that seem like a fair way of achieving it? I'm not sure i like doing LoadXml and then ReadXml... can you think of a neater way of doing it?

Does XmlDocument not validate the xml anymore? You used to be able to set ValidateOnParse to false to tell it not to bother validating the contents of the xml, but that doesn't seem to be an option anymore.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 08-Mar-2004 15:40:48   

netclectic wrote:

Hhmmm... that's a bit neater, so i end up with a SaveEntity like this:

        public static bool SaveEntity(string xml)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xml);       
            XmlAttribute assAttr = (XmlAttribute)doc.DocumentElement.Attributes.GetNamedItem("Assembly");
            XmlAttribute typAttr = (XmlAttribute)doc.DocumentElement.Attributes.GetNamedItem("Type");
        
            Assembly entityAssembly = Assembly.Load(assAttr.Value);
            EntityBase2 ent = (EntityBase2)entityAssembly.CreateInstance(typAttr.Value);            
            ent.ReadXml(xml);
        
            return SaveEntity(ent);
        }

Does that seem like a fair way of achieving it? I'm not sure i like doing LoadXml and then ReadXml... can you think of a neater way of doing it?

You could opt for passing around an XmlNode object (afaik, you can return such an object from a webmethod). You can then import that node into a new empty XmlDocument and pass it directly to the ReadXml method. However I'm not sure if that's faster.

Does XmlDocument not validate the xml anymore? You used to be able to set ValidateOnParse to false to tell it not to bother validating the contents of the xml, but that doesn't seem to be an option anymore.

I don't know, afaik it validates only if a schema is specified.

Frans Bouma | Lead developer LLBLGen Pro
netclectic avatar
netclectic
User
Posts: 255
Joined: 28-Jan-2004
# Posted on: 08-Mar-2004 15:44:28   

Otis wrote:

You could opt for passing around an XmlNode object (afaik, you can return such an object from a webmethod). You can then import that node into a new empty XmlDocument and pass it directly to the ReadXml method. However I'm not sure if that's faster.

Sadly not an option, it has to be xml.

Otis wrote:

I don't know, afaik it validates only if a schema is specified.

That would make sense.

Cheers!

colinvella
User
Posts: 12
Joined: 28-Feb-2006
# Posted on: 09-May-2006 15:46:56   

On a related note, my development team is relying on the ReadXML and WriteXML methods to create import and export functions.

Using the import function on the same database from which the export was performed works, apparently because the exported data matches, and the database is updated.

We are having problems, however, on trying to import the data in another replica of the database where the exported data does not already exist (we are assuming that the data gets inserted into the database).

For the import, we are essentially reading in the XML nodes and feeding them to new instances of the entities. We then invoke the Save() method of the entity to insert the data into the database.

Here is a function (with error checking removed for the sake of simplicity) to import records for a table InspectionHeader:

private void ImportRecordedInspectionHeaders(XmlNode xmlNode) { foreach (XmlNode node in xmlNode.ChildNodes) { InspectionHeaderEntity entity = new InspectionHeaderEntity(); entity.ReadXml(node); entity.Save(); } }

The entity appears to be be populated correctly, but the Save() function fails.

Is there something wrong with the logic?

Regards

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 10-May-2006 10:33:17   

ReadXml reads the data into entities and the XML does contain change tracking info, which means that if the data was read from db A and saved to XML using WriteXml, if you then read it back into entities using ReadXml, the entities won't be new nor changed so no save will take place.

Frans Bouma | Lead developer LLBLGen Pro
colinvella
User
Posts: 12
Joined: 28-Feb-2006
# Posted on: 10-May-2006 10:57:24   

I figured out a solution. I found a code snippet in another thread to set the IsDirty property of the entity and the IsChanged property of all its fields to true.

The last problem was figuring out whether the record was an update or a new one. I solved it with some class reflection trickery. Here is the method (it assumes that all entities have a simple Guid primary key):


        void ForceEntityUpdate(EntityBase entityBase, Guid gEntityId)
        {
            entityBase.IsDirty = true;
            for (int nIndex = 0; nIndex < entityBase.Fields.Count; nIndex++)
                entityBase.Fields[nIndex].IsChanged = true;

            // determine if entity new or existing
            Type entityType = entityBase.GetType();
            Type[] argumentTypes = new Type[1];
            argumentTypes[0] = typeof(Guid);
            ConstructorInfo entityConstructor = entityType.GetConstructor(argumentTypes);
            Object[] argumentValues = new Object[1];
            argumentValues[0] = gEntityId;
            EntityBase existingEntity = (EntityBase) entityConstructor.Invoke(argumentValues);
            entityBase.IsNew = existingEntity.IsNew;
        }

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 10-May-2006 11:13:43   

Indeed the way to do it! simple_smile

Frans Bouma | Lead developer LLBLGen Pro