Från att undersöka hur ForEach-slingan fungerar i SSIS (i syfte att skapa en egen för att lösa problemet) verkar det som det fungerar (såvitt jag kunde se i alla fall) är att räkna upp filsamlingen först, innan någon mask specificerad. Det är svårt att säga exakt vad som händer utan att se den underliggande koden för ForEach-slingan, men det verkar göra det på detta sätt, vilket resulterar i långsam prestanda när man hanterar över 100 000 filer.
Även om @Sivas lösning är fantastiskt detaljerad och definitivt en förbättring jämfört med mitt ursprungliga tillvägagångssätt, är det i princip precis samma process, förutom att använda en uttrycksuppgift för att testa filnamnet, snarare än en skriptuppgift (detta verkar ge en viss förbättring).
Så jag bestämde mig för att ta ett helt annat tillvägagångssätt och istället för att använda en filbaserad ForEach-loop, räkna upp samlingen själv i en skriptuppgift, tillämpa min filtreringslogik och sedan iterera över de återstående resultaten. Det här är vad jag gjorde:
I min skriptuppgift använder jag den asynkrona DirectoryInfo.EnumerateFiles
metod, vilket är det rekommenderade tillvägagångssättet för stora filsamlingar, eftersom det tillåter streaming, snarare än att behöva vänta på att hela samlingen ska skapas innan någon logik tillämpas.
Här är koden:
public void Main()
{
string sourceDir = Dts.Variables["SourceDirectory"].Value.ToString();
int minJobId = (int)Dts.Variables["MinIndexId"].Value;
//Enumerate file collection (using Enumerate Files to allow us to start processing immediately
List<string> activeFiles = new List<string>();
System.Threading.Tasks.Task listTask = System.Threading.Tasks.Task.Factory.StartNew(() =>
{
DirectoryInfo dir = new DirectoryInfo(sourceDir);
foreach (FileInfo f in dir.EnumerateFiles("*.txt"))
{
FileInfo file = f;
string filePath = file.FullName;
string fileName = filePath.Substring(filePath.LastIndexOf("\\") + 1);
int jobId = Convert.ToInt32(fileName.Substring(0, fileName.IndexOf(".txt")));
if (jobId > minJobId)
activeFiles.Add(filePath);
}
});
//Wait here for completion
System.Threading.Tasks.Task.WaitAll(new System.Threading.Tasks.Task[] { listTask });
Dts.Variables["ActiveFilenames"].Value = activeFiles;
Dts.TaskResult = (int)ScriptResults.Success;
}
Så jag räknar upp samlingen, tillämpar min logik när filer upptäcks och lägger omedelbart till sökvägen till min lista för utdata. När det är klart tilldelar jag detta till en SSIS-objektvariabel med namnet ActiveFilenames som jag kommer att använda som samlingen för min ForEach-loop.
Jag konfigurerade ForEach-slingan som en ForEach From Variable Enumerator , som nu itererar över en mycket mindre samling (efterfiltrerad List<string>
jämfört med vad jag bara kan anta var en ofiltrerad List<FileInfo>
eller något liknande i SSIS inbyggda ForEach File Enumerator .
Så uppgifterna i min loop kan bara ägnas åt att bearbeta data, eftersom den redan har filtrerats innan den träffar loopen. Även om det inte verkar skilja sig mycket från varken mitt initiala paket eller Sivas exempel, i produktionen (för det här speciella fallet i alla fall) verkar det som att filtrera samlingen och räkna upp asynkront ger en enorm ökning jämfört med att använda den inbyggda ForEach File Enumerator.
Jag kommer att fortsätta undersöka ForEach-loopbehållaren och se om jag kan replikera denna logik i en anpassad komponent. Om jag får det här att fungera så lägger jag upp en länk i kommentarerna.