Search code examples
c#extension-methodslinqpad

How to query multiple database names in LinqPad via a global extension method ("My Extensions")?


I am trying to query additional database names in LinqPad in a multi-connection scenario.

For this purpose, I have written the following extension method, which doesn't work in My Extensions (the place where you can declare global extensions in LinqPad) - but it works fine when placed in a new ordinary LinqPad query:

public static class MyExtensions
{
    // Write custom extension methods here. They will be available to all queries.

    // get list of additional databases used. Pass "this" (of type UserQuery)
    internal static List<string> GetAdditionalDatabaseNames(this UserQuery uq)
    {

        var props = uq.GetType().GetProperties();
        var result = new List<string>();
        foreach (var db in props.Where(
                               w => w.PropertyType.Name == "TypedDataContext").Distinct())
        {
            result.Add(db.Name);
        }
        return result;
    }
}

Usage example:

void Main()
{
    this.Connection.Database.Dump();
    this.GetAdditionalDatabaseNames().Dump();
}

Hold the Ctrl key while dragging + dropping multiple databases into the query window to add them, and you will get a list of the additional database names from this method (all except the first database, which is available in this.Connection.Database.Dump();).

Originally I wanted to declare the extension method as "public static" so it becomes available for all queries, but when I declared it as public I got the message

CS0051 Inconsistent accessibility: parameter type 'UserQuery' is less accessible than method 'MyExtensions.GetAdditionalDatabaseNames(UserQuery)'


Update: I tried a workaround and added a public generic wrapper method inside MyExtensions as follows:

// Wrapper to be able to use it from outside of My Extensions
public static List<string> GenericAdditionalDatabaseNames<T>(this T userQuery)
{
    return GetAdditionalDatabaseNames(userQuery as UserQuery);
}

But when I use it in a query, I am getting a different error now:

NullReferenceException: Object reference not set to an instance of an object.


Rather than having to place the code above in every query, what can I do to fix this?


Solution

  • I just found out a workaround that works for me: Refactoring the updated code in the question as follows:

    My Extensions

    public static class MyExtensions
    {
        public static List<string> GetAdditionalDatabaseNames<T>(this T userQuery)
        {
    
            var props = userQuery.GetType().GetProperties();
            var result = new List<string>();
            foreach (var db in props.Where(
                                   w => w.PropertyType.Name == "TypedDataContext").Distinct())
            {
                result.Add(db.Name);
            }
            return result;
        }
    }
    

    The difference is, that this time I am not wrapping another extension method around which calls the internal method.

    But why does it work? It accepts every kind of object, and since it is using reflection, it doesn't care about the type passed as parameter. The workaround in the question above didn't work because it was trying to cast it into an UserQuery - because the cast doesn't work, it passed null which resulted in the NullReferenceException.

    Now you can use it in any query window as mentioned in the example. To get all database names, you can do

    Any C# query window

    void Main()
    {
        var dbNames=this.GetAdditionalDatabaseNames(); dbNames.Add(this.Connection.Database);
        dbNames.Dump();
    }
    

    Note: You might wonder, why this.Connection.Database isn't just added to the list inside the extension method - this is because the Connection object is not accessible inside the My Extensions module.