如何使用 SQL 表作为源填充 BIML 中的数组

Posted

技术标签:

【中文标题】如何使用 SQL 表作为源填充 BIML 中的数组【英文标题】:how to fill an array in BIML using SQL table as source 【发布时间】:2021-07-03 06:58:17 【问题描述】:

我有一个包含文件名和一些其他属性的表。我想将这些记录插入到 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

所以基本上下面的代码应该是上面提到的表格中的文件名

<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"#>

【问题讨论】:

我觉得我们在这里错过了这个问题。你在问什么?上面的代码不起作用怎么办? 所以你有一个数据表,你需要把它变成你可以在 Biml 中使用的“东西”。您是否需要在 .net 中对类似 sql 的数据做任何“花哨”的操作,或者您只是要遍历数据行然后对它们执行操作? billinkc:理想情况下,它会遍历文件名。文件名将与我们昨天处理的代码一起使用。其他属性将用于 a) 设置与目标服务器的连接,b) 决定现有表是先插入数据还是截断,c) 分隔符应该是什么(不幸的是,每个文件可能会有所不同)。所以在循环中我需要能够根据字段 dest*、refreshtype 和 separator 的值做一些逻辑。 @Larnu 我想将上面表格的内容输入到我可以在 BIML 中循环遍历的内容中 【参考方案1】:

我看到您使用实际代码对其进行了编辑,但问题的关键是您正在寻找 ExternalDataAccess.GetDataTable 方法。这里也提到了Foreach datarow filter in BIML,奇怪的是我没有回答...

该方法会填充一个数据表,然后您可以将其作为二维数组使用。 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 帖子中,我传入了一个模式和一个表,构建单个包实例的所有逻辑都在其中,因此主构建器包只关心构建***工件 - 连接、包等并将工作推向功能。

GetBimlScriptWithOutput 的聪明之处在于您可以将数据返回给调用者。例如,您在创建包时正在构建包名称,并且您已重复该逻辑以让主包执行它们。不要重复自己。相反,让包生成器返回一个属性,该属性是新包的名称(并添加到数组/列表中)。然后,Orchestrator 包可以简单地获取包名,而不知道它们是如何派生的。

https://billfellows.blogspot.com/2015/01/biml-replicate-o-matic.html https://billfellows.blogspot.com/2018/10/biml-excel-data-source-without-excel.html

【讨论】:

很长一段时间以来,我都尝试将Biml implementation 添加到我所有的 SSIS 答案中,因此如果您正在寻找如何做 X,可能会有一些奇怪的事情发生。

以上是关于如何使用 SQL 表作为源填充 BIML 中的数组的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 SQL 中的 URI 数据类型列填充数据表?

根据输入参数使用日期时间值自动填充 SQL 表字段

php - 如何生成 MySQL INSERT 语句以使用数组中的多行填充表

sql 在SSIS,BIML,SQL和C#之间映射数据类型

MS SQL:如果记录数超过 N,则执行截断

BIML 创建错误的元数据