自动输出SQL Server对象依赖列表到Excel文件

日期: 2010-07-12 作者:unruledboy 来源:TechTarget中国 英文

  需求

  现在数据库很老很大,表不多,200来个,但数据量很大:最大的数据表2亿6千万条,每天增加50多w,925个存储过程。

  系统大,耦合度很高,牵一发而动全身。人员变动频繁,接手的人员要在修改之前,就得花相当长的时间来分析关联性。

  所以,印度资深系统分析员要求我在一个EXCEL文件中,把925个存储过程的所有依赖的对象(表、函数、视图、存储过程等等)都列举出来。

  分析

  手工逐个打开存储过程去做,对写软件的人来说是很傻的事情,一般重复性工作,如果预计耗时超过3分钟,我就会卷起袖子写个代码。

  工作内容有3部分:

  1.获取所有的存储过程。我们可以用sysobjects这个系统表,它存储了所有的表、存储过程、视图、函数等。其中存储过程的xtype是P,CLR存储过程,类型是PC;函数的类型是FN/IF或TF,CLR函数类型是FS;视图类型是V。

  2. 获取某存储过程所依赖的对象,当然是先google了。很久之前我就知道可以用系统存储过程sp_depends来获取,不过还是应该看看还有什么更好的办法。首先我发现这个:http://www.mssqltips.com/tip.asp?tip=1294 。作者研究出4种办法:INFORMATION_SCHEMA.ROUTINES/sp_depends/syscomments/sp_MSdependencies。其中就有我一直在用的sp_depends。其它办法有的霸王硬上弓:用charindex来遍历存储过程内容,或者用LIKE来判断。。。。。我服了,写代码的风格千差万别,一些是[Foo],一些是Foo,而且不同的存储过程名称可能存在完全给另外一个包含,譬如Foo Foo1 AFoo等。

  看完之后,我还是觉得使用sp_depends相对靠谱。为什么说“相对靠谱”呢?因为我发现它某些情况下也会没有返回所有依赖的,这应该是SQL Server的bug吧?如果要把所有依赖都找回来,你可以去修改被遗忘的引用存储过程,随便加个空行,运行(就是保存结果),你会发现之前没有显示的依赖终于出现了。而且,sp_depends会输出重复的记录。。。所以我们在代码中要剔除掉。

  3. 既然是输出到EXCEL文件,我们就需要找相应的代码。在这个网站已经有很多EXCEL文件生成的代码了,譬如NPOI。我最后采用了GemBox的,因为够轻便。本来想用更轻便的MyXLS,但发现它不支持单背景色。当然你也可以用别的,譬如XML格式的EXCEL文件,这是你个人的选择了。

  解决了上述的3个问题,我们就可以大干一场了。我用VS2005+C#2.0,因为公司还是在用古老的XP搭配VS2005,鬼佬国家要求什么都正版,自然不会像我们在中国那样随便就升级到2010了。所以只能放弃LINQ,老老实实地写老派的代码了。

  以下代码没有什么特别的,都是循环所有存储过程,然后循环每个存储过程的依赖对象,然后排序输出(先按照类型,然后按照名称)。本来想用DataTable.Select对多个字段排序,但后来发现没效果,也没心思去研究为什么,干脆就改成写一个IComparer。

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Data;
using System.Data.SqlClient;
using GemBox.Spreadsheet;

namespace SQLServerDocumenter
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                args = new string[4];
                args[0] = “database”;
                args[1] = “datasource”;
                args[2] = “user”;
                args[3] = “password”;
            }

            string db = args[0];
            string dataSource = args.Length > 1 ? args[1] : string.Empty;
            string user = args.Length > 2 ? args[2] : string.Empty;
            string password = args.Length > 3 ? args[3] : string.Empty;

            Work work = new Work();
            work.Progress += new EventHandler<ProgressEventArgs>(OnWorkProgress);
            work.Run(db, dataSource, user, password);

            Console.WriteLine();
            Console.WriteLine(“all done!”);
            Console.Read();
        }

        private static void OnWorkProgress(object sender, ProgressEventArgs e)
        {
            Console.WriteLine(e.Status);
        }
    }

    public class Work
    {
        public event EventHandler<ProgressEventArgs> Progress;

        public void Run(string Database, string DataSource, string UserName, string Password)
        {
            ExcelFile xls = new ExcelFile();
            ExcelWorksheet sheet = xls.Worksheets.Add(“Dictionary”);
            CellStyle nameStyle = new CellStyle(xls);
            nameStyle.FillPattern.SetSolid(Color.DarkGray);
            nameStyle.Font.Color = Color.Black;
            nameStyle.Font.Weight = ExcelFont.BoldWeight;

            sheet.Cells[0, 0].Value = string.Format(“{0} database dictionary”, Database);

            sheet.Cells[4, 0].Value = “Name”;
            sheet.Cells[4, 0].Style = nameStyle;

            sheet.Cells[4, 1].Value = “Dependencies”;
            sheet.Cells[4, 1].Style = nameStyle;

            sheet.Cells[4, 2].Value = “Type”;
            sheet.Cells[4, 2].Style = nameStyle;

            string connectionString = string.Format(“Password={0};Persist Security Info=True;User ID={1};Initial Catalog={2};Data Source={3}”, Password, UserName, Database, DataSource);
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();

                int index = 5;

                AddObjects(connection, “‘P'”, “Stored Procedures”, new List<string>(new string[] { “sp_alterdiagram”, “sp_creatediagram”, “sp_dropdiagram”, “sp_helpdiagramdefinition”, “sp_helpdiagrams”, “sp_renamediagram”, “sp_upgraddiagrams” }), sheet, ref index);
                AddObjects(connection, “‘FN’,’IF’,’TF'”, “Functions”, new List<string>(), sheet, ref index);
                AddObjects(connection, “‘V'”, “Views”, new List<string>(), sheet, ref index);

                connection.Close();
            }

            string path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + @”” + Database + “.xls”;
            xls.SaveXls(path);
        }

        private void AddObjects(SqlConnection Connection, string Types, string Name, List<string> IgnoreNames, ExcelWorksheet Sheet, ref int Index)
        {
            CellStyle itemStyle = new CellStyle();
            itemStyle.FillPattern.SetSolid(Color.LightGray);
            itemStyle.Font.Color = Color.Black;
            itemStyle.Font.Weight = ExcelFont.BoldWeight;
            CellStyle typeStyle = new CellStyle();
            typeStyle.FillPattern.SetSolid(Color.Yellow);
            typeStyle.Font.Color = Color.Black;
            typeStyle.Font.Weight = ExcelFont.BoldWeight;
            Sheet.Cells[Index, 0].Value = Name;
            Sheet.Cells[Index, 0].Style = typeStyle;

            Index++;

            DataSet data = new DataSet();
            using (SqlCommand command = new SqlCommand(string.Format(“SELECT * FROM sysobjects WHERE XTYPE IN ({0}) ORDER BY NAME”, Types), Connection))
            {
                SqlDataAdapter adapter = new SqlDataAdapter(command);
                adapter.Fill(data);
                if (data.Tables.Count > 0)
                {
                    DataTable objects = data.Tables[0];
                    for (int i = 0; i < objects.Rows.Count; i++)
                    {
                        string objectName = objects.Rows[i][“name”].ToString();
                        if (!IgnoreNames.Contains(objectName))
                        {
                            Sheet.Cells[Index, 0].Value = objectName;
                            Sheet.Cells[Index, 0].Style = itemStyle;
                            DataSet data2 = new DataSet();
                            using (SqlCommand command2 = new SqlCommand(string.Format(“exec sp_depends ‘{0}'”, objectName), Connection))
                            {
                                adapter = new SqlDataAdapter(command2);
                                adapter.Fill(data2);
                            }
                            if (data2.Tables.Count > 0)
                            {
                                DataTable dependencies = data2.Tables[0];
                                Dictionary<string, KeyValuePair<string, string>> uniqueDependencies = new Dictionary<string, KeyValuePair<string, string>>();
                                for (int j = 0; j < dependencies.Rows.Count; j++)
                                {
                                    string itemName = dependencies.Rows[j][“name”].ToString();
                                    if (itemName.ToLower().StartsWith(“dbo.”))
                                        itemName = itemName.Substring(4);
                                    if (!uniqueDependencies.ContainsKey(itemName))
                                        uniqueDependencies.Add(itemName, new KeyValuePair<string, string>(itemName, dependencies.Rows[j][“type”].ToString()));
                                }
                                List<KeyValuePair<string, string>> allItems = new List<KeyValuePair<string, string>>();
                                foreach (KeyValuePair<string, KeyValuePair<string, string>> item in uniqueDependencies)
                                {
                                    allItems.Add(new KeyValuePair<string, string>(item.Value.Key, item.Value.Value));
                                }
                                allItems.Sort(new KVPComparer());
                                foreach (KeyValuePair<string, string> item in allItems)
                                {
                                    Index++;
                                    Sheet.Cells[Index, 1].Value = item.Key;
                                    Sheet.Cells[Index, 2].Value = item.Value;
                                }
                            }
                            else
                            {
                                Index++;
                                Sheet.Cells[Index, 1].Value = “(N/A)”;
                            }
                            Index += 3;
                            AddProgress(string.Format(“({0}/{1}) {2} done”, i + 1, objects.Rows.Count, objectName));
                        }
                        else
                            AddProgress(string.Format(“({0}/{1}) {2} ignored”, i + 1, objects.Rows.Count, objectName));
                    }
                }
                else
                    Sheet.Cells[Index, 0].Value = “(N/A)”;
            }

            Index++;
        }

        private void AddProgress(string Status)
        {
            if (Progress != null)
                Progress(this, new ProgressEventArgs(Status));
        }
    }

    public class ProgressEventArgs : EventArgs
    {
        private string status;
        public string Status
        {
            get { return status; }
            set { status = value; }
        }

        public ProgressEventArgs(string Status)
        {
            status = Status;
        }
    }

    internal class KVPComparer : IComparer<KeyValuePair<string, string>>
    {
        public int Compare(KeyValuePair<string, string> x, KeyValuePair<string, string> y)
        {
            int compare = string.Compare(x.Value, y.Value);
            if (compare == 0)
                return string.Compare(x.Key, y.Key);
            else
                return compare;
        }
    }
}

  使用

  使用很简单,编译(你得找个EXCEL输出代码。。。),在命令行(改成Win应用也可以啊)输入3个参数:数据库名、服务器名和密码。当然,大家都有自己的品味,喜欢怎么改输出格式就怎么改吧。

  结论

  印度资深系统分析员只是让我给个EXCEL文件,没有让我写代码,所以把我自己的研究成果发上来也无伤大雅。一般我都喜欢把写的东西弄成可重用的,不仅仅为了一个固定的目的,所以也便有了4个参数和同时输出函数和视图的依赖列表。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

电子邮件地址不会被公开。 必填项已用*标注

敬请读者发表评论,本站保留删除与本文无关和不雅评论的权力。

相关推荐