250116 ‐ Data null - cywongg/2025 GitHub Wiki

The issue in your code is likely due to the Task.Run(async () => await GetCorporateActions(from, to, prStocks)).Result combined with the subsequent Where(ca => ca != null). The error message indicates that the source sequence (qDataPdi) is null at some point, despite your attempt to filter out null values with Where(ca => ca != null).

Here’s a detailed explanation of the issue and how to handle it better:


Problem Analysis

  1. Task.Run with .Result:

    • Using Task.Run in combination with .Result is a blocking operation. It can lead to deadlocks or unintended behavior in situations where the synchronization context is involved (e.g., in UI frameworks like WPF or ASP.NET).
    • If GetCorporateActions throws an exception, the exception is wrapped in an AggregateException by Task.Run. This might not be handled properly in your code.
  2. GetCorporateActions might return null:

    • If GetCorporateActions returns null for any reason (e.g., network issues, empty data, or unhandled exceptions), then calling Where on null will throw a NullReferenceException.
  3. Misleading Filtering Logic:

    • Filtering with Where(ca => ca != null) assumes that the source collection (qDataPdi) is non-null, but if qDataPdi itself is null, LINQ will still fail.

Steps to Fix the Problem

  1. Ensure GetCorporateActions Never Returns Null:

    • Modify GetCorporateActions to always return a non-null collection. If no results are found, ensure it returns an empty collection (new Collection<CorporateAction>()) instead of null.
    private async Task<Collection<CorporateAction>> GetCorporateActions(DateTime from, DateTime to, Collection<string> eliotids)
    {
        try
        {
            Uri buildUri = _httpServiceUriBuilder
                .SetPath("corporate-actions")
                .SetParam("source", "BBG")
                .SetParam("exDateFrom", from.ToString("yyyy-MM-dd"))
                .SetParam("exDateTo", to.ToString("yyyy-MM-dd"))
                .BuildUri();
    
            // Example call to fetch data (replace with actual implementation)
            Collection<CorporateAction> result = await FetchCorporateActionsFromApi(buildUri);
    
            // Ensure non-null result
            return result ?? new Collection<CorporateAction>();
        }
        catch (Exception)
        {
            // Log and return empty collection in case of failure
            return new Collection<CorporateAction>();
        }
    }
  2. Avoid Blocking Code:

    • Instead of Task.Run combined with .Result, use async and await throughout your code to avoid blocking calls. This ensures proper exception propagation and prevents deadlocks.
    public async Task<Collection<CorporateAction>> GetActiveCorporateActions(DateTime from, DateTime to, Collection<string> prStocks)
    {
        Collection<CorporateAction> qDataPdi;
        try
        {
            qDataPdi = await GetCorporateActions(from, to, prStocks);
    
            // Filter out null items (if any)
            return qDataPdi.Where(ca => ca != null).ToList();
        }
        catch (Exception e)
        {
            Elastic.addError("Error occurred when getting CA from qData: " + e.Message, e.StackTrace);
            return new List<CorporateAction>();
        }
    }
  3. Handle Null Collection Safely in LINQ:

    • Before applying LINQ operations like Where, ensure the collection is not null. Use null-coalescing operators (??) or conditional checks.
    qDataPdi = (await GetCorporateActions(from, to, prStocks)) ?? new Collection<CorporateAction>();
    return qDataPdi.Where(ca => ca != null).ToList();
  4. Improve Logging:

    • Log the exception details clearly to understand the root cause. Include inner exceptions when dealing with AggregateException.
    catch (Exception e)
    {
        Elastic.addError("Error occurred when getting CA from qData: " + e.Message, e.ToString());
        return new List<CorporateAction>();
    }

Final Refactored Code

Here’s the refactored version of your code:

public async Task<Collection<CorporateAction>> GetActiveCorporateActions(DateTime from, DateTime to, Collection<string> prStocks)
{
    try
    {
        // Fetch data and ensure non-null result
        var qDataPdi = (await GetCorporateActions(from, to, prStocks)) ?? new Collection<CorporateAction>();

        // Filter out null items
        return qDataPdi.Where(ca => ca != null).ToList();
    }
    catch (Exception e)
    {
        // Log the error and return an empty collection
        Elastic.addError("Error occurred when getting CA from qData: " + e.Message, e.ToString());
        return new List<CorporateAction>();
    }
}

private async Task<Collection<CorporateAction>> GetCorporateActions(DateTime from, DateTime to, Collection<string> eliotids)
{
    try
    {
        Uri buildUri = _httpServiceUriBuilder
            .SetPath("corporate-actions")
            .SetParam("source", "BBG")
            .SetParam("exDateFrom", from.ToString("yyyy-MM-dd"))
            .SetParam("exDateTo", to.ToString("yyyy-MM-dd"))
            .BuildUri();

        // Example call to fetch data (replace with actual implementation)
        Collection<CorporateAction> result = await FetchCorporateActionsFromApi(buildUri);

        // Ensure non-null result
        return result ?? new Collection<CorporateAction>();
    }
    catch (Exception)
    {
        // Log and return empty collection in case of failure
        return new Collection<CorporateAction>();
    }
}

Key Improvements

  1. Avoid Blocking Calls:

    • Use async/await instead of Task.Run + .Result.
  2. Handle Null Safely:

    • Ensure GetCorporateActions never returns null.
    • Use null-coalescing (??) to handle unexpected null values.
  3. Better Logging:

    • Log exceptions with full details to aid debugging.
  4. Null-Filtering:

    • Apply Where(ca => ca != null) only after ensuring the source collection is not null.

This approach ensures robust error handling and avoids common pitfalls like deadlocks and null reference exceptions.

⚠️ **GitHub.com Fallback** ⚠️