Monday, January 10, 2011

ArcSDE–Adding domains from code

(The images for this post were taken from the ESRI site because I no longer use the ArcDesktop applications)

In our project we are using Domains to display text instead of the codes written in our tables.

Well first what are Domains?

I always found Domains and Subtypes very similar. They are both used to bring description to a field in a feature. The difference I guess is that Subtypes also separate the features to groups whereas Domains only give text value.

According to ESRI domains are used as validators for the data inserted using ArcMap/ArcCatalog. The trouble with that is that in all my systems so far we didn’t insert the feature data by those programs – we did it all by code! And at least in 9.3.1 there were no validations on the field…

 

(from ESRI site using domain coded values)

Whereas according to ESRI subtypes allow much more. They increase performance, they create all kinds of rules and they allow the applying of domains on the fields.

How do I create domains regularly (in ArcCatalog)?

In ArcCatalog right click on the Geodatabase –> Properties –> Domains tab:

 

(Taken from the ESRI site)

On the first empty line in the top table add your domain name and description.

On the next table enter the Domain Type: Coded Values, Range

If you choose Range you will have to enter a minimum and maximum values

If you choose Coded Values you will have to enter in the bottom table the codes of the domain and their description. We use Coded Values domains.

How do I associate a field with a domain(in ArcCatalog)?

In ArcCatalog right click on a table/layer –> Properties –> Subtypes:

(Taken from the ESRI site)

On the bottom table click on the field you want to add the domain to and select the domain from the combo box.

How do I create domains by C# code?

You can read more about it here.

  1. #region Create/Delete Domains
  2.  
  3. public void CreateDomain(string name, esriFieldType type, List<KeyValuePair<object ,string >> domains)
  4. {
  5.     ICodedValueDomain codedValueDomain = new CodedValueDomainClass();
  6.  
  7.     foreach (var pair in domains)
  8.     {
  9.         codedValueDomain.AddCode(pair.Key, pair.Value);
  10.     }
  11.  
  12.     // The code to set the common properties for the new coded value domain.
  13.     var domain = (IDomain)codedValueDomain;
  14.     domain.Name = name;
  15.     domain.FieldType = type;
  16.     domain.SplitPolicy = esriSplitPolicyType.esriSPTDuplicate;
  17.     domain.MergePolicy = esriMergePolicyType.esriMPTDefaultValue;
  18.  
  19.     ((IWorkspaceDomains) _workspace).AddDomain(domain);
  20. }
  21.  
  22. public void DeleteDomain(string name)
  23. {
  24.     ((IWorkspaceDomains)_workspace).DeleteDomain(name);
  25. }
  26.  
  27. //For tests
  28. internal IDomain GetDomain(string name)
  29. {
  30.     return ((IWorkspaceDomains)_workspace).DomainByName[name];
  31. }
  32.  
  33. #endregion

The code uses an inner variable _workspace which is the current IWorkspace.

Weird things (found while unit testing):

1. You can create a domain with a name of “” or null but they will have the same name (later in my code I added a validation for the name field so that it has to have a value).

2. You can create a domain without any coded values (empty list not null list!)

3. You cannot create two domains with the same name, an exception will be thrown: COMException: Domain name already in use.

4. You cannot delete a domain with a name of “” or null – an exception is thrown that is NOT COMException - System.ArgumentException: Invalid function arguments – from the DeleteDomain method(my DB is doomed…). I manually deleted it from the database it was the only line in GDB_ITEMS where Name,PhysicalName,Path and Url were empty string (and just because its good to know – the type for domains is ‘8C368B12-A12E-4C7E-9638-C9C64E69E98F’).

How do I use a domain in a feature field by C# code?

You can read more about it here.

  1. #region Assign Domains
  2.  
  3. public void AssignDomain(string layerName, string fieldName, string domainName)
  4. {
  5.     var featureClass = GetFeatureClass(layerName);
  6.     var domain = ((IWorkspaceDomains) _workspace).DomainByName[domainName];
  7.     var field = featureClass.GetField(fieldName);
  8.  
  9.     if (field.Type != domain.FieldType)
  10.         throw new ApplicationException(
  11.             "The field and the domain are not of the same type. Cannot assign domain to field.");
  12.  
  13.     var schemaLock = (ISchemaLock)featureClass;
  14.     try
  15.     {
  16.         schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);
  17.         ((IClassSchemaEdit)featureClass).AlterDomain(fieldName, domain);
  18.  
  19.     }
  20.     finally
  21.     {
  22.         schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
  23.     }
  24. }
  25.  
  26. #endregion

Weird things (found while unit testing):

1. If you try to delete a domain that is in use it will throw a COMException: The domain is used as a default domain

2. To remove a domain simply do it with null -  AlterDomain(fieldName, null) . I added a method called RemoveDomain that does that.

In GDB_ITEMS a field in a feature class that uses domain looks like:

  1. <GPFieldInfoEx xsi:type="typens:GPFieldInfoEx">
  2.   <Name>Type</Name>
  3.   <AliasName>Type</AliasName>
  4.   <ModelName>Type</ModelName>
  5.   <DomainName>DefaultDomain</DomainName>
  6.   <FieldType>esriFieldTypeSmallInteger</FieldType>
  7.   <IsNullable>false</IsNullable>
  8. </GPFieldInfoEx>

And a field in a feature class that doesn’t use domain looks like:

  1. <GPFieldInfoEx xsi:type="typens:GPFieldInfoEx">
  2.   <Name>Type</Name>
  3.   <AliasName>Type</AliasName>
  4.   <ModelName>Type</ModelName>
  5.   <FieldType>esriFieldTypeSmallInteger</FieldType>
  6.   <IsNullable>false</IsNullable>
  7. </GPFieldInfoEx>

 

The end result was this:

  1. #region Create/Delete Domains
  2.  
  3. /// <summary>
  4. /// Create a domain in the DB
  5. /// </summary>
  6. /// <param name="name"></param>
  7. /// <param name="type"></param>
  8. /// <param name="domains"></param>
  9. public void CreateDomain(string name, esriFieldType type, List<KeyValuePair<object ,string >> domains)
  10. {
  11.     if (String.IsNullOrEmpty(name))
  12.         throw new ArgumentException("Parameter name cannot be null or empty.", "name");
  13.  
  14.     ICodedValueDomain codedValueDomain = new CodedValueDomainClass();
  15.  
  16.     foreach (var pair in domains)
  17.     {
  18.         codedValueDomain.AddCode(pair.Key, pair.Value);
  19.     }
  20.  
  21.     // The code to set the common properties for the new coded value domain.
  22.     var domain = (IDomain)codedValueDomain;
  23.     domain.Name = name;
  24.     domain.FieldType = type;
  25.     domain.SplitPolicy = esriSplitPolicyType.esriSPTDuplicate;
  26.     domain.MergePolicy = esriMergePolicyType.esriMPTDefaultValue;
  27.  
  28.     ((IWorkspaceDomains) _workspace).AddDomain(domain);
  29. }
  30.  
  31.  
  32. /// <summary>
  33. /// Delete a domain from the DB
  34. /// </summary>
  35. /// <param name="name"></param>
  36. public void DeleteDomain(string name)
  37. {
  38.     if (String.IsNullOrEmpty(name))
  39.         throw new ArgumentException("Parameter name cannot be null or empty.", "name");
  40.  
  41.     ((IWorkspaceDomains)_workspace).DeleteDomain(name);
  42. }
  43.  
  44. //For tests
  45. internal IDomain GetDomain(string name)
  46. {
  47.     return ((IWorkspaceDomains)_workspace).DomainByName[name];
  48. }
  49.  
  50. #endregion
  51.  
  52. #region Assign Domains
  53.  
  54. /// <summary>
  55. /// Assign the domain to the field in layerName
  56. /// </summary>
  57. /// <param name="layerName"></param>
  58. /// <param name="fieldName"></param>
  59. /// <param name="domainName"></param>
  60. public void AssignDomain(string layerName, string fieldName, string domainName)
  61. {
  62.     if (String.IsNullOrEmpty(domainName))
  63.         throw new ArgumentException("Parameter name cannot be null or empty.", domainName);
  64.  
  65.     var domain = ((IWorkspaceDomains)_workspace).DomainByName[domainName];
  66.     AssignDomain(layerName, fieldName, domain);
  67. }
  68.  
  69. /// <summary>
  70. /// Remove the domain usage from the field
  71. /// </summary>
  72. /// <param name="layerName"></param>
  73. /// <param name="fieldName"></param>
  74. public void RemoveDomain(string layerName, string fieldName)
  75. {
  76.     AssignDomain(layerName, fieldName, (IDomain)null);
  77. }
  78.  
  79. private void AssignDomain(string layerName, string fieldName, IDomain domain)
  80. {
  81.     if (String.IsNullOrEmpty(layerName) || String.IsNullOrEmpty(fieldName))
  82.         throw new ArgumentException("Parameter name cannot be null or empty.");
  83.  
  84.     var featureClass = GetFeatureClass(layerName);
  85.  
  86.     if(domain != null)
  87.     {
  88.         var field = featureClass.GetField(fieldName);
  89.  
  90.         if (field.Type != domain.FieldType)
  91.             throw new ApplicationException(
  92.                 "The field and the domain are not of the same type. Cannot assign domain to field.");
  93.     }
  94.  
  95.     var schemaLock = (ISchemaLock)featureClass;
  96.     try
  97.     {
  98.         schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);
  99.         ((IClassSchemaEdit)featureClass).AlterDomain(fieldName, domain);
  100.  
  101.     }
  102.     finally
  103.     {
  104.         schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
  105.     }
  106. }
  107.  
  108. //For Tests
  109. internal IField GetLayerField(string layerName, string fieldName)
  110. {
  111.     return GetFeatureClass(layerName).GetField(fieldName);
  112. }
  113. #endregion

//TODO: post this only after writing about extension methods and WorkspaceUtils

//TODO: Refactor the code…

Resources:

ESRI: A quick tour of attribute domains

ESRI: A quick tour of subtypes

ESRI: About maintaining attribute integrity while editing (using domains)

ESRI: Exercise 3: Creating subtypes and attribute domains

ESRI: Creating and modifying domains

ESRI: Assigning domains to fields

 

Keywords: ESRI, ArcSDE, SDE, domain, subtype, ArcCatalog, ArcMap, ArcDesktop,IWorkspace, C#, Add, Delete, ArcObjects