天天看點

使用程式生成要部署的實體和目标環境實體的差别

我是微軟Dynamics 365 & Power Platform方面的工程師/顧問羅勇,也是2015年7月到2018年6月連續三年Dynamics CRM/Business Solutions方面的微軟最有價值專家(Microsoft MVP),歡迎關注我的微信公衆号 MSFTDynamics365erLuoYong ,回複466或者20220313可友善擷取本文,同時可以在第一間得到我釋出的最新博文資訊,follow me!

我們釋出解決方案到目标環境,希望釋出的内容盡量精準,釋出過去的元件盡量隻是需要釋出的,不要帶其他不需要釋出的元件,如果要檢查呢?一個個手工比較太Low。我目前沒有找到很好的辦法,根據項目的實踐,一般容易造成問題的主要是實體以及它的元件,我就準備了一個程式,讀取導出解決方案的 customizations.xml 内容和要導入的目标環境的實體中繼資料進行對比,然後将對比結果生成Excel友善分析和比較。

關于查詢實體中繼資料可以參考我前面的博文:

  • ​​Dynamics CRM使用中繼資料之一:查詢實體的主字段(托管代碼版本) ​​
  • ​​Dynamics CRM 2015/2016新特性之二十七:使用Web API查詢中繼資料​​
  • ​​Dynamics 365使用JavaScript調用Web API批量設定字段的稽核屬性為禁用​​

程式使用的主要代碼如下:

using Microsoft.Xrm.Tooling.Connector;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.Office.Interop.Excel;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using System.IO;
using Microsoft.Xrm.Sdk.Query;

namespace CompareComponents
{
    class Program
    {
        public static string[] excludeColumns = "ownerid,owningbusinessunit,owningteam,owninguser,statecode,modifiedby,modifiedbyexternalparty,modifiedon,modifiedonbehalfby,overriddencreatedon,createdby,createdbyexternalparty,createdon,createdonbehalfby,entityimage".Split(',');
        static void Main(string[] args)
        {
            Console.OutputEncoding = Encoding.GetEncoding("gb2312");
            Console.WriteLine("本程式用于讀取解決方案中的customizations.xml中内容和要布署的目标環境元件進行對比!,開始使用請輸入Y!");
            var inputValue = Console.ReadLine();
            if (inputValue.Equals("Y", StringComparison.OrdinalIgnoreCase))
            {
                Console.WriteLine("開始讀取customizations.xml檔案中内容!");
                var customizationsFilePath = ConfigurationManager.AppSettings["CustomizationsFilePath"];
                List<TableMetadata> lsTables = new List<TableMetadata>();
                XElement componentstoDeploy = XElement.Load(customizationsFilePath);
                IEnumerable<XElement> tableEles = from item in componentstoDeploy.Descendants("Entities").FirstOrDefault().Descendants("Entity")
                                                        select item;
                foreach(var tableEle in tableEles)
                {
                    var table = new TableMetadata();
                    table.TableDispalyName = tableEle.Element("Name").Attribute("LocalizedName").Value.ToString();
                    table.TableSchemaName = tableEle.Element("Name").Value.ToString();
                    table.Columns = new List<ColumnMetadata>();
                    table.Forms = new List<FormMetadata>();
                    table.SavedQueries = new List<SavedQueryMetadata>();
                    table.Visualizations = new List<VisualizationMetadata>();

                    //處理列
                    if (tableEle.Descendants("attributes").Any())
                    {
                        var columnEles = from item in tableEle.Descendants("attributes").FirstOrDefault().Descendants("attribute")
                                         select item;
                        foreach (var columnEle in columnEles)
                        {
                            var columnLogicalName = columnEle.Attribute("PhysicalName").Value.ToString().ToLower();
                            var columnDataType = columnEle.Element("Type").Value.ToString();
                            if (!excludeColumns.Contains(columnLogicalName) && !columnDataType.Equals("primarykey", StringComparison.OrdinalIgnoreCase))
                                table.Columns.Add(new ColumnMetadata()
                                {
                                    LogicalName = columnLogicalName,
                                    DataType = ChangeColumnType(columnDataType)
                                });
                        }
                    }

                    //處理表單
                    if (tableEle.Element("FormXml") != null)
                    {
                        var formEles = from item in tableEle.Element("FormXml").Descendants("forms")
                                       select item;
                        foreach (var formEle in formEles)
                        {
                            foreach (var systemfrom in formEle.Descendants("systemform"))
                            {
                                table.Forms.Add(new FormMetadata()
                                {
                                    Name = systemfrom.Element("LocalizedNames").Descendants("LocalizedName").FirstOrDefault().Attribute("description").Value.ToString(),
                                    FormId = Guid.Parse(systemfrom.Element("formid").Value.ToString())
                                });
                            }
                        }
                    }

                    //處理公共視圖
                    if (tableEle.Element("SavedQueries") != null)
                    {
                        var savedqueryEles = from item in tableEle.Element("SavedQueries").Element("savedqueries").Descendants("savedquery")
                                             select item;
                        foreach (var savedqueryEle in savedqueryEles)
                        {
                            table.SavedQueries.Add(new SavedQueryMetadata()
                            {
                                Name = savedqueryEle.Element("LocalizedNames").Descendants("LocalizedName").FirstOrDefault().Attribute("description").Value.ToString(),
                                SavedQueryId = Guid.Parse(savedqueryEle.Element("savedqueryid").Value.ToString())
                            });
                        }
                    }

                    //處理實體公共圖表
                    if (tableEle.Element("Visualizations") != null) {
                        var visualizationEles = from item in tableEle.Element("Visualizations").Descendants("visualization")
                                                select item;
                        foreach (var visualizationEle in visualizationEles)
                        {
                            table.Visualizations.Add(new VisualizationMetadata()
                            {
                                Name = visualizationEle.Element("LocalizedNames").Descendants("LocalizedName").FirstOrDefault().Attribute("description").Value.ToString(),
                                SavedQueryVisualizationId = Guid.Parse(visualizationEle.Element("savedqueryvisualizationid").Value.ToString())
                            });
                        }
                    }

                    lsTables.Add(table);
                }
                Console.WriteLine("完成讀取customizations.xml檔案中内容!");
                var excelApp = new Application();
                excelApp.Visible = false;
                Workbook deployWorkbook = excelApp.Workbooks.Add();
                Worksheet deployWorksheet = (Worksheet)excelApp.ActiveSheet;
                deployWorksheet.Name = "布署資訊";
                int row = 1;
                deployWorksheet.Cells[1, 1] = "表名";
                deployWorksheet.Cells[1, 2] = "表架構名";
                deployWorksheet.Cells[1, 3] = "元件類型";
                deployWorksheet.Cells[1, 4] = "元件名";
                deployWorksheet.Cells[1, 5] = "元件ID/字段類型";
                deployWorksheet.Cells[1, 6] = "布署類型";
                deployWorksheet.Cells[1, 7] = "說明";
                deployWorksheet.Rows[1].Font.Bold = true;//字型加粗
                row++;
                try
                {
                    Console.WriteLine($"開始連接配接到指定的Dynamics 365環境");
                    CrmServiceClient crmServiceClient = new CrmServiceClient(ConfigurationManager.AppSettings["connectStr"]);
                    if (crmServiceClient.IsReady)
                    {
                        Console.WriteLine($"連接配接到Dynamics 365環境 ({crmServiceClient.ConnectedOrgFriendlyName}) 成功");
                        foreach (var table in lsTables)
                        {
                            try
                            {
                                RetrieveEntityRequest request = new RetrieveEntityRequest
                                {
                                    EntityFilters = EntityFilters.All,
                                    LogicalName = table.TableSchemaName.ToLower(),
                                    RetrieveAsIfPublished = true
                                };
                                RetrieveEntityResponse response = crmServiceClient.Execute(request) as RetrieveEntityResponse;
                                var attributes = response.EntityMetadata.Attributes;
                                //處理列
                                foreach (var column in table.Columns)
                                {
                                    deployWorksheet.Cells[row, 1] = table.TableDispalyName;
                                    deployWorksheet.Cells[row, 2] = table.TableSchemaName;
                                    deployWorksheet.Cells[row, 3] = "字段";
                                    deployWorksheet.Cells[row, 4] = column.LogicalName;
                                    deployWorksheet.Cells[row, 5] = column.DataType;
                                    var findColumn = attributes.Where(t => t.LogicalName.Equals(column.LogicalName,StringComparison.OrdinalIgnoreCase));
                                    if (findColumn.Any())
                                    {
                                        deployWorksheet.Cells[row, 6] = "修改";
                                        if (!findColumn.First().AttributeTypeName.Value.Equals(column.DataType, StringComparison.OrdinalIgnoreCase))
                                        {
                                            deployWorksheet.Cells[row, 7] = $"字段類型由原來的{findColumn.First().AttributeTypeName.Value}要變成{column.DataType}";
                                        }
                                    }
                                    else
                                    {
                                        deployWorksheet.Cells[row, 6] = "新增";
                                    }
                                    row++;
                                }

                                //處理表單
                                var systemFormsQuery = new QueryExpression("systemform");
                                systemFormsQuery.NoLock = true;
                                systemFormsQuery.ColumnSet.AddColumn("formid");
                                systemFormsQuery.Criteria.AddCondition("objecttypecode",ConditionOperator.Equal,table.TableSchemaName.ToLower());
                                var systemForms = crmServiceClient.RetrieveMultiple(systemFormsQuery);
                                foreach (var form in table.Forms)
                                {
                                    deployWorksheet.Cells[row, 1] = table.TableDispalyName;
                                    deployWorksheet.Cells[row, 2] = table.TableSchemaName;
                                    deployWorksheet.Cells[row, 3] = "表單";
                                    deployWorksheet.Cells[row, 4] = form.Name;
                                    deployWorksheet.Cells[row, 5] = form.FormId.ToString();
                                    var findForm = systemForms.Entities.Where(t => t.Id.Equals(form.FormId));
                                    if (findForm.Any())
                                    {
                                        deployWorksheet.Cells[row, 6] = "修改";
                                    }
                                    else
                                    {
                                        deployWorksheet.Cells[row, 6] = "新增";
                                    }
                                    row++;
                                }

                                //處理公共視圖
                                var savedQueriesQuery = new QueryExpression("savedquery");
                                savedQueriesQuery.NoLock = true;
                                savedQueriesQuery.ColumnSet.AddColumn("savedqueryid");
                                savedQueriesQuery.Criteria.AddCondition("returnedtypecode", ConditionOperator.Equal, table.TableSchemaName.ToLower());
                                var savedQueries = crmServiceClient.RetrieveMultiple(savedQueriesQuery);
                                foreach (var savedquery in table.SavedQueries)
                                {
                                    deployWorksheet.Cells[row, 1] = table.TableDispalyName;
                                    deployWorksheet.Cells[row, 2] = table.TableSchemaName;
                                    deployWorksheet.Cells[row, 3] = "視圖";
                                    deployWorksheet.Cells[row, 4] = savedquery.Name;
                                    deployWorksheet.Cells[row, 5] = savedquery.SavedQueryId.ToString();
                                    var findSavedQueries = savedQueries.Entities.Where(t => t.Id.Equals(savedquery.SavedQueryId));
                                    if (findSavedQueries.Any())
                                    {
                                        deployWorksheet.Cells[row, 6] = "修改";
                                    }
                                    else
                                    {
                                        deployWorksheet.Cells[row, 6] = "新增";
                                    }
                                    row++;
                                }

                                //處理公共圖表
                                var visualizationsQuery = new QueryExpression("savedqueryvisualization");
                                visualizationsQuery.NoLock = true;
                                visualizationsQuery.ColumnSet.AddColumn("savedqueryvisualizationid");
                                visualizationsQuery.Criteria.AddCondition("primaryentitytypecode", ConditionOperator.Equal, table.TableSchemaName.ToLower());
                                var visualizations = crmServiceClient.RetrieveMultiple(visualizationsQuery);
                                foreach (var visualization in table.Visualizations)
                                {
                                    deployWorksheet.Cells[row, 1] = table.TableDispalyName;
                                    deployWorksheet.Cells[row, 2] = table.TableSchemaName;
                                    deployWorksheet.Cells[row, 3] = "圖表";
                                    deployWorksheet.Cells[row, 4] = visualization.Name;
                                    deployWorksheet.Cells[row, 5] = visualization.SavedQueryVisualizationId.ToString();
                                    var findVisualizations = visualizations.Entities.Where(t => t.Id.Equals(visualization.SavedQueryVisualizationId));
                                    if (findVisualizations.Any())
                                    {
                                        deployWorksheet.Cells[row, 6] = "修改";
                                    }
                                    else
                                    {
                                        deployWorksheet.Cells[row, 6] = "新增";
                                    }
                                    row++;
                                }
                            }
                            catch (Exception ex)
                            {
                                deployWorksheet.Cells[row, 1] = table.TableDispalyName;
                                deployWorksheet.Cells[row, 2] = table.TableSchemaName;
                                deployWorksheet.Cells[row, 3] = "表";
                                deployWorksheet.Cells[row, 6] = "新增";
                                deployWorksheet.Cells[row, 7] = ex.Message;
                                Console.WriteLine($"程式運作出現異常,請關注異常資訊。{ex.Message}");
                                row++;
                            }
                        }
                        var resultFilePath = ConfigurationManager.AppSettings["ResultFilePath"];
                        if (File.Exists(resultFilePath))
                        {
                            File.Delete(resultFilePath);
                        }
                        deployWorksheet.Columns[1].AutoFit();//自動列寬
                        deployWorksheet.Columns[2].AutoFit();//自動列寬
                        deployWorksheet.Columns[3].AutoFit();//自動列寬
                        deployWorksheet.Columns[4].AutoFit();//自動列寬
                        deployWorksheet.Columns[5].AutoFit();//自動列寬
                        deployWorksheet.Columns[6].AutoFit();//自動列寬
                        deployWorksheet.Columns[7].AutoFit();//自動列寬
                        deployWorkbook.SaveAs(Filename: resultFilePath, FileFormat: XlFileFormat.xlWorkbookDefault);
                        deployWorkbook.Close();
                        excelApp.Quit();
                    }
                    else
                    {
                        throw new Exception($"連接配接Dynamics 365報錯.{crmServiceClient.LastCrmError}");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"程式運作出錯:{ex.Message};{ex.StackTrace}");
                }
            }
            else
            {
                Console.WriteLine("你取消了程式運作!");
            }
            Console.WriteLine("程式運作結束,按任意鍵退出!");
            Console.ReadKey();
        }

        static string ChangeColumnType(string typeInXml)
        {
            string returnVal = typeInXml;
            if (typeInXml.Equals("picklist", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "PicklistType";
            }
            else if (typeInXml.Equals("primarykey", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "UniqueidentifierType";
            }
            else if (typeInXml.Equals("nvarchar", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "StringType";
            }
            else if (typeInXml.Equals("ntext", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "MemoType";
            }
            else if (typeInXml.Equals("float", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "DoubleType";
            }
            else if (typeInXml.Equals("int", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "IntegerType";
            }
            else if (typeInXml.Equals("money", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "MoneyType";
            }
            else if (typeInXml.Equals("lookup", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "LookupType";
            }
            else if (typeInXml.Equals("datetime", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "DateTimeType";
            }
            else if (typeInXml.Equals("bit", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "BooleanType";
            }
            else if (typeInXml.Equals("image", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "ImageType";
            }
            else if (typeInXml.Equals("decimal", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "DecimalType";
            }
            else if (typeInXml.Equals("owner", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "OwnerType";
            }
            else if (typeInXml.Equals("state", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "StateType";
            }
            else if (typeInXml.Equals("status", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "StatusType";
            }
            else if (typeInXml.Equals("file", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "FileType";
            }
            else if (typeInXml.Equals("multiselectpicklist", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "MultiSelectPicklistType";
            }
            else if (typeInXml.Equals("uniqueidentifier", StringComparison.OrdinalIgnoreCase))
            {
                returnVal = "UniqueidentifierType";
            }
            return returnVal;
        }
    }

    public class TableMetadata
    {
        public string TableDispalyName { get; set; }

        public string TableSchemaName { get; set; }

        public List<ColumnMetadata> Columns { get; set; }

        public List<FormMetadata> Forms { get; set; }

        public List<SavedQueryMetadata> SavedQueries { get; set; }

        public List<VisualizationMetadata> Visualizations { get; set; }
    }

    public class ColumnMetadata
    {
        public string LogicalName { get; set; }

        public string DataType { get; set; }
    }

    public class FormMetadata
    {
        public Guid FormId { get; set; }

        public string Name { get; set; }
    }

    public class SavedQueryMetadata
    {
        public Guid SavedQueryId { get; set; }

        public string Name { get; set; }
    }

    public class VisualizationMetadata
    {
        public Guid SavedQueryVisualizationId { get; set; }

        public string Name { get; set; }
    }
}      

配置檔案執行個體内容如下:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
  </startup>
  <appSettings>
    <add key="connectStr" value="AuthType=OAuth;Url=https://thomasluotarget.crm5.dynamics.com;[email protected];Password=Q****J;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97;LoginPrompt=Auto" />
    <!--<add key="connectStr" value="AuthType=ClientSecret;url=https://xxxx.crm.dynamics.cn/;ClientId=54f2327-274c-2dd0-bde2-ertf571ery6e;ClientSecret=2******Uzxht" />-->
    <add key="CustomizationsFilePath" value="C:\Users\yolu\Downloads\Main_1_0_0_13\customizations.xml" />
    <add key="ResultFilePath" value="C:\DeployPreCheckResult.xlsx" />
  </appSettings>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>      

運作界面如下,需要輸入Y确認後才會運作。

使用程式生成要部署的實體和目标環境實體的差别

打開生成Excel檔案如下:

使用程式生成要部署的實體和目标環境實體的差别

修改内容我分為新增或者修改,修改不一定修改,因為我沒有完全去比較是否做了修改,新增是新增。

但是如果字段類型修改了,我還是可以比較出來,因為這個修改會導緻部署報錯,我特意做了比較。

如果發現了示例如下,元件類型為字段,說明列會有說明類型做什麼樣的轉換。

使用程式生成要部署的實體和目标環境實體的差别

你問我為啥不找删除的?因為加入的可能是部分子元件,并不會全部加入,生成删除資訊可能帶來誤解,如果需要可以自己加上。

繼續閱讀