18

I have an Esri file geodatabase with attribute domains defined. I need to delete some of the attribute domains but can't because "The domain is used by an attribute rule.". How might I discover which feature class(es) are using the domains?

Executing: DeleteDomain R:\v5\YT_Canvec.gdb Permanency
Start Time: Thu May 19 11:01:02 2011
ERROR 999999: Error executing function.
The domain is used by an attribute rule.
Failed to execute (DeleteDomain).
Failed at Thu May 19 11:01:02 2011 (Elapsed Time: 0.00 seconds)

There are over a hundred feature classes in the geodatabase, interactively looking at the FC field properties for each one is a non-starter. The gdb is too large to convert to a personal gdb and go in the back door with ms-access (a dodgy method anyway).


(2011-May-26): Another way to phrase this is "which Feature Class is using domain X?"

  • Are you using subtyped domains? – Kirk Kuykendall May 19 '11 at 19:43
  • @kirk, yes there is a subtype, but the domains I'm trying to remove are not using the subtype – matt wilkie May 19 '11 at 21:49
  • 1
    In that case I think Brian's code would work. – Kirk Kuykendall May 19 '11 at 21:52
  • 1
    @kirk, correction: I didn't think I was using subtypes+domains, but after much mucking about and opening a tech support case, it turns out I actually was using one after all. It was veritable click-fest to identify the particular remaining cuplrit. I should have invested more time in following up on your c# method! – matt wilkie May 26 '11 at 22:51
3

To answer the question of handling feature classes with subtypes, it is possible with arcpy (10.1+).

arcpy.env.workspace = your_gdb

for FC in arcpy.ListFeatureClasses():
    for stcode, stdict in list(arcpy.da.ListSubtypes(FC).items()):
        for stkey in list(stdict.keys()):
            if stkey == 'FieldValues':
                for field, fieldvals in list(stdict[stkey].items()):
                    if fieldvals[1] is not None:
                        print(
                            "{},{},{},{}".format(FC,
                                                 'None' if stcode == 0 else stdict['Name'],
                                                 field,
                                                 fieldvals[1].name))

The subtype code, stcode, will be zero if there are no subtypes, so the code prints out 'None'.

The subtypes dictionary has more to it, so inspect it in code.

21
+50

Python has methods for listing feature classes in a geodatabase, looping through each feature class in the list, listing fields in each feature class, and showing the domain of each field.

import arcpy

#Set workspace environment to geodatabase
arcpy.env.workspace = your_gdb

#Get list of feature classes in geodatabase
FCs = arcpy.ListFeatureClasses()

#Loop through feature classes in list
for FC in FCs:

    #List fields in feature class
    fields = arcpy.ListFields(FC)

    #Loop through fields
    for field in fields:

        #Check if field has domain
        if field.domain != "":

            #Print feature class, field, domain name
            print FC, field.name, field.domain

The above code should work in ArcGIS 10 and it will print a list right in the python interpreter window. You can then copy and paste the list into a text editor or Excel to review the results more easily.

  • Will this handle subtyped domains too? – Kirk Kuykendall May 19 '11 at 19:43
  • I'm not sure if this will handle subtypes or subtyped domains. I have never used subtypes before. If there is a domain assigned to a particular field, the domain name will be printed. – Brian May 19 '11 at 20:10
  • beautiful, thanks Brian. Initially it didn't work for me, but eventually I remembered that listFC doesn't recurse into FeatureDatasets without some extra help (gis.stackexchange.com/questions/5893/…). All good now! :) – matt wilkie May 19 '11 at 21:51
  • @Kirk, no it doesn't see subtypes using domains. – matt wilkie May 26 '11 at 22:48
  • Follow the example resources.arcgis.com/en/help/main/10.1/index.html#//… to go through all subtypes and their associated domains. – Michael Stimson Jul 15 '15 at 2:11
8

Since I don't think python handles subtypes, I'm posting this c# code that should. I tested it with Esri's sample water/wastewater geodb and found the following unused domains:

HistoryType is not used
PLSSFirstDivisionType is not used
PLSSDirection is not used
PLSSPrincipalMeridian is not used
ParcelType is not used
PLSSSpecialSurveyType is not used
CartoLineType is not used
PLSSSecondDivisionType is not used

Often DBA's get annoyed that domains - which are essentially lookup tables - cannot be accessed via SQL.

This code tested from arcmap (updated per Matt's comment):

protected override void OnClick()
{
    string fgdbPath = @"C:\projects\NetTools\InfrastructureEditingTemplate\MapsandGeodatabase\LocalGovernment.gdb";
    var dict = SummarizeDomains(fgdbPath);
    ListDomains(dict);
    // list what featureclasses use a particular domain ...
    string domName = "State_Bnd_Rules";
    if (dict.ContainsKey(domName))
    {
        if (dict[domName].Count > 0)
        {
            Debug.Print("{0} is used by these featureclasses: ", domName);
            foreach (string fcfldName in dict[domName])
            {
                Debug.Print("\t{0}", fcfldName);
            }
        }
        else
            Debug.Print("{0} is not used by any featureclasses", domName);
    }
    else
    {
        Debug.Print("Domain name not found in geodb: {0}", domName);
    }
}

private void ListDomains(Dictionary<string,List<string>> dict)
{
    foreach (KeyValuePair<string, List<string>> kvp in dict)
    {
        Debug.Print("Domain {0}",kvp.Key);
        if (kvp.Value.Count > 0)
        {
            foreach (string fcfldName in kvp.Value)
            {
                Debug.Print("\t{0}", fcfldName);
            }
        }
        else
            Debug.Print("\tUNUSED DOMAIN!");
    }
}

private Dictionary<string, List<string>> SummarizeDomains(string fgdPath)
{
    var ws = Open(fgdPath);
    var dict = InitDict(ws);

    var enumDs1 = ws.get_Datasets(esriDatasetType.esriDTAny);
    IDataset ds;
    while ((ds = enumDs1.Next()) != null)
    {
        Debug.Print("processing {0}", ds.Name);
        if (ds is IObjectClass)
            LoadDomains((IObjectClass)ds, dict);
        else if (ds is IFeatureDataset)
        {
            var enumDs2 = ds.Subsets;
            enumDs2.Reset();
            IDataset ds2;
            while ((ds2 = enumDs2.Next()) != null)
            {
                if (ds2 is IObjectClass)
                    LoadDomains((IObjectClass)ds2, dict);
            }
        }
    }
    return dict;
}
private void LoadDomains(IObjectClass oc, Dictionary<string, List<string>> dict)
{
    if (oc is ISubtypes && ((ISubtypes)oc).HasSubtype)
        LoadSubtypeDomains(oc, dict);
    else
    {
        for (int i = 0; i < oc.Fields.FieldCount; i++)
        {
            var fld = oc.Fields.get_Field(i);
            if (fld.Domain == null)
                continue;
            if (dict.ContainsKey(fld.Domain.Name))
                dict[fld.Domain.Name].Add(String.Format("{0}.{1}",((IDataset)oc).Name,fld.Name));
            else
                throw new Exception("domain not found: " + fld.Domain.Name);
        }
    }
}
private void LoadSubtypeDomains(IObjectClass oc, Dictionary<string, List<string>> dict)
{
    ISubtypes subTypes = oc as ISubtypes;
    var enumSubtypes = subTypes.Subtypes;
    enumSubtypes.Reset();
    int code;
    string stName;
    while ((stName = enumSubtypes.Next(out code)) != null)
    {
        for (int i = 0; i < oc.Fields.FieldCount; i++)
        {
            string fldName = oc.Fields.get_Field(i).Name;
            var domain = subTypes.get_Domain(code, fldName);
            if (domain != null)
            {
                if (dict.ContainsKey(domain.Name))
                    dict[domain.Name].Add(String.Format("{0}.{1}.{2}",stName,((IDataset)oc).Name,fldName));
                else
                    throw new Exception("domain not found: " + domain.Name);
            }
        }
    }
}
private Dictionary<string, List<string>> InitDict(IWorkspace ws)
{
    var dict = new Dictionary<string, List<string>>(StringComparer.InvariantCultureIgnoreCase);
    var enumDomain = ((IWorkspaceDomains)ws).Domains;
    enumDomain.Reset();
    IDomain d = null;
    while ((d = enumDomain.Next()) != null)
        dict.Add(d.Name, new List<string>());
    return dict;
}

private IWorkspace Open(string fgdbPath)
{
    Type t = Type.GetTypeFromProgID("esriDataSourcesGDB.FileGDBWorkspaceFactory");
    var wsf = Activator.CreateInstance(t) as IWorkspaceFactory;
    return wsf.OpenFromFile(fgdbPath, 0);
}
  • while listing unused domains is useful, this is the inverse of the problem to be solved. I was actually looking for "which FC is using domain X?" (so I may remove the link, and make the domain an unused domain). ((I still haven't tried the code, I'm just going on the name of the function)) – matt wilkie May 26 '11 at 22:55
  • @matt oh, yeah, that make sense. I've changed the code to show how to do that. – Kirk Kuykendall May 27 '11 at 13:52
  • uhh, maybe this should be a full fledged question, but, where do I put this code? I can't locate the v10 equivalent of the VBA editor (Tools->Macros->Visual Basic Editor). – matt wilkie May 31 '11 at 20:51
  • You'll need to install Visual Studio Express (free) or greater, and the ArcGIS SDK. Once you've done that, you should be able to follow this walkthrough for creating a command button, then copy and paste my code into the Click event. You'll also need to add appropriate references to the project. – Kirk Kuykendall May 31 '11 at 21:26
5

This code should return what is being asked. It will succinctly traverse all feature classes and tables in a workspace GDB/FS and return all fields associated with a domain, the field name, and the feature class/table it belongs in.

import os
import arcpy
lst=[]
for dirpath,dirnames,files in arcpy.da.Walk( # the path to your workspace
, datatype=["FeatureClass","Table"]):
     for file in files:
         lst.append(os.path.join(dirpath,file))
for i in lst:
     for fld in arcpy.ListFields(i):
         if fld.domain != "":
             print os.path.basename(i),fld.name, fld.domain 
4

Unfortunately Brian's answer, which is a direct and usable answer the question asked, does not solve my actual problem. I presume because of a bug in the gdb at hand (even though none of the feature classes have domains attached, there is still one I'm not allowed to delete). In any case I found another method for determining which fc's have associated domains. It's interactive, but much faster than going through each field property on every single fc:

Drag and drop bunches of fc's from problem gdb to another gdb and inspect the Data Transfer dialog. Linked attribute domains, if any, will be at the bottom of the list. Repeat in smaller and smaller bunches until you narrow down which @$%## fc is giving you a hard time.

finally narrowed down to 2 FCs linked to a CV domain

  • Curiously, even though drag-n-drop says HD_148009_2 is linked to the CV Domain Permanency, Brian's arcpy script reports no linked domain, and neither does the Feature Class Properties fields inspector in ArcCatalog. However now I've finally narrowed it down enough to log a bug report with Esri tech support. – matt wilkie May 19 '11 at 22:59
3

This is what I imagine Matt Wilkie had to look up and write to augment Brian's code. I had to get all domains for tables, feature classes in the root directory of a database, and features in all feature datasets. I exported the information as a csv to allow some other workers to clean up our geodatabase environments of old domains.

def domainInfo(csvExportFolder):
    import arcpy,csv,os

    fcTabList = []
    list = []

    #Set workspace environment to geodatabase
    arcpy.env.workspace = r"H:\GIS\SDEConnections\Admin\Infrastructure.sde"

    #Prepping the csv
    csvFile = csv.writer(open(csvExportFolder+"\\"+ "Infrastructure Domains" + ".csv","wb"),delimiter = "|")
    csvFile.writerow(["FeatureDataSet","FeatureClass","FieldName","Domain"])

    #Get list of all features in geodatabase
    fdsList = arcpy.ListDatasets()
    fcs = arcpy.ListFeatureClasses()
    tbs = arcpy.ListTables()

    for fds in fdsList:
        fcs = arcpy.ListFeatureClasses("","",fds)
        if len(fcs) != 0:
            for fc in fcs:
                fcTabList.append([fds,fc])

    for fc in fcs:
        fcTabList.append([None,fc])

    for tb in tbs:
        fcTabList.append([None,tb])

    # Loop through all features in the database list
    for item in fcTabList:
        fds = item[0]
        fc = item[1]
        # List fields in feature class
        fields = arcpy.ListFields(fc)

        # Loop through fields
        for field in fields:

            # Check if field has domain
            if field.domain != "":

                # Print feature class, field, domain name
                csvFile.writerow([fds,fc,field.name,field.domain])

def main():
    csvExportFolder = r"H:\GIS"
    domainInfo(csvExportFolder)

if __name__ == "__main__":
    main()
0

Esri: FAQ: How can I find all the places where domains are referenced in my geodatabase?. "Python functions that can list the properties of these structures in a geodatabase. Among the properties are the referenced domains. A sample script and file geodatabase are provided that demonstrate how Python functions might be used to list the domains and other properties of feature classes and tables. Domains can be associated with fields in a feature class or table; they can additionally be set for fields categorized by a subtype."

The results are noisy for this question, going beyond what domains are in use, but is wider platform for getting started.

Executing: ParseDomainReferences [...]

fc at root level: Pt1
  fld OBJECTID
  fld SHAPE
  fld Field_Text, domain [Pets]
  fld Field_Long
  fld Field_Short, domain [Counts]
  fld Field_Double, domain [Ratios]
[...]
Subtype Code: 1
subCode: ('Default', False)
subCode: ('Name', u'One')
subCode: ('SubtypeField', u'Field_Long')
FieldValues
fldName: Field_Double, default: [no default], domain: Ratios
fldName: OBJECTID, default: [no default], domain: [no domain]
fldName: Field_Long, default: [no default], domain: [no domain]
fldName: Field_Short, default: 1, domain: Counts
fldName: SHAPE, default: [no default], domain: [no domain]
fldName: Field_Text, default: N, domain: [no domain]
[...etc]

Code excerpt, edited for brevity:

def ParseFieldList (fc, fcPath):
...
      for fld in fldList:
        if fld.domain != None:
          if fld.domain != "":
...
        arcpy.AddMessage ("  fld " + fld.name + s)

      # get subtype list
      subDict = arcpy.da.ListSubtypes (fcPath)
      if len (subDict) > 0:
        for stCode in subDict.iteritems():
...
          valkey, vallist = stCode
          arcpy.AddMessage ("Subtype Code: {0}".format(valkey))
          i = 0
          for subCode in vallist.iteritems():
            i += 1
            if i < 4:
              arcpy.AddMessage ("subCode: {0}".format(subCode))
            else:
              fldkey, fldlist = subCode
              arcpy.AddMessage (fldkey)
              for fld in fldlist.iteritems():
...
                if dom != None:
                  s2 = dom.name
                arcpy.AddMessage ("fldName: " + fldName + ", default: " + s1 + ", domain: " + s2)
...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.