如何在不耗尽内存的情况下部署包含130,000多个条目的脚本

时间:2015-11-09 07:40:54

标签: sql-server visual-studio-2015 sql-server-data-tools

我在使用SSDT构建的Visual Studio 2015中有一个数据库项目。在这个项目中有一个T4模板,它生成对存储过程的SQL调用,以输入世界数据库的初始数据。

它包括国家,地区,时区,地区和城市。

完成T4模板后,它会生成大约130,000个:

EXEC [geo].[addUpdateCountry] @countryCode = N'AD', @countryName = N'Andorra', @initData = 1
EXEC [geo].[addUpdateCountry] @countryCode = N'AE', @countryName = N'United Arab Emirates', @initData = 1
EXEC [geo].[addUpdateCountry] @countryCode = N'AF', @countryName = N'Afghanistan', @initData = 1
EXEC [geo].[addUpdateCountry] @countryCode = N'AG', @countryName = N'Antigua and Barbuda', @
然后,我将文件链接到PostDeploy脚本,以便在使用特定发布配置文件时运行这些脚本。

这只是一个大的,发布过程耗尽了内存,所以我想我必须将它们分成几个批处理,但我不确定在这种情况下我会怎么做。

3 个答案:

答案 0 :(得分:1)

当我遇到部署脚本和内存问题时,我已经把我的脚本放在存储过程中并从部署脚本中调用这些存储过程,你的t4模板会生成如下内容:

create proc deploy.country_data
as
   EXEC [geo].[addUpdateCountry] @countryCode = N'AD', @countryName = N'Andorra', @initData = 1
   EXEC [geo].[addUpdateCountry] @countryCode = N'AE', @countryName = N'United Arab Emirates', @initData = 1
   EXEC [geo].[addUpdateCountry] @countryCode = N'AF', @countryName = N'Afghanistan', @initData = 1
   etc...

然后从部署后的脚本中调用它。

我通常将这些部署存储过程分成单独的ssdt项目,并使用/ p进行部署:IncludeCompositeObject = true。

我还会提出一个关于OO异常的连接项。

您还可以使用以下命令将不同的脚本导入主后部署脚本中来分离部署脚本:r import,但您需要知道脚本的位置。

答案 1 :(得分:1)

我建议您打开与您遇到的OOM例外相关的Connect问题。这可以在https://connect.microsoft.com/SQLServer/feedback/CreateFeedback.aspx使用类别"开发人员工具(SSDT,BIDS等)"来完成。

要在发布期间解决内存不足问题,可以使用(64位)命令行工具SqlPackage.exe来执行发布操作。 SqlPackage.exe是围绕相同的数据层应用程序框架代码的命令行包装器,它在Visual Studio和SSMS中执行发布操作。

答案 2 :(得分:1)

我使用T4模板遇到了一些问题,为我的数据库生成初始设置数据。

1:每次模板重新运行时,没有.sql输出扩展名的T4模板会将其Build Action重置为Build。这是一个问题,因为我想将脚本链接到postDeploy脚本,并且不希望它构建,因为它构建会抛出构建错误。

2:后部署脚本太大,部署它的内存不足,因为它也有用于调试的打印语句。

为了解决我在@Ed Alliot的解决方案中使用我的所有init数据存储过程的问题。但是,我创建了一个名为setup的新模式,并将所有设置存储过程放在该模式中,以便它们可以与数据库的其余部分分开保护。因为我没有生成存储过程,所以我可以让他们的构建操作保持不变#34; Build"它将部署存储过程。

如果有人对此设计过程感兴趣,我会发布一些屏幕和t4模板供参考。

enter image description here

geoDataSql.ttinclude

<#@ template language="C#" hostspecific="true" #>
<#@ CleanupBehavior processor="T4VSHost" CleanupAfterProcessingtemplate="true" #>
<#@ output extension=".sql" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#+
    string countriesFile = "";
    string zonesFile = "";
    string timeZonesFile = "";
    string regionsFile = "";
    string citiesFile = ""; 

    public void GenerateCountries() 
    {       
        List<country> countries = ParseCSV<country>(Host.ResolvePath("..\\T4\\geoSetup\\" + countriesFile));        
        WriteLine("");
        WriteLine("-- Sql to add/update countries");
        foreach(var country in countries)
        {
            if(string.IsNullOrEmpty(country.country_name) || string.IsNullOrEmpty(country.country_code))
            {
                WriteLine("--Skipped: country_code:" + country.country_code + "\ncountry_code was null or empty in the csv file.");
                continue;
            }
            WriteLine(string.Format("EXEC [geo].[addUpdateCountry] @countryCode = N'{0}', @countryName = N'{1}', @initData = 1", country.country_code, country.country_name.Replace("'", "''")));
        }
    }

    public void GenerateZones()
    {
        List<zone> zones = ParseCSV<zone>(Host.ResolvePath("..\\T4\\geoSetup\\"  + zonesFile));
        WriteLine("");
        WriteLine("-- Sql to add/update zones");
        foreach(var zone in zones)
        {
            WriteLine(string.Format("EXEC [geo].[addUpdateZone] @id={0}, @countryCode = N'{1}', @zoneName = N'{2}', @initData = 1", zone.zone_id, zone.country_code, zone.zone_name.Replace("'", "''")));
        }
    }
    public void GenerateTimeZones()
    {
        List<timezone> timeZones = ParseCSV<timezone>(Host.ResolvePath("..\\T4\\geoSetup\\"  + timeZonesFile));
        WriteLine("");
        WriteLine("-- Sql to add/update zones");
        foreach(var timeZone in timeZones)
        {
            if (string.IsNullOrEmpty(timeZone.time_start))
                timeZone.time_start = "0";
            if (string.IsNullOrEmpty(timeZone.gmt_offset))
                timeZone.gmt_offset = "0";
            WriteLine(string.Format("EXEC [geo].[addUpdateTimeZone] @zoneId={0}, @zoneShortName = N'{1}', @timeStart = {2}, @gmtOffset = {3}, @dst = {4}, @initData = 1", timeZone.zone_id, timeZone.abbreviation, timeZone.time_start, timeZone.gmt_offset, timeZone.dst));
        }
    }

    public void GenerateRegions()
    {
        List<city> cities = ParseCSV<city>(Host.ResolvePath("..\\T4\\geoSetup\\"  + citiesFile));
        List<region> regionsOther = ParseCSV<region>(Host.ResolvePath("..\\T4\\geoSetup\\" + regionsFile));
        Dictionary<string, region> regions = new Dictionary<string, region>();

        foreach(var city in cities)
        {
            if (string.IsNullOrEmpty(city.city_name))
                continue;

            if (!string.IsNullOrEmpty(city.subdivision_1_iso_code))
            {
                string rKey = city.country_iso_code + "_" + city.subdivision_1_iso_code;
                region r = new region() { countryCode = city.country_iso_code, regionCode = city.subdivision_1_iso_code, regionName = city.subdivision_1_name };
                if (!regions.ContainsKey(rKey))
                    regions[rKey] = r;
            }           
            if (!string.IsNullOrEmpty(city.subdivision_2_iso_code))
            {
                string rKey = city.country_iso_code + "_" + city.subdivision_2_iso_code;
                region r = new region() { countryCode = city.country_iso_code, regionCode = city.subdivision_2_iso_code, regionName = city.subdivision_2_name };
                if (!regions.ContainsKey(rKey))
                    regions[rKey] = r;              
            }
        }

        foreach (var region in regionsOther)
        {
            string rKey = region.countryCode + "_" + region.regionCode;
            if (!regions.ContainsKey(rKey))
                regions[rKey] = region;
        }

        WriteLine("");
        WriteLine("-- Regions (Pulled from cities.csv, only regions with cities/towns etc are here.)");
        foreach(var region in regions.Values)
        {
            WriteLine(string.Format("EXEC [geo].[addUpdateRegion] @countryCode = N'{0}', @regionCode = N'{1}', @regionName = N'{2}', @initData = 1", region.countryCode, region.regionCode, region.regionName.Replace("'", "''")));
        }

    }

    public void GenerateCities()
    {
        List<city> cities = ParseCSV<city>(Host.ResolvePath("..\\T4\\geoSetup\\"  + citiesFile));
        WriteLine("");
        WriteLine("-- Cities");
        foreach (var city in cities)
        {
            if (string.IsNullOrEmpty(city.city_name))
                continue;
            if (string.IsNullOrEmpty(city.subdivision_1_iso_code) && string.IsNullOrEmpty(city.subdivision_2_iso_code))
            {
                WriteLine("--Skipped City: " + city.geoname_id.ToString() + " it doesn't have any region info!");
                continue;
            }
            string sql = "EXEC [geo].[addUpdateCity] @countryCode = N'{0}', @cityName = N'{1}', @region1Code = {2}, @region2Code = {3},  @zoneName = N'{4}', @initData = 1";
            WriteLine(string.Format(sql, city.country_iso_code, city.city_name.Replace("'", "''"), city.subdivision_1_iso_code == null ? "null" : "N'" + city.subdivision_1_iso_code + "'", city.subdivision_2_iso_code == null ? "null" : "N'" + city.subdivision_2_iso_code + "'", city.time_zone));
        }
    }
    public List<T> ParseCSV<T>(string filePath) where T: class, new()
    {
        if (!System.IO.File.Exists(filePath))
            return null;

        string[] csvContents = File.ReadAllText(filePath).Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
        if (csvContents.Length <= 1)
            return null;
        Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"]+|\"\")*\"|[^,]*)", RegexOptions.Compiled);
        string[] fieldNames = csvContents[0].Split(new string[] { "," }, StringSplitOptions.None);
        if (fieldNames.Length <= 0)
            return null;
        List<T> ret = new List<T>();
        Type objType = typeof(T);

        for(int i = 1; i < csvContents.Length; ++i)
        {
            List<string> values = new List<string>();
            foreach (Match match in csvSplit.Matches(csvContents[i]))                
                values.Add(match.Value.TrimStart(',').Trim('"').Trim());   
            if (values.Count != fieldNames.Length)                              
                throw new Exception("Test");                

            T obj = new T();
            for(int i2 = 0; i2 < fieldNames.Length; ++i2)
            {
                var field = fieldNames[i2];
                var props = objType.GetProperties();
                var property = objType.GetProperty(field.Trim(), System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreCase | System.Reflection.BindingFlags.SetProperty);
                if (property == null) {
                    throw new Exception("PROPERTY NULL");
                }                   
                if (property == null)
                    throw new Exception("Unable to parse field: " + field + " into Type: " + objType.FullName + "\n unable to find property on the Type matching the fields name.");
                if (property.PropertyType != typeof(string))
                    throw new Exception("Unable to parse field: " + field + " into Property becuase the Property is not of Type string. Type:" + objType.FullName);
                string v = values[i2] == null || values[i2] == string.Empty ? null : values[i2].Trim();
                property.SetValue(obj, v);
            }
            ret.Add(obj);
        }
        return ret;
    }
    public class city
    {
        public string geoname_id { get; set;}
        public string locale_code { get; set;}
        public string continent_code { get; set;}
        public string continent_name { get; set;}
        public string country_iso_code { get; set;}
        public string country_name { get; set;}
        public string subdivision_1_iso_code { get; set;}
        public string subdivision_1_name { get; set;}
        public string subdivision_2_iso_code { get; set;}
        public string subdivision_2_name { get; set;}
        public string city_name { get; set;}
        public string metro_code { get; set;}
        public string time_zone { get; set;}
    }
    public class region
    {
        public string countryCode { get; set;}
        public string regionCode { get; set;}
        public string regionName { get; set;}
    }
    public class country
    {
        public string country_code { get; set; }
        public string country_name { get; set; }
    }
    public class zone
    {
        public string zone_id { get; set;}
        public string country_code { get; set;}
        public string zone_name { get; set;}
    }
    public class timezone
    {
        public string zone_id { get; set;}
        public string abbreviation { get; set;}
        public string time_start { get; set;}
        public string gmt_offset { get; set;}
        public string dst { get; set;}
    }
#>    

setupCountryData.tt

&lt;#@ include file =&#34; .. \ T4 \ geoSetup \ geoDataSql.ttinclude&#34; #&GT;

<#  
    countriesFile = "countries.csv";
    WriteLine("CREATE PROCEDURE [setup].[setupCountryData] AS");
    GenerateCountries();
    WriteLine("RETURN 0;");
#>

要创建其他csv驱动的存储过程,只需将一个名为GenerateXYZ()的方法添加到geoDataSql.ttinclude。然后创建一个类似setupCountryData.sql的文件并设置文件名并调用您添加的相应生成方法。

CONS: 这使得构建需要永远......所以我可能会在一段时间后将这个逻辑移到控制台应用程序中......然后在部署/更改后运行它。