如何使用 SQL table 作为源在 BIML 中填充数组
how to fill an array in BIML using SQL table as source
我有一个 table,其中包含文件名和一些其他属性。我想将这些记录插入 BIML 中的数组中,以便我可以遍历这些文件(并使用附加属性导入它们)。
导入此类文件的代码已基本完成。
现在只需要我将下面的信息放在一个数组中的部分,这样我就可以遍历它们。
CREATE TABLE [config].[FilesToImport](
[id] [int] IDENTITY(1,1) NOT NULL,
[path] [nvarchar](1024) NULL,
[importfilename] [nvarchar](1024) NULL,
[dest_server] [nvarchar](256) NULL,
[dest_db] [nvarchar](256) NULL,
[dest_schema] [nvarchar](256) NULL,
[refreshtype] [int] NULL,
[separator] [nvarchar](5) NULL,
[order] [int] NULL
)
GO
SET IDENTITY_INSERT [config].[FilesToImport] ON
GO
INSERT [config].[FilesToImport] ([id], [path], [importfilename], [dest_server], [dest_db], [dest_schema], [refreshtype], [separator], [order]) VALUES (1, N'c:\temp', N'FileA.csv', N'SMVPDA001', N'TEST', N'landingzone', 0, N';', 1)
GO
INSERT [config].[FilesToImport] ([id], [path], [importfilename], [dest_server], [dest_db], [dest_schema], [refreshtype], [separator], [order]) VALUES (2, N'c:\temp', N'FileZ.csv', N'SMVPDA001', N'TEST', N'landingzone', 0, N';', 2)
GO
INSERT [config].[FilesToImport] ([id], [path], [importfilename], [dest_server], [dest_db], [dest_schema], [refreshtype], [separator], [order]) VALUES (3, N'c:\temp', N'File4.cvs', N'SMVPDA001', N'TEST', N'landingzone', 0, N';', 3)
GO
SET IDENTITY_INSERT [config].[FilesToImport] OFF
所以基本上下面的代码应该是从上面提到的 table
中获取的文件名
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<#
string Applicatie = "BIML";
string Prefix = "import";
string fileName;
string path = @"c:\temp";
string[] myFiles = Directory.GetFiles(path, "*.csv");
string[] myColumns;
#>
<FileFormats>
<#
foreach (string filePath in myFiles)
{
#>
<FlatFileFormat Name="FlatFileFormat<#=Path.GetFileNameWithoutExtension(filePath)#>" RowDelimiter="CRLF" ColumnNamesInFirstDataRow="true" IsUnicode="false">
<Columns>
<#
StreamReader myFile = new StreamReader(filePath);
myColumns = myFile.ReadLine().Replace("\"","").Split('|');
myFile.Close();
// to determine the column delimeter
int columnCount = 0;
string columnDelimiter = "";
//WriteLine($"<!-- ref count {myColumns.Count} -->");
foreach(string myColumn in myColumns)
{
columnCount++;
bool finalColumn = columnCount == myColumns.Length;
WriteLine($"<!-- actual count {columnCount} -->");
// WriteLine($"<!-- what {columnCount == myColumns.Count} {finalColumn} -->");
if (finalColumn)
{
columnDelimiter = "CRLF";
}
else
{
columnDelimiter = "|";
}
WriteLine($"<!-- delimiter {columnDelimiter} -->");
#>
<Column Name="<#=myColumn#>" DataType = "String" Length="250" Delimiter="<#=columnDelimiter#>"></Column>
<# } #>
</Columns>
</FlatFileFormat>
<#}#>
</FileFormats>
<Connections>
<#
foreach (string filePath in myFiles)
{
#>
<FlatFileConnection Name="FF_CSV-<#=Path.GetFileNameWithoutExtension(filePath)#>"
FilePath="<#=filePath#>"
FileFormat="FlatFileFormat<#=Path.GetFileNameWithoutExtension(filePath)#>"
>
<Expressions>
<Expression ExternalProperty="TextQualifier">"\""</Expression>
<Expression ExternalProperty="RowDelimiter">"CRLF"</Expression>
</Expressions>
</FlatFileConnection>
<# } #>
<OleDbConnection
Name="STG_<#=Applicatie#>"
ConnectionString="Data Source=SQLSERVER;Initial Catalog=TEST;Provider=SQLNCLI11.1;Integrated Security=SSPI;Auto Translate=False;">
</OleDbConnection>
</Connections>
<Packages>
<# // Loop trough the files
int TableCount = 0;
foreach (string filePath in myFiles)
{
TableCount++;
fileName = Path.GetFileNameWithoutExtension(filePath);
#>
<Package Name="stg_<#=Prefix#>_<#=TableCount.ToString()#>_<#=fileName#>" ConstraintMode="Linear" AutoCreateConfigurationsType="None" ProtectionLevel="EncryptSensitiveWithPassword" PackagePassword="secret">
<Variables>
<Variable Name="CountStage" DataType="Int32" Namespace="User">0</Variable>
</Variables>
<Tasks>
<ExecuteSQL ConnectionName="STG_<#=Applicatie#>" Name="SQL-Truncate <#=fileName#>">
<DirectInput>TRUNCATE TABLE dbo.<#=Prefix#>_<#=fileName#></DirectInput>
</ExecuteSQL>
<Dataflow Name="DFT-Transport CSV_<#=fileName#>">
<Transformations>
<FlatFileSource Name="SRC_FF-<#=fileName#> " ConnectionName="FF_CSV-<#=Path.GetFileNameWithoutExtension(filePath)#>">
</FlatFileSource>
<OleDbDestination ConnectionName="STG_<#=Applicatie#>" Name="OLE_DST-<#=fileName#>" >
<ExternalTableOutput Table="dbo.<#=Prefix#>_<#=fileName#>"/>
</OleDbDestination>
</Transformations>
</Dataflow>
</Tasks>
</Package>
<# } #>
<!-- Create Master Package -->
<Package Name="stg_<#=Prefix#>_0_Master" ConstraintMode="Parallel" AutoCreateConfigurationsType="None" ProtectionLevel="EncryptSensitiveWithPassword" PackagePassword="secret">
<Tasks>
<# int TableCount2 = 0;
foreach (string filePath in myFiles)
{
TableCount2++;
fileName = Path.GetFileNameWithoutExtension(filePath); #>
<ExecutePackage Name="stg_<#=Prefix#>_<#=TableCount2.ToString()#>_<#=fileName#>">
<ExternalProjectPackage Package="stg_<#=Prefix#>_<#=TableCount2.ToString()#>_<#=fileName#>.dtsx" />
</ExecutePackage>
<#
}
#>
</Tasks>
</Package>
</Packages>
</Biml>
<!--Includes/Imports for C#-->
<#@ template language="C#" hostspecific="true"#>
<#@ import namespace="System.Data"#>
<#@ import namespace="System.IO"#>
我看到你用实际代码编辑了它,但问题的症结在于你正在寻找 ExternalDataAccess.GetDataTable method. Also mentioned here Foreach datarow filter in BIML 奇怪的是我没有回答它...
该方法填充数据 table,然后您可以将其作为二维数组使用。 Rows 属性 允许您枚举行,然后每个 DataRow 都是一个可为空的对象数组。是的,即使它在数据库中有一个类型,您也必须显式转换为其他类型才能使用数据。
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<#
// Could also pull this from this.RootNodes.Connections
string connectionStringSource = @"Server=.\dev2017;Initial Catalog=tempdb;Integrated Security=SSPI;Provider=SQLNCLI11.1;";
string query = @"SELECT
FTI.id
, FTI.path
, FTI.importfilename
, FTI.dest_server
, FTI.dest_db
, FTI.dest_schema
, FTI.refreshtype
, FTI.separator
, FTI.defaultfieldtype
, FTI.[order]
FROM
config.FilesToImport AS FTI;";
DataTable dt = null;
dt = ExternalDataAccess.GetDataTable(connectionStringSource, query);
foreach(DataRow row in dt.Rows)
{
// Downside to data table is everything is object so explict casts required
WriteLine($"<!-- {row[0]},{row[1]},{row[2]} etc -->");
}
#>
<FileFormats>
<#
// Enumerate through the data table to define FFCM
foreach(DataRow row in dt.Rows)
{
#>
<FlatFileFormat Name="SO_<#=row[0]#>" RowDelimiter="CRLF" ColumnNamesInFirstDataRow="true" IsUnicode="false"/>
<#
}
#>
</FileFormats>
<Packages>
<#
// Enumerate through the data table to define packages, etc
foreach(DataRow row in dt.Rows)
{
#>
<Package Name="Load_<#=row[5]#>_<#=row[0]#>" />
<#
}
#>
</Packages>
</Biml>
浏览一下您的代码,您可能会对 CallBimlScript 或 CallBimlScriptWithOutput(我博客上的示例)感兴趣。您可以将代码分解为函数(本质上),而不是单一的 BimlScript。在 Replicate-o-matic post 中,我传入了一个模式和一个 table 并且构建单个包实例的所有逻辑都在那里,所以主构建器包只关心构建顶级工件 - 连接、包等,并将工作推给函数。
GetBimlScriptWithOutput 的巧妙之处在于您可以 return 向调用方发送数据。例如,您在创建包时正在构建您的包名称,并且您已经为您的主包重复了该逻辑以执行它们。不要重复自己。相反,让包构建器 return 一个 属性 作为新包的名称(并添加到 array/list)。然后 Orchestrator 包可以简单地获取包名而不知道它们是如何派生的。
我有一个 table,其中包含文件名和一些其他属性。我想将这些记录插入 BIML 中的数组中,以便我可以遍历这些文件(并使用附加属性导入它们)。
导入此类文件的代码已基本完成。 现在只需要我将下面的信息放在一个数组中的部分,这样我就可以遍历它们。
CREATE TABLE [config].[FilesToImport](
[id] [int] IDENTITY(1,1) NOT NULL,
[path] [nvarchar](1024) NULL,
[importfilename] [nvarchar](1024) NULL,
[dest_server] [nvarchar](256) NULL,
[dest_db] [nvarchar](256) NULL,
[dest_schema] [nvarchar](256) NULL,
[refreshtype] [int] NULL,
[separator] [nvarchar](5) NULL,
[order] [int] NULL
)
GO
SET IDENTITY_INSERT [config].[FilesToImport] ON
GO
INSERT [config].[FilesToImport] ([id], [path], [importfilename], [dest_server], [dest_db], [dest_schema], [refreshtype], [separator], [order]) VALUES (1, N'c:\temp', N'FileA.csv', N'SMVPDA001', N'TEST', N'landingzone', 0, N';', 1)
GO
INSERT [config].[FilesToImport] ([id], [path], [importfilename], [dest_server], [dest_db], [dest_schema], [refreshtype], [separator], [order]) VALUES (2, N'c:\temp', N'FileZ.csv', N'SMVPDA001', N'TEST', N'landingzone', 0, N';', 2)
GO
INSERT [config].[FilesToImport] ([id], [path], [importfilename], [dest_server], [dest_db], [dest_schema], [refreshtype], [separator], [order]) VALUES (3, N'c:\temp', N'File4.cvs', N'SMVPDA001', N'TEST', N'landingzone', 0, N';', 3)
GO
SET IDENTITY_INSERT [config].[FilesToImport] OFF
所以基本上下面的代码应该是从上面提到的 table
中获取的文件名<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<#
string Applicatie = "BIML";
string Prefix = "import";
string fileName;
string path = @"c:\temp";
string[] myFiles = Directory.GetFiles(path, "*.csv");
string[] myColumns;
#>
<FileFormats>
<#
foreach (string filePath in myFiles)
{
#>
<FlatFileFormat Name="FlatFileFormat<#=Path.GetFileNameWithoutExtension(filePath)#>" RowDelimiter="CRLF" ColumnNamesInFirstDataRow="true" IsUnicode="false">
<Columns>
<#
StreamReader myFile = new StreamReader(filePath);
myColumns = myFile.ReadLine().Replace("\"","").Split('|');
myFile.Close();
// to determine the column delimeter
int columnCount = 0;
string columnDelimiter = "";
//WriteLine($"<!-- ref count {myColumns.Count} -->");
foreach(string myColumn in myColumns)
{
columnCount++;
bool finalColumn = columnCount == myColumns.Length;
WriteLine($"<!-- actual count {columnCount} -->");
// WriteLine($"<!-- what {columnCount == myColumns.Count} {finalColumn} -->");
if (finalColumn)
{
columnDelimiter = "CRLF";
}
else
{
columnDelimiter = "|";
}
WriteLine($"<!-- delimiter {columnDelimiter} -->");
#>
<Column Name="<#=myColumn#>" DataType = "String" Length="250" Delimiter="<#=columnDelimiter#>"></Column>
<# } #>
</Columns>
</FlatFileFormat>
<#}#>
</FileFormats>
<Connections>
<#
foreach (string filePath in myFiles)
{
#>
<FlatFileConnection Name="FF_CSV-<#=Path.GetFileNameWithoutExtension(filePath)#>"
FilePath="<#=filePath#>"
FileFormat="FlatFileFormat<#=Path.GetFileNameWithoutExtension(filePath)#>"
>
<Expressions>
<Expression ExternalProperty="TextQualifier">"\""</Expression>
<Expression ExternalProperty="RowDelimiter">"CRLF"</Expression>
</Expressions>
</FlatFileConnection>
<# } #>
<OleDbConnection
Name="STG_<#=Applicatie#>"
ConnectionString="Data Source=SQLSERVER;Initial Catalog=TEST;Provider=SQLNCLI11.1;Integrated Security=SSPI;Auto Translate=False;">
</OleDbConnection>
</Connections>
<Packages>
<# // Loop trough the files
int TableCount = 0;
foreach (string filePath in myFiles)
{
TableCount++;
fileName = Path.GetFileNameWithoutExtension(filePath);
#>
<Package Name="stg_<#=Prefix#>_<#=TableCount.ToString()#>_<#=fileName#>" ConstraintMode="Linear" AutoCreateConfigurationsType="None" ProtectionLevel="EncryptSensitiveWithPassword" PackagePassword="secret">
<Variables>
<Variable Name="CountStage" DataType="Int32" Namespace="User">0</Variable>
</Variables>
<Tasks>
<ExecuteSQL ConnectionName="STG_<#=Applicatie#>" Name="SQL-Truncate <#=fileName#>">
<DirectInput>TRUNCATE TABLE dbo.<#=Prefix#>_<#=fileName#></DirectInput>
</ExecuteSQL>
<Dataflow Name="DFT-Transport CSV_<#=fileName#>">
<Transformations>
<FlatFileSource Name="SRC_FF-<#=fileName#> " ConnectionName="FF_CSV-<#=Path.GetFileNameWithoutExtension(filePath)#>">
</FlatFileSource>
<OleDbDestination ConnectionName="STG_<#=Applicatie#>" Name="OLE_DST-<#=fileName#>" >
<ExternalTableOutput Table="dbo.<#=Prefix#>_<#=fileName#>"/>
</OleDbDestination>
</Transformations>
</Dataflow>
</Tasks>
</Package>
<# } #>
<!-- Create Master Package -->
<Package Name="stg_<#=Prefix#>_0_Master" ConstraintMode="Parallel" AutoCreateConfigurationsType="None" ProtectionLevel="EncryptSensitiveWithPassword" PackagePassword="secret">
<Tasks>
<# int TableCount2 = 0;
foreach (string filePath in myFiles)
{
TableCount2++;
fileName = Path.GetFileNameWithoutExtension(filePath); #>
<ExecutePackage Name="stg_<#=Prefix#>_<#=TableCount2.ToString()#>_<#=fileName#>">
<ExternalProjectPackage Package="stg_<#=Prefix#>_<#=TableCount2.ToString()#>_<#=fileName#>.dtsx" />
</ExecutePackage>
<#
}
#>
</Tasks>
</Package>
</Packages>
</Biml>
<!--Includes/Imports for C#-->
<#@ template language="C#" hostspecific="true"#>
<#@ import namespace="System.Data"#>
<#@ import namespace="System.IO"#>
我看到你用实际代码编辑了它,但问题的症结在于你正在寻找 ExternalDataAccess.GetDataTable method. Also mentioned here Foreach datarow filter in BIML 奇怪的是我没有回答它...
该方法填充数据 table,然后您可以将其作为二维数组使用。 Rows 属性 允许您枚举行,然后每个 DataRow 都是一个可为空的对象数组。是的,即使它在数据库中有一个类型,您也必须显式转换为其他类型才能使用数据。
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<#
// Could also pull this from this.RootNodes.Connections
string connectionStringSource = @"Server=.\dev2017;Initial Catalog=tempdb;Integrated Security=SSPI;Provider=SQLNCLI11.1;";
string query = @"SELECT
FTI.id
, FTI.path
, FTI.importfilename
, FTI.dest_server
, FTI.dest_db
, FTI.dest_schema
, FTI.refreshtype
, FTI.separator
, FTI.defaultfieldtype
, FTI.[order]
FROM
config.FilesToImport AS FTI;";
DataTable dt = null;
dt = ExternalDataAccess.GetDataTable(connectionStringSource, query);
foreach(DataRow row in dt.Rows)
{
// Downside to data table is everything is object so explict casts required
WriteLine($"<!-- {row[0]},{row[1]},{row[2]} etc -->");
}
#>
<FileFormats>
<#
// Enumerate through the data table to define FFCM
foreach(DataRow row in dt.Rows)
{
#>
<FlatFileFormat Name="SO_<#=row[0]#>" RowDelimiter="CRLF" ColumnNamesInFirstDataRow="true" IsUnicode="false"/>
<#
}
#>
</FileFormats>
<Packages>
<#
// Enumerate through the data table to define packages, etc
foreach(DataRow row in dt.Rows)
{
#>
<Package Name="Load_<#=row[5]#>_<#=row[0]#>" />
<#
}
#>
</Packages>
</Biml>
浏览一下您的代码,您可能会对 CallBimlScript 或 CallBimlScriptWithOutput(我博客上的示例)感兴趣。您可以将代码分解为函数(本质上),而不是单一的 BimlScript。在 Replicate-o-matic post 中,我传入了一个模式和一个 table 并且构建单个包实例的所有逻辑都在那里,所以主构建器包只关心构建顶级工件 - 连接、包等,并将工作推给函数。
GetBimlScriptWithOutput 的巧妙之处在于您可以 return 向调用方发送数据。例如,您在创建包时正在构建您的包名称,并且您已经为您的主包重复了该逻辑以执行它们。不要重复自己。相反,让包构建器 return 一个 属性 作为新包的名称(并添加到 array/list)。然后 Orchestrator 包可以简单地获取包名而不知道它们是如何派生的。