我创建了一个SSIS包(在VS 2013中),其中包含一个包含多个脚本组件的数据流任务。我需要添加另一个与现有数据流类似的数据流任务,因此我将其复制并粘贴到包中 并添加了一个优先约束,以便新的约束在旧的约束之后运行。我根据业务需求更改了新数据流任务中脚本组件中的代码并运行了包。我很快意识到 当新数据流任务运行时,旧数据流中的脚本组件正在执行。怪异。
经过一些互联网搜索后,我找到了这个页面:
http://kohera.be/blog/sql-server/the-dangers-of-copying-script-components-in-ssis/
描述了复制数据流任务如何更改新数据流中任何脚本组件中的程序集名称或根命名空间。显然,在运行时,新脚本的程序集将覆盖旧脚本的程序集。该网页说明可以通过复制脚本组件来解决问题 到另一个包,然后将其复制回原始包。据推测,这将更改每个脚本组件的程序集名称。我很高兴它适合他,但它对我不起作用。
我还尝试在脚本编辑器中更改程序集名称:
但那也没有用。旧脚本组件中的代码仍在新数据流中运行。我猜测根名称空间需要改变,但是它是灰色的,所以我无法手动更改它。因此,我仍然在寻找一种解决方案,它不涉及从头开始重新创建脚本组件,因为它们包含多个输出,每个输出中包含许多列。希望你能得到答案。
答案 0 :(得分:1)
我一直对你的问题感到好奇,因为我过去曾经遇到过这种情况,我只是通过重新创建脚本组件并复制相关代码来纠正。显然不是想法,但是......
无论如何,我通过查看记事本中的dtsx包(请注意它只是xml)来确认复制的问题是应用程序/命名空间保持不变,因此在编译时只会覆盖另一个。
我在Sript Task
(http://agilebi.com/jwelch/2008/04/30/renaming-the-script-project-in-ssis-2008/)找到了一个很好的博客,但同样的解决方案看起来并不像Script Component
那样可用,因为后来没有表达可供使用.....
脚本组件方法 - 成功测试
然而,我刚刚重新创建了问题并发现了一个解决方案。诚然,我不确定我的解决方案是否比重新创建脚本组件和在脚本中复制更容易。无论如何,DTSX是一个XML文件,因此您可以直接编辑它!在我的情况下,可能标准有10个位置,我不得不改变价值观。所以
为了以防万一,请创建您的软件包的备份副本!
脚本任务方法 - 成功测试
如果您正在讨论控制流程中的Script task
,那么这是一种简单的方法。
properties
的{{1}}并选择Script task
Expressions
答案 1 :(得分:0)
谢谢马特。我有同样的想法。但我试图避免触及XML,因为一些程序集名称需要更改而其他程序集保持不变会变得棘手。此外,我的数据流目前有7个脚本组件,所有这些组件都可能需要更改其程序集名称,因此更改XML文件也很痛苦。我相信我已经通过完成每个数据流任务并将每个数据流移动到自己的子包中来解决了这个问题。即使使用相同的程序集/命名空间,SSIS也不会在执行期间混淆分离的数据流。如果您认为不会有效,请告诉我。
答案 2 :(得分:0)
“从另一个VS解决方案复制”变通办法并不总是有效,至少对我而言不是。
我创建了一个C#例程,该例程通过执行一些基本的文本处理来强行修复.dtsx XML。脚本组件组合件定义按组查找,每个定义均以可grep开头的RegEx模式开头和结尾。通常的想法是遍历文件的各行,并且: 1.当看到开始的RegEx模式时,解析出程序集ID并生成一个新的程序集ID(“ SC_”,后跟32个字符的GUID) 2.在找到结束的RegEx模式之前,将旧的Assembly ID替换为新的Assembly ID
注意:除2012年以外,SQL Server版本有占位符,但不支持。
与所有内容一样,使用此代码的风险自负。如果有任何错误,请随时告诉我:
/// <summary>Repair a .dtsx file with conflicting AssemblyIDs in Script copmonents</summary>
/// <remarks>
/// A simple text-processing routine to repair the result of the Visual Studio bug that causes conflicting assembly names to
/// appear when copying a Script component into an SSIS Data Flow container.
///
/// Input: A corrupted .dtsx file containing an SSIS package with Script components having duplicate Assembly IDs
/// Output: A repaired .dtsx file with each Script component having a unique Assembly ID
/// </remarks>
/// <param name="inputDtsxFile">The full path to the .dtsx package to repair</param>
/// <param name="outputDtsxFile">The full path name of the repaired .dtsx package. Optional -
/// Null or default results in a file in the same folder as the source file, with "_repairedNNNN" appended to the file name, incrementing NNNN by 1 each time.</param>
/// <param name="startRegEx">Optional - Overrides the default RegEx for the version of SQL Server found in parameter targetVersion</param>
/// <param name="endRegEx">Optional - Overrides the default RegEx for the version of SQL Server found in parameter targetVersion</param>
/// <param name="targetVersion">Optional - The version of SQL Server the package build target is for. Default (and only version currently supported) is "SQL Server 2016"</param>
private void RepairDtsxScriptComponentCopyError(string inputDtsxFile, string outputDtsxFile = null, string targetVersion = null, string startRegEx = null, string endRegEx = null)
{
//Default the target version to "SQL Server 2016"
if (targetVersion == null)
targetVersion = "SQL Server 2016";
//Make sure if start or end RegEx patters are supplied, BOTH are supplied
if (startRegEx != null || endRegEx != null)
{
if (startRegEx == null)
{
Console.WriteLine("If either start or end regex is specified, both must be specified");
return;
}
}
//Set any variables specific to a target version of Visual Studio for SSIS
switch (targetVersion)
{
case "SQL Server 2012":
Console.WriteLine("SQL Server 2012 target version not supported yet.");
return;
case "SQL Server 2014":
Console.WriteLine("SQL Server 2014 target version not supported yet.");
return;
case "SQL Server 2016":
startRegEx = "\\[assembly: AssemblyTitle\\(\"SC_[a-zA-Z0-9]{32}\"\\)\\]";
endRegEx = "typeConverter=\"NOTBROWSABLE\"";
break;
case "SQL Server 2018":
Console.WriteLine("SQL Server 2018 target version not supported yet.");
return;
}
try
{
//Variables for output stream:
string folderName = "";
string fileName = "";
string fileExt = "";
//If no output file name is supplied, use the folder where the input file is located,
// look for files with the same name as the input file plus suffix "_repairedNNNN"
// and increment NNNN by one to make the new file name
// e.g. fixme.dtsx --> fixme_repared0000.dtsx (the first time it's cleaned)
// fixme.dtsx --> fixme_repared0001.dtsx (the second time it's cleaned)
// and so on.
if (outputDtsxFile == null || String.IsNullOrEmpty(outputDtsxFile) || String.IsNullOrWhiteSpace(outputDtsxFile))
{
folderName = Path.GetDirectoryName(inputDtsxFile);
fileName = Path.GetFileNameWithoutExtension(inputDtsxFile) + "_repaired";
fileExt = Path.GetExtension(inputDtsxFile);
int maxserial = 0;
//Output file will be in the form originalname_NNNN.dtsx
//Each run of the program will increment NNNN
//First, find the highest value of NNNN in all the file names in the target folder:
foreach (string foundFile in Directory.GetFiles(folderName, fileName + "_*" + fileExt))
{
string numStr = Regex.Replace(Path.GetFileNameWithoutExtension(foundFile), "^.*_", "");
int fileNum = -1;
if (int.TryParse(numStr, out fileNum))
maxserial = Math.Max(maxserial, fileNum);
}
//Increment by 1
maxserial++;
//Create new file name
fileName = Path.Combine(folderName, fileName + "_" + maxserial.ToString("0000") + fileExt);
}
else //Use the value passed in as a parameter
fileName = outputDtsxFile;
//Create the new StreamWriter handle for the output file
Stream outputStream = File.OpenWrite(fileName);
StreamWriter outputWriter = new StreamWriter(outputStream);
Console.WriteLine("----START----");
//Open the input file
StreamReader inputFile = new StreamReader(inputDtsxFile);
//Set up some variables
string line = "";
int linepos = 1;
int matchcount = 1;
int assyCount = 0;
string assyname = "";
string oldGuidLC = "";
string oldGuidUC = "";
string newGuidLC = "";
string newGuidUC = "";
Boolean inAssembly = false;
while ((line = inputFile.ReadLine()) != null)
{
//Look for the start of a section that contains the assembly name:
if (!inAssembly && Regex.IsMatch(line, startRegEx))
{
//Get the new GUID
assyname = Regex.Match(line, "SC_[a-zA-Z0-9]{32}").ToString();
oldGuidLC = assyname;
oldGuidUC = "SC_" + assyname.Substring(3, 32).ToUpper();
newGuidLC = "SC_" + Guid.NewGuid().ToString().Replace("-", "");
newGuidUC = newGuidLC.ToUpper();
//Set the "in Assembly" flag
inAssembly = true;
Console.WriteLine("Found Assembly " + assyname + " at line " + linepos.ToString());
Console.WriteLine("Old GUID (LC): " + oldGuidLC);
Console.WriteLine("Old GUID (UC): " + oldGuidUC);
Console.WriteLine("New GUID (LC): " + newGuidLC);
Console.WriteLine("New GUID (UC): " + newGuidUC);
assyCount++;
}
//Substitute the old GUID for the new GUID, but only bother doing it when in an assembly section
if (inAssembly && Regex.IsMatch(line, "SC_[a-zA-Z0-9]{32}"))
{
line = line.Replace(oldGuidLC, newGuidLC);
line = line.Replace(oldGuidUC, newGuidUC);
Console.WriteLine(linepos.ToString("000000") + "/" + assyCount.ToString("0000") + "/" + matchcount++.ToString("0000") + "/" + assyname + ": " + line);
}
//Look for the end of the assembly section
if (inAssembly && Regex.IsMatch(line, endRegEx) && Regex.IsMatch(line, "SC_[a-zA-Z0-9]{32}"))
{
inAssembly = false;
}
//Output the line
outputWriter.WriteLine(line);
linepos++;
}
inputFile.Close();
outputWriter.Close();
outputStream.Close();
Console.WriteLine("----DONE----");
}
catch (Exception ex)
{
Console.WriteLine("ERROR: " + ex.Message);
Console.WriteLine("----DONE----");
}
}