如何在 运行 内存不足的情况下部署包含 130,000 多个条目的脚本
How can I deploy a script with 130,000+ entries without running out of memory
我在 Visual Studio 2015 年使用 SSDT 构建了一个数据库项目。在这个项目中是一个 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 脚本到 运行 使用特定发布配置文件时的脚本。
它太大了,发布过程 运行 内存不足,所以我想我必须以某种方式将它们分成几批,但我不确定在这种情况下我会怎么做。
当我遇到部署脚本和内存问题时,我将我的脚本放在存储过程中,并从部署脚本中调用这些存储过程,你的 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...
然后从您的 post 部署脚本中调用它。
我通常将这些部署存储过程分离到一个单独的 ssdt 项目中,并使用 /p:IncludeCompositeObject=true 进行部署。
我还会提出一个关于 OO 异常的连接项目。
您还可以通过使用 :r import 将不同的脚本导入主 post-部署脚本来分离部署脚本,但您需要知道脚本的位置。
我建议打开一个与您遇到的 OOM 异常相关的连接问题。这可以在 https://connect.microsoft.com/SQLServer/feedback/CreateFeedback.aspx 使用类别 "Developer Tools (SSDT, BIDS, etc.)".
完成
要解决发布期间内存不足的问题,可以使用(64 位)命令行工具 SqlPackage.exe 来执行发布操作。 SqlPackage.exe 是围绕在 Visual Studio 和 SSMS 中执行发布操作的同一数据层应用程序框架代码的命令行包装器。
SqlPackage.exe 可以从这里下载:http://www.microsoft.com/en-us/download/details.aspx?id=49500
文档可在此处获得:https://msdn.microsoft.com/en-us/library/hh550080(v=vs.103).aspx
用法示例:
SqlPackage.exe /a:publish /sf:C:\temp\mydb.dacpac /tcs:"Data Source=myserver;Initial Catalog=mydb;Integrated Security=true"
我 运行 遇到了一些使用 T4 模板为我的数据库生成初始设置数据的问题。
1:没有输出扩展名 .sql 的 T4 模板会在每次模板重新 运行 时将其构建操作重置为构建。这是一个问题,因为我想 link 将脚本放入 post 部署脚本,但不希望构建它,因为构建会引发构建错误。
2:post 部署脚本太大,运行 内存不足,因为它也有用于调试的打印语句。
为了解决这些问题,我使用了@Ed Alliot 的解决方案来制作我所有的初始化数据存储过程。但是,我创建了一个名为 setup 的新模式,并将所有设置存储过程放在该模式中,以便它们可以与数据库的其余部分分开保护。因为我没有生成存储过程,所以我可以让他们的构建操作保持不变 "Build" 它将部署存储过程。
如果有人对这个设计过程感兴趣,我会 post 一些屏幕和 t4 模板供参考。
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
<#@ include file="..\T4\geoSetup\geoDataSql.ttinclude" #>
<#
countriesFile = "countries.csv";
WriteLine("CREATE PROCEDURE [setup].[setupCountryData] AS");
GenerateCountries();
WriteLine("RETURN 0;");
#>
要创建其他 csv 驱动的存储过程,只需将名为 GenerateXYZ() 的方法添加到 geoDataSql.ttinclude。然后创建一个类似setupCountryData.sql的文件并设置文件名并调用你添加的适当的生成方法。
缺点:
这使得构建永远持续......所以我可能会在一段时间后将这个逻辑移出到控制台应用程序......然后 运行 在 deploys/changes.
之后
我在 Visual Studio 2015 年使用 SSDT 构建了一个数据库项目。在这个项目中是一个 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 脚本到 运行 使用特定发布配置文件时的脚本。
它太大了,发布过程 运行 内存不足,所以我想我必须以某种方式将它们分成几批,但我不确定在这种情况下我会怎么做。
当我遇到部署脚本和内存问题时,我将我的脚本放在存储过程中,并从部署脚本中调用这些存储过程,你的 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...
然后从您的 post 部署脚本中调用它。
我通常将这些部署存储过程分离到一个单独的 ssdt 项目中,并使用 /p:IncludeCompositeObject=true 进行部署。
我还会提出一个关于 OO 异常的连接项目。
您还可以通过使用 :r import 将不同的脚本导入主 post-部署脚本来分离部署脚本,但您需要知道脚本的位置。
我建议打开一个与您遇到的 OOM 异常相关的连接问题。这可以在 https://connect.microsoft.com/SQLServer/feedback/CreateFeedback.aspx 使用类别 "Developer Tools (SSDT, BIDS, etc.)".
完成要解决发布期间内存不足的问题,可以使用(64 位)命令行工具 SqlPackage.exe 来执行发布操作。 SqlPackage.exe 是围绕在 Visual Studio 和 SSMS 中执行发布操作的同一数据层应用程序框架代码的命令行包装器。
SqlPackage.exe 可以从这里下载:http://www.microsoft.com/en-us/download/details.aspx?id=49500
文档可在此处获得:https://msdn.microsoft.com/en-us/library/hh550080(v=vs.103).aspx
用法示例:
SqlPackage.exe /a:publish /sf:C:\temp\mydb.dacpac /tcs:"Data Source=myserver;Initial Catalog=mydb;Integrated Security=true"
我 运行 遇到了一些使用 T4 模板为我的数据库生成初始设置数据的问题。
1:没有输出扩展名 .sql 的 T4 模板会在每次模板重新 运行 时将其构建操作重置为构建。这是一个问题,因为我想 link 将脚本放入 post 部署脚本,但不希望构建它,因为构建会引发构建错误。
2:post 部署脚本太大,运行 内存不足,因为它也有用于调试的打印语句。
为了解决这些问题,我使用了@Ed Alliot 的解决方案来制作我所有的初始化数据存储过程。但是,我创建了一个名为 setup 的新模式,并将所有设置存储过程放在该模式中,以便它们可以与数据库的其余部分分开保护。因为我没有生成存储过程,所以我可以让他们的构建操作保持不变 "Build" 它将部署存储过程。
如果有人对这个设计过程感兴趣,我会 post 一些屏幕和 t4 模板供参考。
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
<#@ include file="..\T4\geoSetup\geoDataSql.ttinclude" #>
<#
countriesFile = "countries.csv";
WriteLine("CREATE PROCEDURE [setup].[setupCountryData] AS");
GenerateCountries();
WriteLine("RETURN 0;");
#>
要创建其他 csv 驱动的存储过程,只需将名为 GenerateXYZ() 的方法添加到 geoDataSql.ttinclude。然后创建一个类似setupCountryData.sql的文件并设置文件名并调用你添加的适当的生成方法。
缺点: 这使得构建永远持续......所以我可能会在一段时间后将这个逻辑移出到控制台应用程序......然后 运行 在 deploys/changes.
之后