Revit API Forum
Welcome to Autodesk’s Revit API Forums. Share your knowledge, ask questions, and explore popular Revit API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Trouble adding new KeyValue to Extensible Storage dictionary

7 REPLIES 7
Reply
Message 1 of 8
a.bejenaru
620 Views, 7 Replies

Trouble adding new KeyValue to Extensible Storage dictionary

Dear friends,

 

I'm trying to create a sort of HistoryReport for a model and I thought of using a Extensible Storage for this.

I have it almost all working and i've created a Method that should add a Time Stamp Key to the Extensible Storage field.

 

What happens is that i does not add a new value to the dictionary. Instead it overwrites the dictionary and I always get a Single entry dictionary. 

 

Here's the method:

        public static void AddSchemaEntityActionsHistory(Schema schema, ProjectInfo projInfo, string actionToAdd)
        {
            // Read exisiting data and add new actions
            Entity retrieveEntity = projInfo.GetEntity(schema);
            IDictionary<string, string> dict = retrieveEntity.Get<IDictionary<string, string>>("ActionsHistory");

            //add the new action
            dict.Add(DateTime.Now.ToString(), actionToAdd);

            // create an entity object (object) for this schema (class)
            Entity entity = new Entity(schema);

            // get the field from schema
            Field schemaField = schema.GetField("ActionsHistory");

            // set the value for entity
            entity.Set(schemaField, dict);

            // store the entity on the element
            projInfo.SetEntity(entity);

        }

 

And here is the hole solution:

#region Namespaces
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;

using Autodesk.Revit.UI.Selection;
using Autodesk.Revit.DB.ExtensibleStorage;


#endregion // Namespaces

namespace MIDEON_Addin
{
    // Create Elment Filter that passes all elements through
    public class ElementSelectionFilter : ISelectionFilter
    {
        public bool AllowElement(Element e)
        {
            return true;
        }
        public bool AllowReference(Reference r, XYZ p)
        {
            return false;
        }
    }

    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    public class MIDEON_CmdModelConfiguration : IExternalCommand
    {
        static AddInId appId = new AddInId(new Guid("ECA85C56-8307-4DDB-8F3F-10ADDA80EEA2")); //guid generat per MIDEON_CmdModelConfiguration
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elementSet)
        {
            UIApplication uiapp = commandData.Application;
            UIDocument uidoc = uiapp.ActiveUIDocument;
            Application app = uiapp.Application;
            Document doc = uidoc.Document;

            //https://archi-lab.net/what-why-and-how-of-the-extensible-storage/

            Schema schema = null;
            string schemaName = "MBProductivityConfigSchema";
            try
            {
                // get all elements with schema
                if (SchemaExist(schemaName))
                {
                    schema = GetSchema(schemaName);
                }

                using (Transaction t = new Transaction(doc, "MB Productivity model setup"))
                {
                    t.Start();

                    if (schema == null)
                    {
                        TaskDialog.Show("testing", "creating schema");
                        schema = CreateSchemaMBProductivity(schemaName);
                    }
                    ProjectInfo projectInfo = doc.ProjectInformation;
                    AddSchemaEntityLastAppliedPrototype(schema, projectInfo, "PF202102055552");
                    AddSchemaEntityActionsHistory(schema, projectInfo, "click"); // + new Random().ToString());


                    t.Commit();
                }

                // show message
                //TaskDialog.Show(Caption, "There is total of " + refs.Count.ToString() + " elements protected from being deleted.");
                return Result.Succeeded;
            }
            catch (OperationCanceledException)
            {
                return Result.Cancelled;
            }
            catch (Exception ex)
            {
                message = ex.Message;
                return Result.Failed;
            }

            //return Result.Succeeded;
        }


        public static Schema GetSchema(string schemaName)
        {
            Schema schema = null;
            IList<Schema> schemas = Schema.ListSchemas();
            if (schemas != null && schemas.Count > 0)
            {
                // get schema
                foreach (Schema s in schemas)
                {
                    if (s.SchemaName == schemaName)
                    {
                        schema = s;
                        break;
                    }
                }
            }
            return schema;
        }

        public static bool SchemaExist(string schemaName)
        {
            bool result = false;
            if (GetSchema(schemaName) != null)
            {
                result = true;
            }
            return result;
        }

        public static Schema CreateSchemaMBProductivity(string schemaName)
        {
            Guid schemaGuid = new Guid("6F95B812-01D3-4401-BAFB-3F86D1712206");

            SchemaBuilder schemaBuilder = new SchemaBuilder(schemaGuid);

            // set read access
            schemaBuilder.SetReadAccessLevel(AccessLevel.Public);

            // set write access
            schemaBuilder.SetWriteAccessLevel(AccessLevel.Public);

            // set schema name
            schemaBuilder.SetSchemaName(schemaName);

            // set documentation
            schemaBuilder.SetDocumentation("String value containing the latest applied Prototype.");

            // create a field to store the bool value
            FieldBuilder fieldBuilder = schemaBuilder.AddSimpleField("LastAppliedPrototype", typeof(string));

            // set documentation
            schemaBuilder.SetDocumentation("History of actions applied to model.");

            // create a field to store the bool value
            fieldBuilder = schemaBuilder.AddMapField("ActionsHistory", typeof(string), typeof(string));

            // register the schema
            Schema schema = schemaBuilder.Finish();

            return schema;
        }
        public static Schema CreateSchema(string schemaName, Dictionary<string, string> fieldsDict)
        {
            Guid schemaGuid = new Guid(Guid.NewGuid().ToString());

            SchemaBuilder schemaBuilder = new SchemaBuilder(schemaGuid);

            // set read access
            schemaBuilder.SetReadAccessLevel(AccessLevel.Public);

            // set write access
            schemaBuilder.SetWriteAccessLevel(AccessLevel.Public);

            // set schema name
            schemaBuilder.SetSchemaName(schemaName);
            foreach (KeyValuePair<string, string> kvp in fieldsDict)
            {
                // set documentation
                schemaBuilder.SetDocumentation(kvp.Value);

                // create a field to store the bool value
                FieldBuilder fieldBuilder = schemaBuilder.AddSimpleField(kvp.Key, typeof(String));
            }

            // register the schema
            Schema schema = schemaBuilder.Finish();

            return schema;
        }


        public static void AddSchemaEntityLastAppliedPrototype(Schema schema, ProjectInfo projInfo, string fieldValue)
        {
            // create an entity object (object) for this schema (class)
            Entity entity = new Entity(schema);

            // get the field from schema
            Field schemaField = schema.GetField("LastAppliedPrototype");

            // set the value for entity
            entity.Set(schemaField, fieldValue);

            // store the entity on the element
            projInfo.SetEntity(entity);
        }
        public static void AddSchemaEntityActionsHistory(Schema schema, ProjectInfo projInfo, string actionToAdd)
        {
            // Read exisiting data and add new actions
            Entity retrieveEntity = projInfo.GetEntity(schema);
            IDictionary<string, string> dict = retrieveEntity.Get<IDictionary<string, string>>("ActionsHistory");

            //add the new action
            dict.Add(DateTime.Now.ToString(), actionToAdd);

            // create an entity object (object) for this schema (class)
            Entity entity = new Entity(schema);

            // get the field from schema
            Field schemaField = schema.GetField("ActionsHistory");

            // set the value for entity
            entity.Set(schemaField, dict);

            // store the entity on the element
            projInfo.SetEntity(entity);

        }
    }
}

 

Can anyone give me a hand with solving it?

 

Best

7 REPLIES 7
Message 2 of 8
RPTHOMAS108
in reply to: a.bejenaru

Entity.Set takes a generic type parameter:

 

Have you tried the second of below:

 

Entity.Get<IDictionary<string, string>>(X)

Entity.Set<IDictionary<string, string>>(X, dict)

 

Where X is a string representing the name of the Field or the Field object itself (depending on overload used).

 

Also strings representing dates don't make for good unique keys in a dictionary. Depending on your culture settings you may lose granularity of the moment in time.

Message 3 of 8
RPTHOMAS108
in reply to: RPTHOMAS108

Regarding dates there are probably a number of approaches.

 

You can't store Int64 in extensible storage so you can instead:

Add an arbitrary date representing a day as invariant string in a field (say the day the storage was first created, this never changes). In your code you can convert this to an actual date with ticks and calculate the ticks offset from it as Int32() rather than whole ticks value as Int64. You can then store these Int32's as the keys in your dictionary. You then have a record that you can work out each way (i.e. you just get the base date and add the TimeSpan in ticks from Int32()). Also there will be no need to parse dates representing strings in your dictionary. Meaning you have one date in string format to parse rather than hundreds.

 

You may get collisions it depends what you do between calls to Date.Now etc. Would probably opt for more random form of key such as GUID and encode date in prefix to the string value itself. You could use a list without the need for keys that way. Dictionaries are only useful when you know something is likely to exist in the set and you know key being generated is unique. You can't easily serialise them either (manual effort required). Two lists of the same order are usually just as good.

 

Message 4 of 8
a.bejenaru
in reply to: a.bejenaru

Having unique Keys it's not my concern. 

The app should add an element to the Extensible Storage each time I run it. 

The problem is that when I look into the Extensible Storage after running it a couple of times I only have a dictionary that contains only the last added element.

 

In the attached video I'm clicking 4 times the button. We should have  a 4 element dictionary in the extensible, nevertheless, there is only one, the last one.

 

 

 

Message 5 of 8
a.bejenaru
in reply to: RPTHOMAS108

I'm already doing what you are proposing with 

            // set the value for entity
            entity.Set(schemaField, dict);

Full method

        public static void AddSchemaEntityActionsHistory(Schema schema, ProjectInfo projInfo, string actionToAdd)
        {
            // Read exisiting data and add new actions
            Entity retrieveEntity = projInfo.GetEntity(schema);
            IDictionary<string, string> dict = retrieveEntity.Get<IDictionary<string, string>>("ActionsHistory");

            //add the new action
            dict.Add(DateTime.Now.ToString(), actionToAdd);

            // create an entity object (object) for this schema (class)
            Entity entity = new Entity(schema);

            // get the field from schema
            Field schemaField = schema.GetField("ActionsHistory");

            // set the value for entity
            entity.Set(schemaField, dict);

            // store the entity on the element
            projInfo.SetEntity(entity);

        }

 

 

Message 6 of 8
RPTHOMAS108
in reply to: a.bejenaru

Should use SchemaBuilder.AddMapField for dictionary fields (they are not simple fields) (this I think you've done).

Should specify Type for generic methods such as Entity.Get and Entity.Set 

 

Entity.Set<IDictionary<string, string>>(X, dict)

 

Dictionaries require a unique key, you are required to ensure they are unique or you'll get an exception when trying to add a key value pair where key exists in dictionary. Can use .ContainsKey to determine such. e.g. if your date strings contains only the day and it is run twice in the same day you'll get an exception (probably you would be ok since Date.ToString returns to the nearest second but depends on culture info).

Message 7 of 8
peteregan
in reply to: a.bejenaru

This is in your Execute section:

 

ProjectInfo projectInfo = doc.ProjectInformation;
AddSchemaEntityLastAppliedPrototype(schema, projectInfo, "PF202102055552");
AddSchemaEntityActionsHistory(schema, projectInfo, "click"); // + new Random().ToString());

Each time the methods run, you have them find only the field involved, add a record and then overwrite the whole schema, leaving the other field blank.

 

Test by commenting out 'AddSchemaEntityLastAppliedPrototype' and running.

 

If I am right, you need to combine the methods into one: read both fields at once, add to the dictionaries as appropriate and then write them both back to schema at one time.

Message 8 of 8
RPTHOMAS108
in reply to: a.bejenaru

Also notice this:

 

// create an entity object (object) for this schema (class)
Entity entity = new Entity(schema);

 

To me it looks odd as you should be getting an entity from an existing element/field rather than creating one each time.

 

In practice you should be looking for an existing entity, if it doesn't exist then create it otherwise get the entity that exists (from field/element) and change the fields on it. The starting point is Element.GetEntity. I've not looked in detail it may be a valid for a nested workflow.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk Customer Advisory Groups


Rail Community