Sql server SSIS自定义数据流组件循环输入管道缓冲区多次
我仍在学习如何在SSIS中创建自定义组件。假设我有一个500行的输入行计数,我需要从输入管道缓冲区一次读取/批处理/处理100行,然后将它们发送给第三方应用程序,一旦得到结果,我需要用新数据更新管道缓冲区列,然后读取/批处理/处理接下来的100行,依此类推,直到我处理完所有500行 我的问题是,我是否可以多次循环/读取输入管道缓冲区,以便使用第三方应用程序返回的数据更新缓冲区 我想我读到了可以读入所有数据并将其存储到缓存中,然后对数据进行排序,但我不确定如何将数据从缓存返回到输出。我也不确定应该在哪里做,以及如何访问输入管道缓冲区、PrimeOutput或ProcessInput或其他我不知道的重写方法 我试图创建一个定制的异步数据流组件来解决这个问题 任何帮助或想法都将不胜感激,并/或为我指明正确的方向Sql server SSIS自定义数据流组件循环输入管道缓冲区多次,sql-server,ssis,Sql Server,Ssis,我仍在学习如何在SSIS中创建自定义组件。假设我有一个500行的输入行计数,我需要从输入管道缓冲区一次读取/批处理/处理100行,然后将它们发送给第三方应用程序,一旦得到结果,我需要用新数据更新管道缓冲区列,然后读取/批处理/处理接下来的100行,依此类推,直到我处理完所有500行 我的问题是,我是否可以多次循环/读取输入管道缓冲区,以便使用第三方应用程序返回的数据更新缓冲区 我想我读到了可以读入所有数据并将其存储到缓存中,然后对数据进行排序,但我不确定如何将数据从缓存返回到输出。我也不确定应该
谢谢 我很高兴我没有尝试徒手写这篇文章,因为我忘记了很多好的观点 这里有几点值得注意:我的两个数据结构
InData
和OutData
,您需要对它们进行配置,以跟踪输入/输出缓冲区中的内容。正如注释所述,可能有一种聪明的方法可以克隆缓冲区对象的属性,但我不知道如何克隆。定义这些列以匹配数据流中的数据类型,如果您像我一样懒惰,请使用相同的列名,您可以复制/粘贴您的成功之路
ApiCall
是一个虚拟方法,它使用缓存的值来请求数据清理服务完成它的工作。它需要返回清理后的数据,以便我们可以将输入和输出合并到一个统一的行中。可能有更好的方法可以做到这一点,但希望它足以激发你的思维过程
我创建了一个SSIS级别变量,@[User::ApiBatchSize]
,您可以将其初始化为500。使用此方法将允许您在不更改核心代码的情况下优化发送的批大小。我在PreExecute
方法中初始化本地成员变量,因为这是脚本组件的构造函数
通常,在异步脚本组件中,您使用的是ProcessInputRow
方法,这是我最初使用的方法,但如果列表的大小是APIBACHTSIZE的偶数倍,则最终批处理会遇到问题。结果是,在该方法中,EndOfRowset()
从未设置为True。不用担心,我们只需要使用ProcessInput
方法。在“正常”情况下,process input方法会导致process input行处理一行,因此我们将跳过中间人,直接使用process input中的缓冲区。我很懒,没有将我的行
引用重命名为缓冲区
,因为自动生成的代码最初处理了参数
这里的伪逻辑是
- 当有数据行时
-
- 如果我们达到了批量大小,请发送数据收集以进行处理
-
-
- 对于每个已处理的行,向输出缓冲区添加一行,并用干净的数据填充它
-
-
- 清空我们的收集桶(已经发送到下游)
- 将当前行添加到我们的收集桶中
使用系统;
使用系统数据;
使用System.Collections.Generic;
使用Microsoft.SqlServer.Dts.Pipeline.Wrapper;
使用Microsoft.SqlServer.Dts.Runtime.Wrapper;
///
///有一种聪明的方法可以重用输入/输出缓冲区中的元数据
///定义,但我不知道如何访问它,所以我在这里重新定义它
///
公共结构InData
{
公共字符串AddressLine1{get;set;}
}
///
///有一种聪明的方法可以重用输入/输出缓冲区中的元数据
///定义,但我不知道如何访问它,所以我在这里重新定义它
///
公共结构输出数据
{
公共字符串AddressLine1Clean{get;set;}
公共字符串AddressCityClean{get;set;}
公共字符串AddressStateClean{get;set;}
公共字符串地址PostalCodeClean{get;set;}
}
///
///这是要向其中添加代码的类。不要更改名称、属性或父级
///这个班的学生。
///
[Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
公共类ScriptMain:UserComponent
{
列出mData;
国际工商管理学院;
///
///在数据流中开始处理行之前,调用此方法一次。
///
///如果不需要在此处执行任何操作,可以删除此方法。
///
公共覆盖无效预执行()
{
base.PreExecute();
this.mData=新列表();
this.mBatchSize=this.Variables.ApiBatchSize;
}
///
///此方法在所有行都通过此组件后调用。
///
///如果不需要在此处执行任何操作,可以删除此方法。
///
公共重写void PostExecute()
{
base.PostExecute();
}
///
///我们将使用ProcessInput,而不是ProcessInputRow
///“更接近裸金属”,我们需要它
///
///
公共覆盖无效Input0\u ProcessInput(Input0Buffer行)
{
//base.Input0\u ProcessInput(缓冲区);
while(Row.NextRow())
{
如果(this.mData.Count>=this.mBatchSize)
{
foreach(ApiCall()中的变量项)
{
Output0Buffer.AddRow();
var inRow=项。键;
var outRow=项目价值;
//f
using System;
using System.Data;
using System.Collections.Generic;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using Microsoft.SqlServer.Dts.Runtime.Wrapper;
/// <summary>
/// There might be a clever way to re-use the metadata from the Input/OutputBuffer
/// definition but I don't know how to access it so I redefine it here
/// </summary>
public struct InData
{
public string AddressLine1 { get; set; }
}
/// <summary>
/// There might be a clever way to re-use the metadata from the Input/OutputBuffer
/// definition but I don't know how to access it so I redefine it here
/// </summary>
public struct OutData
{
public string AddressLine1Clean { get; set; }
public string AddressCityClean { get; set; }
public string AddressStateClean { get; set; }
public string AddressPostalCodeClean { get; set; }
}
/// <summary>
/// This is the class to which to add your code. Do not change the name, attributes, or parent
/// of this class.
/// </summary>
[Microsoft.SqlServer.Dts.Pipeline.SSISScriptComponentEntryPointAttribute]
public class ScriptMain : UserComponent
{
List<InData> mData;
int mBatchSize;
/// <summary>
/// This method is called once, before rows begin to be processed in the data flow.
///
/// You can remove this method if you don't need to do anything here.
/// </summary>
public override void PreExecute()
{
base.PreExecute();
this.mData = new List<InData>();
this.mBatchSize = this.Variables.ApiBatchSize;
}
/// <summary>
/// This method is called after all the rows have passed through this component.
///
/// You can delete this method if you don't need to do anything here.
/// </summary>
public override void PostExecute()
{
base.PostExecute();
}
/// <summary>
/// We're going to work with ProcessInput versus PorcessInputRow as it is
/// "closer to the bare metal" and we need that
/// </summary>
/// <param name="Buffer"></param>
public override void Input0_ProcessInput(Input0Buffer Row)
{
//base.Input0_ProcessInput(Buffer);
while (Row.NextRow())
{
if (this.mData.Count >= this.mBatchSize)
{
foreach (var item in ApiCall())
{
Output0Buffer.AddRow();
var inRow = item.Key;
var outRow = item.Value;
// fill columns with original data
Output0Buffer.AddressLine1 = inRow.AddressLine1;
// etc
// fill columns with clean data
Output0Buffer.AddressLine1Clean = outRow.AddressLine1Clean;
Output0Buffer.AddressCityClean = outRow.AddressCityClean;
Output0Buffer.AddressStateClean = outRow.AddressStateClean;
Output0Buffer.AddressPostalCodeClean = outRow.AddressPostalCodeClean;
// etc
}
// TODO Remove this for production, just ensuring batching is working as intended
bool fireAgain = false;
string status = "Batch released. Conditions => mDataCount := " + this.mData.Count;
this.ComponentMetaData.FireInformation(0, "ApiProcessing", status, "", 0, ref fireAgain);
// Reset for next iteration
this.mData.Clear();
}
this.mData.Add(new InData() { AddressLine1 = Row.AddressLine1 });
}
// Handle the final possible partial batch
if (this.mData.Count > 0)
{
foreach (var item in ApiCall())
{
Output0Buffer.AddRow();
var inRow = item.Key;
var outRow = item.Value;
// fill columns with original data
Output0Buffer.AddressLine1 = inRow.AddressLine1;
// etc
// fill columns with clean data
Output0Buffer.AddressLine1Clean = outRow.AddressLine1Clean;
Output0Buffer.AddressCityClean = outRow.AddressCityClean;
Output0Buffer.AddressStateClean = outRow.AddressStateClean;
Output0Buffer.AddressPostalCodeClean = outRow.AddressPostalCodeClean;
// etc
}
// TODO Remove this for production, just ensuring batching is working as intended
bool fireAgain = false;
string status = "Final batch released. Conditions => mDataCount := " + this.mData.Count;
this.ComponentMetaData.FireInformation(0, "ApiProcessing", status, "", 0, ref fireAgain);
// Reset for next iteration
this.mData.Clear();
}
}
///// <summary>
///// This method is called once for every row that passes through the component from Input0.
///// We need to preserve rows in our own memory allocation
///// We're not getting the EndOfRowset call in time to release the final
///// </summary>
///// <param name="Row">The row that is currently passing through the component</param>
//public override void Input0_ProcessInputRow(Input0Buffer Row)
//{
//}
public override void CreateNewOutputRows()
{
// I don't think we need to do anything special here
// but I'm leaving it in in case you have some weird case
}
/// <summary>
/// Simulate data cleaning
/// </summary>
/// <returns></returns>
public Dictionary<InData, OutData> ApiCall()
{
int macGuffin = 0;
Dictionary<InData, OutData> cleanData = new Dictionary<InData, OutData>();
foreach (var item in this.mData)
{
cleanData.Add(item, new OutData() { AddressLine1Clean = "Clean" + item.AddressLine1, AddressCityClean = "Clean", AddressPostalCodeClean = "12345-1234", AddressStateClean = "CL" });
macGuffin = macGuffin % this.mBatchSize;
}
return cleanData;
}
}