Hero image for Goodbye For Loops: 10x Your RPA Performance with LINQ and RegEx

Goodbye For Loops: 10x Your RPA Performance with LINQ and RegEx

RPA UiPath LINQ RegEx Data Manipulation

Goodbye For Loops: 10x Your RPA Performance with LINQ and RegEx

There’s a moment in every RPA developer’s journey when they realize: Recording clicks was just the beginning. The real complexity isn’t in clicking buttons—it’s in handling data. Filtering thousands of rows. Extracting patterns from messy text. Transforming data between formats. This is where amateurs struggle and professionals shine. Today, we’re going to level up your data manipulation skills with two powerful tools: LINQ and Regular Expressions.

The Problem with Traditional Loops

Let’s start with a common scenario. You have an Excel file with 10,000 invoices. You need to find all invoices from vendor “ACME Corp” with amounts greater than $1,000.

The Beginner’s Approach

' Initialize empty list
filteredInvoices = New List(Of DataRow)
' Loop through all rows
For Each row As DataRow In invoiceTable.Rows
    If row("Vendor").ToString() = "ACME Corp" Then
        If Convert.ToDecimal(row("Amount")) > 1000 Then
            filteredInvoices.Add(row)
        End If
    End If
Next

This works. But it has problems:

IssueImpact
Verbose8 lines for a simple filter
Error-proneEasy to mess up nested conditions
Hard to modifyAdding criteria requires restructuring
Type unsafeManual casting for each field
DBNull crashesrow("Amount").ToString() fails on null
SlowIterates through every single row

The LINQ Approach

filteredInvoices = invoiceTable.AsEnumerable() _
    .Where(Function(row) row.Field(Of String)("Vendor") = "ACME Corp" _
           AndAlso row.Field(Of Decimal)("Amount") > 1000) _
    .CopyToDataTable()

Same result. One line. Type-safe. And often faster due to internal optimizations.

The Professional Way: Field(Of T) vs ToString()

Before diving deeper, let’s address the single most important syntax choice in LINQ for DataTables.

The Amateur Approach

' Dangerous: Will crash on DBNull
vendor = row("Vendor").ToString()
amount = Convert.ToDecimal(row("Amount"))

The Professional Approach

' Safe: Handles DBNull, provides compile-time type checking
vendor = row.Field(Of String)("Vendor")
amount = row.Field(Of Decimal)("Amount")

Why Field(Of T) is Superior

Aspectrow("Col").ToString()row.Field(Of T)("Col")
DBNull handling❌ Crashes✅ Returns Nothing/default
Type safety❌ Runtime errors✅ Compile-time checking
Null support❌ Manual checks needed✅ Works with Nullable(Of T)
Performance❌ Boxing overhead✅ Direct type access
Readability❌ Casts scattered✅ Type clear at glance

Handling Nullable Fields

' For columns that might be DBNull
amount = row.Field(Of Decimal?)("Amount")  ' Returns Nothing if DBNull
' Safe access with default value
safeAmount = If(row.Field(Of Decimal?)("Amount"), 0D)
' Check before use
If row.Field(Of Decimal?)("Amount").HasValue Then
    ProcessAmount(row.Field(Of Decimal)("Amount"))
End If
' Ultimate trick: GetValueOrDefault for cleaner code
amount = row.Field(Of Decimal?)("Amount").GetValueOrDefault(0D)

Rule: Always use row.Field(Of T)("ColumnName") in production code. Reserve row("Col").ToString() for quick prototyping only.


LINQ Fundamentals for RPA

LINQ (Language Integrated Query) is a query language built into .NET. Think of it as SQL for your in-memory data.

How LINQ Works: The Pipeline

┌─────────────────────────────────────────────────────────────────┐
│                    LINQ Query Pipeline                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  DataTable                                                       │
│     │                                                            │
│     ▼                                                            │
│  .AsEnumerable()  ──→  Converts to IEnumerable<DataRow>         │
│     │                                                            │
│     ▼                                                            │
│  .Where(...)      ──→  Filter: Keep rows matching condition     │
│     │                                                            │
│     ▼                                                            │
│  .OrderBy(...)    ──→  Sort: Arrange by specified field         │
│     │                                                            │
│     ▼                                                            │
│  .Select(...)     ──→  Transform: Project to new shape          │
│     │                                                            │
│     ▼                                                            │
│  .Take(N)         ──→  Limit: Get first N results               │
│     │                                                            │
│     ▼                                                            │
│  .CopyToDataTable() ──→  Convert back to DataTable              │
│                                                                  │
│  Each step is LAZY: Only executes when results are needed       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

[!NOTE] Deferred Execution (Lazy Evaluation): LINQ queries don’t execute immediately! When you write .Where(...), nothing happens yet. The “factory” only starts running when you call a terminal operation like .CopyToDataTable(), .ToList(), or .Count(). This means you can build complex queries step by step without performance penalty — the entire pipeline executes in a single pass when you finally need the results.

Core LINQ Methods

🔍 Filtering & Searching:

MethodPurposeExample
WhereFilter rows.Where(Function(r) r.Field(Of String)("Status") = "Active")
FirstGet first match.First(Function(r) r.Field(Of Integer)("ID") = 123)
FirstOrDefaultFirst or Nothing.FirstOrDefault(Function(r) r.Field(Of Integer)("ID") = 123)
AnyCheck if exists.Any(Function(r) r.Field(Of String)("Status") = "Error")
AllCheck all match.All(Function(r) r.Field(Of Decimal)("Amount") > 0)
🔄 Transformation & Projection:
MethodPurposeExample
--------------------------
SelectTransform/project.Select(Function(r) r.Field(Of String)("Name"))
DistinctRemove duplicates.Select(Function(r) r.Field(Of String)("Category")).Distinct()
GroupByGroup by key.GroupBy(Function(r) r.Field(Of String)("Department"))
📊 Sorting & Limiting:
MethodPurposeExample
--------------------------
OrderBySort ascending.OrderBy(Function(r) r.Field(Of DateTime)("Date"))
OrderByDescendingSort descending.OrderByDescending(Function(r) r.Field(Of Decimal)("Amount"))
TakeGet first N items.Take(10)
SkipSkip first N items.Skip(5)
FirstGet first match.First(Function(r) r.Field(Of Integer)("ID") = 123)
FirstOrDefaultFirst or Nothing.FirstOrDefault(Function(r) r.Field(Of Integer)("ID") = 123)
AnyCheck if exists.Any(Function(r) r.Field(Of String)("Status") = "Error")
AllCheck all match.All(Function(r) r.Field(Of Decimal)("Amount") > 0)
🧮 Aggregation:
MethodPurposeExample
--------------------------
CountCount matches.Count(Function(r) r.Field(Of String)("Type") = "Invoice")
SumCalculate sum.Sum(Function(r) r.Field(Of Decimal)("Amount"))
AverageCalculate average.Average(Function(r) r.Field(Of Decimal)("Price"))
DistinctRemove duplicates.Select(Function(r) r.Field(Of String)("Category")).Distinct()
GroupByGroup by key.GroupBy(Function(r) r.Field(Of String)("Department"))

Real-World LINQ Examples

Example 1: Find Duplicate Invoice Numbers

' Find invoice numbers that appear more than once
duplicates = invoiceTable.AsEnumerable() _
    .GroupBy(Function(r) r.Field(Of String)("InvoiceNumber")) _
    .Where(Function(g) g.Count() > 1) _
    .Select(Function(g) g.Key) _
    .ToList()

Example 2: Calculate Sum by Department

' Total amount per department
departmentTotals = invoiceTable.AsEnumerable() _
    .GroupBy(Function(r) r.Field(Of String)("Department")) _
    .Select(Function(g) New With {
        .Department = g.Key,
        .Total = g.Sum(Function(r) r.Field(Of Decimal)("Amount"))
    }) _
    .ToList()

Example 3: Join Two DataTables

' Combine invoices with vendor details
joinedData = From invoice In invoiceTable.AsEnumerable()
             Join vendor In vendorTable.AsEnumerable()
             On invoice.Field(Of String)("VendorID") Equals vendor.Field(Of String)("ID")
             Select New With {
                 .InvoiceNumber = invoice.Field(Of String)("InvoiceNumber"),
                 .Amount = invoice.Field(Of Decimal)("Amount"),
                 .VendorName = vendor.Field(Of String)("Name"),
                 .VendorCountry = vendor.Field(Of String)("Country")
             }

Example 4: Top 10 Highest Value Transactions

top10 = invoiceTable.AsEnumerable() _
    .OrderByDescending(Function(r) r.Field(Of Decimal)("Amount")) _
    .Take(10) _
    .CopyToDataTable()

Example 5: Filter with Multiple Conditions

' Active vendors from USA with amount > 1000, sorted by date
result = invoiceTable.AsEnumerable() _
    .Where(Function(r) 
        r.Field(Of String)("Status") = "Active" AndAlso _
        r.Field(Of String)("Country") = "USA" AndAlso _
        r.Field(Of Decimal)("Amount") > 1000
    ) _
    .OrderBy(Function(r) r.Field(Of DateTime)("Date")) _
    .CopyToDataTable()

Common LINQ Gotchas in UiPath

Gotcha 1: Empty Results Break CopyToDataTable()

' This will CRASH if no rows match
result = dt.AsEnumerable() _
    .Where(Function(r) r("Type") = "Nonexistent") _
    .CopyToDataTable()  ' ERROR: No DataRows!

Solution: Check for results first.

filtered = dt.AsEnumerable().Where(Function(r) r.Field(Of String)("Type") = "Rare")
If filtered.Any() Then
    result = filtered.CopyToDataTable()
Else
    result = dt.Clone()  ' Empty table with same structure
End If

Gotcha 2: Null Values Cause Exceptions

' Crashes if "Amount" is DBNull (even with Field(Of T)!)
total = dt.AsEnumerable().Sum(Function(r) r.Field(Of Decimal)("Amount"))

Solution: Use Nullable types.

' Option 1: Use Nullable(Of Decimal) and filter
total = dt.AsEnumerable() _
    .Where(Function(r) r.Field(Of Decimal?)("Amount").HasValue) _
    .Sum(Function(r) r.Field(Of Decimal)("Amount"))
' Option 2: Coalesce nulls to zero
total = dt.AsEnumerable() _
    .Sum(Function(r) If(r.Field(Of Decimal?)("Amount"), 0D))

Gotcha 3: Case Sensitivity

' Won't match "active" or "ACTIVE"
filtered = dt.AsEnumerable() _
    .Where(Function(r) r.Field(Of String)("Status") = "Active")

Solution: Use case-insensitive comparison.

' Best practice: StringComparison
filtered = dt.AsEnumerable() _
    .Where(Function(r) String.Equals(r.Field(Of String)("Status"), "Active", 
                                     StringComparison.OrdinalIgnoreCase))
' Alternative: ToUpperInvariant (culture-safe)
filtered = dt.AsEnumerable() _
    .Where(Function(r) r.Field(Of String)("Status")?.ToUpperInvariant() = "ACTIVE")

Regular Expressions: Pattern Matching on Steroids

If LINQ is your tool for structured data, RegEx is your tool for unstructured text. Regular Expressions let you:

  • Extract data from messy strings
  • Validate format (emails, phone numbers, IDs)
  • Replace patterns
  • Split text intelligently

When to Use RegEx

ScenarioRegEx Pattern
Extract email from text[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
Find invoice numbersINV-\d{4}-\d{6}
Extract dates\d{2}/\d{2}/\d{4}
Parse currency amounts\$[\d,]+\.\d{2}
Get phone numbers\(\d{3}\)\s?\d{3}-\d{4}

RegEx Fundamentals

PatternMeaningExample Match
\dAny digit5
\DAny non-digitA
\wWord character (letter, digit, underscore)a, 5, _
\sWhitespace , \t, \n
.Any character except newlinea, 5, !
+One or more\d+ matches 123
*Zero or more\d* matches “ or 123
?Zero or one\d? matches “ or 5
{n}Exactly n times\d{4} matches 2024
{n,m}Between n and m times\d{2,4} matches 12 or 1234
[abc]Character class[aeiou] matches vowels
[^abc]Negated class[^0-9] matches non-digits
^Start of string^Hello matches “Hello world”
$End of stringworld$ matches “Hello world”
( )Capture group(\d{4}) captures year
|Alternation (OR)cat|dog matches either

RegEx in UiPath: Practical Examples

Example 1: Extract All Email Addresses

' Input: "Contact john@acme.com or support@acme.com for help"
pattern = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
emails = System.Text.RegularExpressions.Regex.Matches(inputText, pattern) _
    .Cast(Of System.Text.RegularExpressions.Match)() _
    .Select(Function(m) m.Value) _
    .ToList()
' Result: ["john@acme.com", "support@acme.com"]

Example 2: Extract Invoice Number

' Input: "Please process invoice INV-2024-001234 by Friday"
pattern = "INV-\d{4}-\d{6}"
match = System.Text.RegularExpressions.Regex.Match(inputText, pattern)
invoiceNumber = If(match.Success, match.Value, Nothing)
' Result: "INV-2024-001234"

Example 3: Parse Currency Amount

' Input: "Total: $12,345.67"
pattern = "\$[\d,]+\.\d{2}"
match = System.Text.RegularExpressions.Regex.Match(inputText, pattern)
amountString = match.Value.Replace("$", "").Replace(",", "")
amount = Convert.ToDecimal(amountString)
' Result: 12345.67

Example 4: Validate Email Format

' Input: "user@domain.com"
pattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
isValidEmail = System.Text.RegularExpressions.Regex.IsMatch(inputText, pattern)
' Result: True

Example 5: Extract Data with Capture Groups

' Input: "Order #12345 from John Doe on 01/15/2024"
pattern = "Order #(\d+) from (.+) on (\d{2}/\d{2}/\d{4})"
match = System.Text.RegularExpressions.Regex.Match(inputText, pattern)
If match.Success Then
    orderNumber = match.Groups(1).Value  ' "12345"
    customerName = match.Groups(2).Value ' "John Doe"
    orderDate = match.Groups(3).Value    ' "01/15/2024"
End If

[!TIP] RegexOptions.Compiled for Performance: When processing thousands of rows in a loop, compile your regex pattern once for significant speed gains:

' Compile once, reuse many times
Dim compiledRegex = New System.Text.RegularExpressions.Regex(pattern, System.Text.RegularExpressions.RegexOptions.Compiled)
For Each row In dataTable.Rows
    match = compiledRegex.Match(row("Text").ToString())
Next

Example 6: Replace Sensitive Data

' Mask credit card numbers
' Input: "Card: 4532-1234-5678-9012"
pattern = "(\d{4}-)\d{4}-\d{4}-(\d{4})"
replacement = "$1****-****-$2"
maskedText = System.Text.RegularExpressions.Regex.Replace(inputText, pattern, replacement)
' Result: "Card: 4532-****-****-9012"

Combining LINQ and RegEx

The real power comes when you combine these tools.

Example: Filter DataTable by RegEx Pattern

' Find all rows where Email column contains valid email format
pattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
validEmails = dt.AsEnumerable() _
    .Where(Function(r) 
        System.Text.RegularExpressions.Regex.IsMatch(
            r("Email").ToString(), 
            pattern
        )
    ) _
    .CopyToDataTable()

Example: Extract and Aggregate

' Parse amounts from text column and sum them
' Text like "Invoice total: $1,234.56"
pattern = "\$[\d,]+\.\d{2}"
totalAmount = dt.AsEnumerable() _
    .Select(Function(r) 
        Dim match = System.Text.RegularExpressions.Regex.Match(r("Description").ToString(), pattern)
        If match.Success Then
            Return Convert.ToDecimal(match.Value.Replace("$", "").Replace(",", ""))
        Else
            Return 0D
        End If
    ) _
    .Sum()

Collections: List and Dictionary

Beyond DataTables, you’ll often work with Lists and Dictionaries.

List: Ordered Collection

' Create a list
invoiceNumbers = New List(Of String) From {"INV-001", "INV-002", "INV-003"}
' Add items
invoiceNumbers.Add("INV-004")
' Check if exists
exists = invoiceNumbers.Contains("INV-001")  ' True
' LINQ on Lists
highPriority = invoiceNumbers.Where(Function(inv) inv.StartsWith("INV-00")).ToList()

Dictionary: Key-Value Pairs

' Create a dictionary
vendorMap = New Dictionary(Of String, String) From {
    {"V001", "Acme Corp"},
    {"V002", "Globex Inc"},
    {"V003", "Initech LLC"}
}
' Access by key
vendorName = vendorMap("V001")  ' "Acme Corp"
' Safe access (avoids exception if key missing)
If vendorMap.TryGetValue("V999", vendorName) Then
    ' Found
Else
    ' Not found
End If
' Add new entry
vendorMap.Add("V004", "Umbrella Corp")
' Check if key exists
hasVendor = vendorMap.ContainsKey("V001")  ' True

When to Use Each

Use CaseBest Collection
Tabular data (rows and columns)DataTable
Ordered list of same typeList(Of T)
Key-value lookupsDictionary(Of K, V)
Unique items onlyHashSet(Of T)
Queue processingQueue(Of T)

Dictionary as Lookup Accelerator

One of the most powerful yet underused patterns in RPA: using Dictionary to accelerate comparisons between two Excel files.

The Problem: Comparing Two Large Excel Files

Classic scenario: Compare MasterList.xlsx (10,000 rows) with DailyReport.xlsx (500 rows) to find matches.

┌─────────────────────────────────────────────────────────────────┐
│           The Slow Way vs The Fast Way                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   SLOW: Nested Loop                  FAST: Dictionary Lookup    │
│   ─────────────────                  ──────────────────────     │
│                                                                  │
│   For Each daily In dailyTable       ' Build index once         │
│       For Each master In masterTable masterDict = masterTable   │
│           If daily("ID") = master("ID")    .ToDictionary(...)  │
│               ' Match found!                                     │
│           End If                     For Each daily In daily    │
│       Next                               If masterDict          │
│   Next                                      .ContainsKey(id)    │
│                                              ' Match found!     │
│   Comparisons: 500 × 10,000         Comparisons: 500 + 10,000  │
│             = 5,000,000                        = 10,500         │
│                                                                  │
│   O(n × m) = SLOW                    O(n + m) = FAST            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

[!NOTE] Why is Dictionary So Fast? Dictionary uses Hash Table technology. When you lookup an ID, it doesn’t search from the beginning — it calculates a hash value (a mathematical fingerprint) that points directly to the data location. This gives you O(1) constant time lookup regardless of how many items are in the Dictionary. Finding 1 item in 10 vs 10 million takes the same time!

Building a Lookup Dictionary from DataTable

' Create Dictionary from MasterList - key = ID, value = entire row
masterLookup = masterTable.AsEnumerable() _
    .ToDictionary(
        Function(r) r.Field(Of String)("ID"),  ' Key selector
        Function(r) r                           ' Value = entire row
    )

Example: Find Matching Records

' Step 1: Build dictionary from master list (O(n) - runs once)
masterLookup = masterTable.AsEnumerable() _
    .ToDictionary(Function(r) r.Field(Of String)("CustomerID"))
' Step 2: Iterate daily records and lookup (O(1) per lookup)
For Each dailyRow In dailyTable.AsEnumerable()
    customerId = dailyRow.Field(Of String)("CustomerID")
    
    Dim masterRow As DataRow = Nothing
    If masterLookup.TryGetValue(customerId, masterRow) Then
        ' MATCH FOUND - compare fields
        dailyAmount = dailyRow.Field(Of Decimal)("Amount")
        masterAmount = masterRow.Field(Of Decimal)("Amount")
        
        If dailyAmount <> masterAmount Then
            LogDiscrepancy(customerId, dailyAmount, masterAmount)
        End If
    Else
        ' New customer - not in master
        LogNewCustomer(dailyRow)
    End If
Next

Example: Three-Way Reconciliation

' Scenario: Match Invoice → PO → Goods Receipt
' Build lookup dictionaries
poLookup = poTable.AsEnumerable() _
    .ToDictionary(Function(r) r.Field(Of String)("PONumber"))
grLookup = grTable.AsEnumerable() _
    .GroupBy(Function(r) r.Field(Of String)("PONumber")) _
    .ToDictionary(
        Function(g) g.Key,
        Function(g) g.ToList()  ' Multiple GRs per PO
    )
' Process invoices
For Each invoice In invoiceTable.AsEnumerable()
    poNumber = invoice.Field(Of String)("PONumber")
    
    ' Check PO exists
    If Not poLookup.ContainsKey(poNumber) Then
        LogException(invoice, "PO Not Found")
        Continue For
    End If
    
    ' Check GR exists
    If Not grLookup.ContainsKey(poNumber) Then
        LogException(invoice, "No Goods Receipt")
        Continue For
    End If
    
    ' Compare amounts (O(1) lookups!)
    poAmount = poLookup(poNumber).Field(Of Decimal)("Amount")
    grTotal = grLookup(poNumber).Sum(Function(r) r.Field(Of Decimal)("Amount"))
    invAmount = invoice.Field(Of Decimal)("Amount")
    
    If ThreeWayMatch(invAmount, poAmount, grTotal) Then
        ApproveForPayment(invoice)
    Else
        LogDiscrepancy(invoice, poAmount, grTotal)
    End If
Next

Performance Impact

ScenarioNested LoopDictionarySpeedup
100 × 1,000100,000 ops1,100 ops90x
500 × 10,0005,000,000 ops10,500 ops476x
1,000 × 100,000100,000,000 ops101,000 ops990x

Pro Tip: When you need to compare, match, or lookup between two data sets, always think Dictionary first.


Performance Comparison

Let’s see the real impact of these techniques.

Test: Filter 100,000 Rows

ApproachTimeLines of Code
For Each loop450ms12
LINQ query85ms2
Improvement5.3x faster6x less code

Test: Extract 1,000 Emails from Text

ApproachTime
String.Split + manual parsing1,200ms
RegEx.Matches45ms
Improvement26x faster
These gains scale with data volume. What’s 5x faster at 100K rows becomes critical at 1 million.

When LINQ Gets Too Long: Use Invoke Code

There’s a practical limit to what should go in an Assign activity.

The Problem

' This in an Assign activity... is a nightmare to read and debug
result = dt.AsEnumerable().Where(Function(r) r.Field(Of String)("Status") = "Active" AndAlso r.Field(Of Decimal)("Amount") > 1000 AndAlso Not String.IsNullOrEmpty(r.Field(Of String)("Email")) AndAlso r.Field(Of DateTime)("Date") > DateTime.Today.AddDays(-30)).GroupBy(Function(r) r.Field(Of String)("Department")).Select(Function(g) New With {.Dept = g.Key, .Total = g.Sum(Function(r) r.Field(Of Decimal)("Amount")), .Count = g.Count()}).OrderByDescending(Function(x) x.Total).Take(10).ToList()

Problems:

  • Impossible to debug
  • Can’t set breakpoints
  • Hard to read in Activity panel
  • Error messages are cryptic

The Solution: Invoke Code Activity

When LINQ exceeds 2-3 chained methods, move it to an Invoke Code:

' Invoke Code Activity - In Arguments: dt (DataTable)
'                       Out Arguments: result (List(Of Object))
Dim activeRecords = dt.AsEnumerable() _
    .Where(Function(r) 
        r.Field(Of String)("Status") = "Active" AndAlso
        r.Field(Of Decimal)("Amount") > 1000 AndAlso
        Not String.IsNullOrEmpty(r.Field(Of String)("Email")) AndAlso
        r.Field(Of DateTime)("Date") > DateTime.Today.AddDays(-30)
    )
Dim grouped = activeRecords _
    .GroupBy(Function(r) r.Field(Of String)("Department"))
Dim summarized = grouped _
    .Select(Function(g) New With {
        .Dept = g.Key, 
        .Total = g.Sum(Function(r) r.Field(Of Decimal)("Amount")),
        .Count = g.Count()
    })
result = summarized _
    .OrderByDescending(Function(x) x.Total) _
    .Take(10) _
    .Cast(Of Object)() _
    .ToList()

Benefits of Invoke Code

AspectAssign ActivityInvoke Code
ReadabilityOne long lineFormatted code
DebuggingNo breakpointsFull debugging
Error messagesCrypticLine-specific
IntelliSenseNoneFull support
Complex logicPainfulNatural

Invoke Code Best Practices

┌─────────────────────────────────────────────────────────────────┐
│              When to Use Invoke Code                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   USE ASSIGN when:              USE INVOKE CODE when:           │
│   ───────────────               ─────────────────               │
│   - Simple one-liner            - 3+ chained methods            │
│   - Single Where or Select      - Complex conditionals          │
│   - Quick transformation        - Multiple variables needed     │
│                                  - Try-Catch required            │
│                                  - Loop with complex logic       │
│                                  - Need to debug step-by-step   │
│                                                                  │
│   Example (Assign OK):          Example (Use Invoke Code):      │
│   .Where(x => x > 10)           .Where(...).GroupBy(...)        │
│   .Take(5)                      .Select(...).OrderBy(...)       │
│                                 .ToDictionary(...)               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Quick Reference Cheat Sheet

LINQ One-Liners (Production-Ready)

' Filter
result = dt.AsEnumerable() _
    .Where(Function(r) r.Field(Of String)("Status") = "Active") _
    .CopyToDataTable()
' Sort
result = dt.AsEnumerable() _
    .OrderBy(Function(r) r.Field(Of DateTime)("Date")) _
    .CopyToDataTable()
' Top N
result = dt.AsEnumerable().Take(10).CopyToDataTable()
' Distinct values
values = dt.AsEnumerable() _
    .Select(Function(r) r.Field(Of String)("Category")) _
    .Distinct() _
    .ToList()
' Count
count = dt.AsEnumerable() _
    .Count(Function(r) r.Field(Of String)("Type") = "Invoice")
' Sum (with null handling)
total = dt.AsEnumerable() _
    .Sum(Function(r) If(r.Field(Of Decimal?)("Amount"), 0D))
' Any match?
hasErrors = dt.AsEnumerable() _
    .Any(Function(r) r.Field(Of String)("Status") = "Error")
' First match (returns Nothing if not found)
firstError = dt.AsEnumerable() _
    .FirstOrDefault(Function(r) r.Field(Of String)("Status") = "Error")
' Build lookup dictionary
lookup = dt.AsEnumerable() _
    .ToDictionary(Function(r) r.Field(Of String)("ID"))

RegEx One-Liners

' Is match?
isValid = Regex.IsMatch(text, pattern)
' Get first match
match = Regex.Match(text, pattern).Value
' Get all matches
matches = Regex.Matches(text, pattern).Cast(Of Match)().Select(Function(m) m.Value).ToList()
' Replace
result = Regex.Replace(text, pattern, replacement)
' Split
parts = Regex.Split(text, pattern)

Key Takeaways

  1. LINQ replaces verbose loops with declarative, readable queries.
  2. RegEx extracts patterns from unstructured text that would otherwise require complex parsing.
  3. Combining both lets you process messy real-world data efficiently.
  4. Performance matters when scaling from demo to production volumes.
  5. Type safety in LINQ prevents runtime errors that plague string-based approaches. The difference between an RPA developer who struggles with data and one who excels is often just familiarity with these two tools. They’re not optional skills—they’re essential ones.

Excel Automation Deep Dive

Excel is the #1 data source in RPA. Mastering it separates efficient bots from slow, fragile ones.

Workbook Activities vs Excel Application Scope

┌─────────────────────────────────────────────────────────────────┐
│              Excel Automation Method Comparison                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   WORKBOOK ACTIVITIES             EXCEL APPLICATION SCOPE       │
│   (Use Excel File)                (Use Excel)                   │
│   ────────────────────            ─────────────────────         │
│                                                                  │
│   ✅ No Excel installed needed    ❌ Requires Excel installed    │
│   ✅ Faster for read/write        ❌ Slower (COM automation)     │
│   ✅ Works in background          ❌ Visible Excel window        │
│   ✅ Server-friendly              ❌ Desktop only               │
│                                                                  │
│   ❌ No macros support            ✅ Full macro/VBA support      │
│   ❌ Limited formatting           ✅ Full formatting control     │
│   ❌ No pivot table control       ✅ Pivot tables, charts        │
│   ❌ Basic formulas only          ✅ All Excel features          │
│                                                                  │
│   Best for:                       Best for:                     │
│   - Data extraction               - Complex reports             │
│   - Simple read/write             - Macro execution             │
│   - Server environments           - Interactive editing         │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Decision Matrix

RequirementUse WorkbookUse Excel App Scope
Read data from .xlsx✅ Preferred⚠️ Overkill
Write data to .xlsx✅ Preferred⚠️ Overkill
Run Excel macros❌ Not possible✅ Required
Format cells (colors, borders)❌ Limited✅ Required
Work with Pivot Tables❌ Not possible✅ Required
Server/Unattended bot✅ Required❌ Avoid if possible
Speed is critical✅ Faster❌ Slower

Performance Optimization: Filter vs Loop

The Problem: Processing 100,000 rows by looping takes forever.

Bad Approach (Loop):

For Each row In dataTable.Rows
    If row("Status") = "Active" Then
        activeRows.Add(row)
    End If
Next
' Time: 45 seconds for 100K rows

Good Approach (LINQ Filter):

activeRows = dataTable.AsEnumerable() _
    .Where(Function(r) r.Field(Of String)("Status") = "Active") _
    .CopyToDataTable()
' Time: 0.5 seconds for 100K rows

Best Approach (Excel Filter Activity):

Excel Application Scope
└── Filter Table
    Table: A1:Z100000
    Column: Status
    Values: Active
└── Read Range (filtered data only)
' Time: 0.2 seconds for 100K rows

Working with Named Ranges

Named ranges make your code more readable and maintainable:

' Instead of hardcoded range
dt = ReadRange("A1:F500")

' Use named range (defined in Excel)
dt = ReadRange("InvoiceData")  ' More readable, survives column changes

Define Named Ranges in Excel:

  1. Select the range
  2. Formulas → Define Name
  3. Use in UiPath: ReadRange("RangeName")

Handling Locked Excel Files

Problem: “The file is locked for editing by another user”

Solutions:

' Solution 1: Kill stale Excel processes (aggressive)
For Each proc In System.Diagnostics.Process.GetProcessesByName("EXCEL")
    proc.Kill()
Next

' Solution 2: Read-only access
workbook.Open(path, ReadOnly:=True)

' Solution 3: Copy file first
File.Copy(originalPath, tempPath, overwrite:=True)
ReadRange(tempPath)

' Solution 4: Wait and retry
Dim maxRetries = 5
For i = 1 To maxRetries
    Try
        ReadRange(path)
        Exit For
    Catch ex As IOException
        Thread.Sleep(2000)
    End Try
Next

Advanced: Pivot Table Automation

' Inside Excel Application Scope
' Refresh existing pivot table
excelApp.ActiveWorkbook.RefreshAll()

' Or target specific pivot
pivotTable = worksheet.PivotTables("SalesPivot")
pivotTable.RefreshTable()

' Change pivot filter
pivotField = pivotTable.PivotFields("Region")
pivotField.CurrentPage = "North"

Memory Optimization for Large Files

' Process in chunks instead of loading entire file
Dim rowsPerChunk = 10000
Dim startRow = 2  ' Skip header

While True
    dt = ReadRange($"A{startRow}:Z{startRow + rowsPerChunk - 1}")
    If dt.Rows.Count = 0 Then Exit While
    
    ProcessChunk(dt)
    
    startRow += rowsPerChunk
    dt = Nothing  ' Release memory
    GC.Collect()
End While

Quick Reference: Common Excel Operations

OperationWorkbook ActivityExcel App Scope
Read dataRead RangeRead Range
Write dataWrite RangeWrite Range
Format cellsSet Range Color
Run macroExecute Macro
FilterFilter Data TableAuto Filter
SortSort Data TableSort Range
Find valueLINQ on DataTableFind/Replace
Add formulaWrite Cell (formula string)Write Cell