Hi everyone,
I have made three generic methods for extensible storage, a get and two set methods (one for arrays and simple field and one for maps).
Problem however is that I get an error when getting the storage:
Autodesk.Revit.Exceptions.ArgumentException: 'The Field belongs to a different Schema from this Entity, or this Entity is invalid.
Parameter name: field'
Can somebody help me with making these methods work. Because in the long run it will save a lot of time if methodes can be used for different types.
This is the code I have so far:
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
using(var tx = new Transaction(_doc, "Test"))
{
tx.Start();
var e = _doc.GetElement(id);
var elementInfo = new Dictionary<string, string>
{
{ "Test1", "Hello World!" },
{ "Test2", "Hello World!" },
{ "Test3", "Hello World!" }
};
var extensibleElementInfo = GetStorageData<Dictionary<string,string>>(e, _elementInfo, _guidElementInfo);
SetStorageData<string, string>(e, elementInfo, _elementInfo, _guidElementInfo);
tx.Commit();
}
return Result.Succeeded;
}
private static T GetStorageData<T>(Element e, string fieldName, Guid guid)
{
var schema = GetSchema(guid);
if(schema != null)
{
//try
//{
var entity = e.GetEntity(schema);
return entity.Get<T>(schema.GetField(fieldName));
//}
//catch { }
}
return default(T);
}
private void SetStorageData<T>(Element e, T data, string schemaName, Guid guid)
{
var schema = GetSchema(guid);
Entity entity = null;
if(schema == null)
{
var schemaBuilder = new SchemaBuilder(guid);
schemaBuilder.SetReadAccessLevel(AccessLevel.Public);
schemaBuilder.SetWriteAccessLevel(AccessLevel.Vendor);
schemaBuilder.SetVendorId("BimBuildings");
schemaBuilder.SetSchemaName(schemaName);
try
{
if (typeof(T) != typeof(string) && typeof(IEnumerable).IsAssignableFrom(typeof(T)))
schemaBuilder.AddArrayField(schemaName, typeof(T));
else
schemaBuilder.AddSimpleField(schemaName, typeof(T));
}
catch (Autodesk.Revit.Exceptions.ArgumentException)
{
Message.Display("The type provided is not storable.", WindowType.Error);
}
schema = schemaBuilder.Finish();
entity = new Entity(schema);
}
else
{
entity = e.GetEntity(schema);
}
var fieldSet = schema.GetField(schemaName);
entity.Set(fieldSet, data);
}
private void SetStorageData<TKey, TValue>(Element e, IDictionary<TKey, TValue> data, string schemaName, Guid guid)
{
var schema = GetSchema(guid);
Entity entity = null;
if (schema == null)
{
var schemaBuilder = new SchemaBuilder(guid);
schemaBuilder.SetReadAccessLevel(AccessLevel.Public);
schemaBuilder.SetWriteAccessLevel(AccessLevel.Vendor);
schemaBuilder.SetVendorId("BimBuildings");
schemaBuilder.SetSchemaName(schemaName);
schemaBuilder.AddMapField(schemaName, typeof(TKey), typeof(TValue));
schema = schemaBuilder.Finish();
entity = new Entity(schema);
}
else
{
entity = e.GetEntity(schema);
}
var fieldSet = schema.GetField(schemaName);
entity.Set(fieldSet, data);
}
Solved! Go to Solution.
Solved by RPTHOMAS108. Go to Solution.
Hi @jens.slofstra! ,
I haven't gone through the code in depth, but I would suggest using
SetWriteAccessLevel
as public while you are debugging.
Which line caused the exception?
Hi @matthew_taylor,
I will set the SetWriteAccessLevel to Public.
This line in the Get method:
return entity.Get<T>(schema.GetField(fieldName));
If you have schema defined with a field structure that field structure can't change later including the types used in such (is this what you are aiming for)? From my initial view the code looks more or less right but perhaps how it is being used is wrong?
Once the schema is set i.e. has an id, has a name, has documentation, has field structure then that is it. It is like a class structure that you can't change so if you have a number of fields that can't be changed later (including their types). I found early on that even if you change the documentation it causes problems.
I actually went through a similar thought process to you and created a method that could take a class with values and convert it to a schema (seemed to work quite well and could even incorporate nesting). I had custom attributes defining the schema GUID, name, at class level and units for field of double at field level. I even auto generated all the permutations of type combinations that could be used as a key value pair in a dictionary. In the end I never used any of it through this thought that one day I may add an extra member to a class and inadvertently create a schema with the same id but different definition. Then ForgeTypeIds came about and I had to convert it all to those.
My other concern was always the temptation to create deeply nested structures and wondering exactly what the limits of extensible storage was in that respect.
I am not trying to change the type of the extensible storage, just making a method I can call in multiple external commands so I don't need to type the same get and set method but with different outputs (I am just a bit lazy 😉).
After my question here I have been researching some more and deconstructed my code.
But even after I split the Get and Set of external storage into two external commands I get the same error.
Autodesk.Revit.Exceptions.ArgumentException: 'The Field belongs to a different Schema from this Entity, or this Entity is invalid.
Parameter name: field'
This error occurs after
entity.Set<string>(fieldSet, data);
I made a new project and simplified my code but still the same error.
Below the two external commands:
namespace Testing
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class SetStorage : IExternalCommand
{
private Document _doc;
private UIDocument _uidoc;
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
#region//Utils
// Application context.
_uidoc = commandData.Application.ActiveUIDocument;
_doc = _uidoc.Document;
#endregion
using(var tx = new Transaction(_doc, "Test"))
{
tx.Start();
var reference = _uidoc.Selection.PickObject(ObjectType.Element, new SelectionFilterByCategory("Walls"), "Select an element");
var element = _doc.GetElement(reference);
var guid = new Guid("01b7cd09-2e3b-4ecc-aa8c-d3b5b4e407c0");
SetStorageData(element, "Hello World!", "Test", guid);
tx.Commit();
}
return Result.Succeeded;
}
private static void SetStorageData(Element e, string data, string schemaName, Guid guid)
{
var schema = GetSchema(guid);
Entity entity = null;
if (schema == null)
{
var schemaBuilder = new SchemaBuilder(guid);
schemaBuilder.SetReadAccessLevel(AccessLevel.Public);
schemaBuilder.SetWriteAccessLevel(AccessLevel.Public);
schemaBuilder.SetVendorId("Company");
schemaBuilder.SetSchemaName(schemaName);
schemaBuilder.AddSimpleField(schemaName, typeof(string));
schema = schemaBuilder.Finish();
entity = new Entity(schema);
}
else
{
entity = e.GetEntity(schema);
}
var fieldSet = schema.GetField(schemaName);
entity.Set<string>(fieldSet, data);
}
private static Schema GetSchema(Guid guid)
{
var schema = Schema.Lookup(guid);
if (schema != null)
return schema;
return null;
}
}
}
namespace Testing
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class GetStorage : IExternalCommand
{
private Document _doc;
private UIDocument _uidoc;
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
#region//Utils
// Application context.
_uidoc = commandData.Application.ActiveUIDocument;
_doc = _uidoc.Document;
#endregion
using (var tx = new Transaction(_doc, "Test"))
{
tx.Start();
var reference = _uidoc.Selection.PickObject(ObjectType.Element, new SelectionFilterByCategory("Walls"), "Select an element");
var element = _doc.GetElement(reference);
var guid = new Guid("01b7cd09-2e3b-4ecc-aa8c-d3b5b4e407c0");
TaskDialog.Show("Info", GetStorageData(element, "Test", guid));
tx.Commit();
}
return Result.Succeeded;
}
private static string GetStorageData(Element e, string fieldName, Guid guid)
{
var schema = GetSchema(guid);
if (schema != null)
{
var entity = e.GetEntity(schema);
return entity.Get<string>(schema.GetField(fieldName));
}
return null;
}
private static Schema GetSchema(Guid guid)
{
var schema = Schema.Lookup(guid);
if (schema != null)
return schema;
return null;
}
}
}
Ok I don't see a call to Element.SetEntity so nothing is being stored on the element to get or set in terms of fields. In itself I'm not sure it would lead to the issue stated because it would just create a new one each time. If you've tested multiple times in same session it may be the issue (in terms of the lifecycle of it). The logic would be as follows:
if schema exists in memory from previous run then:
var schema = GetSchema(guid);
will return it however since it has never been attached to the element
entity = e.GetEntity(schema)
will return an invalid entity.
Then you try 'entity.Set<string>(fieldSet, data)' on that invalid entity.
So the very first session run should do nothing but further runs would throw the exception I guess.
Would also avoid using the schema name for the field also.
Failing all that I would go back to the example under SchemaBuilder in the ReviAPI.chm. Remembering also that schemas persist in memory outside the document.
Can't find what you're looking for? Ask the community or share your knowledge.