[GIS] Updating MXD file using ArcObjects

arcgis-9.3arcmaparcobjectsnet

I'm using ArcGIS 9.3.1. product line and MXD files.

At our customer we have multiple environment (DEV, QAS, PROD) which should be more or less identical except they use different datbases and a few other minor tweaks.
They alo share the same MXD file published as Map Services.
These MXD should be the same, save for the fact their layer point to different data source (database) but with the same name & fields.

Now, to update map services we basically manually edit each MXD on all platform manually.
This manual process is error prone and has caused trouble in the past.
I'd like to edit the MXD document once and simply copy them on other platform.
But then I'll need to pass them through some home made made custom tool to edit the data source.

I did write such a tool, it read the MXD document, find the used SDF file (connection file) and update the MXD's connetcion properties with the value from the local version of the SDE files.

It works well but for 1 problem.
When I change a layer's data source, its fields loose their aliases and visibility flags, which are reset to the default.

I'd like to read the original value before update, update and then set the new values (to the old value)(for field's alias and visibility)

I try to cast my layers to ITableField, but I got FieldCount = 0!!! So I can't update anything!!!

Below is my current code (UpdateSDEWorkspaces is the method of interest)
How could I set up the layer alias and visibility?

public static class MxdUtils
{
    public static IEnumerable<IMap> GetMaps(this IMapDocument pMapDocument)
    {
        int N = pMapDocument.MapCount;
        for (int i = 0; i < N; i++)
            yield return pMapDocument.get_Map(i);
    }
    public static IEnumerable<ILayer> GetLayers(this IMap pMap, UID uid, bool recursive)
    {
        IEnumLayer pEnumLayer = pMap.get_Layers(uid, recursive);
        pEnumLayer.Reset();
        for (ILayer pLayer = pEnumLayer.Next(); pLayer != null; pLayer = pEnumLayer.Next())
            yield return pLayer;
    }
    public static IEnumerable<ILayer> GetLayers(this IMapDocument pMapDocument, UID uid, bool recursive)
    {
        return
            from m in pMapDocument.GetMaps()
            from l in m.GetLayers(uid, recursive)
            select l;
    }

    /// <summary>
    /// Will update each SDE feature layer to the local sde connection.
    /// It will will loop through all layer, find a layer's sde connection, if the local find can be found, 
    /// connection properties will be updated from it.
    /// </summary>
    public static void UpdateSDEWorkspaces(string mxdPath) { UpdateSDEWorkspaces(mxdPath, null); }
    public static void UpdateSDEWorkspaces(string mxdPath, Dictionary<string, IPropertySet> memory)
    {
        if (memory == null)
            memory = new Dictionary<string, IPropertySet>();

        IMapDocument pMapDocument = new MapDocumentClass();
        pMapDocument.Open(mxdPath, "");

        foreach (var pMap in pMapDocument.GetMaps())
        {
            var sdelayers =
                from pLayer in pMap.GetLayers(new UID { Value = "{40A9E885-5533-11d0-98BE-00805F7CED21}" }, true)
                let pFeatureLayer = pLayer as IFeatureLayer
                where pFeatureLayer != null
                let pDataLayer = pFeatureLayer as IDataLayer2
                let pDatasetName = pDataLayer.DataSourceName as IDatasetName
                where pDatasetName != null
                let pWorkspaceName = pDatasetName.WorkspaceName
                let pNewProp = GetConnectionProperties(memory, pWorkspaceName.PathName, mxdPath)
                where pNewProp != null
                select new
                {
                    Layer = pLayer,
                    DataLayer = pDataLayer,
                    DSName = pDatasetName,
                    WorkspaceName = pWorkspaceName,
                    UpdatedProperties = pNewProp,
                };

            foreach (var item in sdelayers)
            {
                var tf = (ITableFields)item.Layer;
                int N = tf.FieldCount;
                // damn, why is N always 0?

                item.WorkspaceName.ConnectionProperties = item.UpdatedProperties;
            }

            pMapDocument.ReplaceContents(pMap as IMxdContents);
        }
        pMapDocument.Save(true, false);
        pMapDocument.Close();
    }

    public static IPropertySet GetConnectionProperties(Dictionary<string, IPropertySet> memory, string sdeConnPath, string mxdPath)
    {
        var path = sdeConnPath;
        if (!File.Exists(path))
            path = Path.Combine(Path.GetDirectoryName(mxdPath), Path.GetFileName(sdeConnPath));
        if (!File.Exists(path))
            path = Path.Combine(Environment.CurrentDirectory, Path.GetFileName(sdeConnPath));
        if (!File.Exists(path))
            return null;

        IPropertySet set;
        if (memory.TryGetValue(sdeConnPath, out set))
            return set;

        Console.WriteLine("Reading " + path);
        set = GetSDEFactory().ReadConnectionPropertiesFromFile(path);
        if (set == null)
            return null;

        memory[sdeConnPath] = set;
        memory[path] = set;
        return set;
    }

    static SdeWorkspaceFactoryClass GetSDEFactory()
    {
        if(SDEDataSourceFactory == null)
            SDEDataSourceFactory = new SdeWorkspaceFactoryClass();
        return SDEDataSourceFactory;
    }
    static SdeWorkspaceFactoryClass SDEDataSourceFactory;
}

Best Answer

My guess is that when you open the mxd document it doesn't manage to connect to the datasource.

I did something similar a couple of years ago. Guess I never bothered about aliases though. I just tested it and the aliases are available in ITableFields.FieldInfo, but I never copied them to the new location.

One other thing that you also might lose when you change source is joined tables. I know I had to write some special for that.

Do you really need MXD based services? I've changed to MSD and the datasource replacement got a lot easier. Just unzip the MSD (it a bunch of zipped xml files) and do some string replacement. There is an IMSDHelper class to do some stuff, but it didn't all for me. Passwords are stored encrypted in the MSD as encryped_password tag. I replaced that tag with a password tag and stored them as plaintext. This is of course not supported by ESRI, but it works for me.

Related Question