getting a different IQueryHandler<TQuery, TResult> by name

Oct 17, 2013 at 2:41 PM
Hi,

I've been trying to use Resolve Instances by Key to resolve my scenario, but I'm just not focusing properly today. Can you suggest a resolution? Basically my user will be passing a string of the datasource/query they need to run. I need to resolve that to a specific handler with the TQuery. All of the TQuery objects essentially have a single parameter (Id) that needs to get passed to the handler. The handler itself will point to a different database connection and return a datatable (yuck but could be a IEnumerable<someobject> one I get past the general issue). Using the Query Processor I get partially there but now I'm stuck on the TQuery part. Any guidance would be much appreciated.
Coordinator
Oct 17, 2013 at 5:39 PM
Can you post an example demonstrating a problem. I'm having a hard time visualizing what you're trying to do.
Oct 17, 2013 at 5:48 PM
here is essential what I've got to work.
            foreach (var item in dataSoures)//a list of strings
            {
                Assembly a = Assembly.GetAssembly(typeof(IQuery<>));
                var t = a.GetType(item);

                var query = Activator.CreateInstance(t, AccountNumber) as IQuery<DataTable>;
                var table = _queryProcessor.Process(query);
                cachedData.Add(item + "|" + AccountNumber, table);
            }
I don't really like it but it works. Basically there are some 20 odd calls that the user can select one or many of with one or more columns from each. I want to run all the necessary queries first then aggregate the columns in the order they requested them. At least the QueryProcessor code eliminated a large switch block and is very elegant (a tiny bit elaborate). Using the CreateInstance is the part I'm hating because it feels fragile if one or more of the queries need more than just the AccountNUmber.
Coordinator
Oct 18, 2013 at 6:58 AM
It seems you're abusing the query/handler model by passing names of the query by string. The essence of this model is to have typed query objects and pass them on. I think I'm missing some part of the picture here. Can you explain or show why you are passing this these data sources by string and how does the calling code look like?
Oct 18, 2013 at 1:41 PM
It seems you're abusing the query/handler model by passing names of the query by string.
absolutely agree. I'm quite happy to take any thing you can offer to make it better

Here's the what and why

This is a legacy application with two interfaces. The first is a web page with widgets that will be the perfect candidate for using the Query/Handler model when we have the bandwidth to clean it up. The second thing the user can do is request a file of data. using a tree they are able to select columns from different datasources (actually stored procs that cross AS400 & Oracle, etc.) to make the content of the file. They then save that request with all the picked columns (and associated datasources) to a table. Later a polling service picks up the request, runs all the datasources asked for, picks out the selected columns, writes the file. That's the code you see above. The web page and the file creation process use the same stored procs and the goal is to eventually 'correct' the UI to use the query/handler. It's also a longer term goal to ditch most of the stored procs for the file creation process and rewrite the sql to support these bulk query operations. That being said, I'm currently trying to re-engineer the current process to at least be readable so when we do get to the refactoring we at least have a chance of being successful.

After noodling and considering some more I've thought that perhaps I could do something like maybe;

creating a dictionary of all the idbcommand objects (or Func<idbCommand>) that can be run with the key that I'm looking for and then use the key that comes in from the saved request to get that command and execute it that way. Then I actually have a factory. I could pass optional arguments in if I had them which would be an improvement on the above.

I still want to use the query/handler part in the web page piece so the above way at least lets me reuse then handlers that i'm creating.

I am completely open to your thoughts on the subject and am very grateful that you've asked. Sometimes having a extra brain and set of eyes really helps.
Coordinator
Oct 18, 2013 at 3:27 PM
The second thing the user can do is request a file of data. using a tree they are able to select columns from different datasources (actually stored procs that cross AS400 & Oracle, etc.) to make the content of the file. They then save that request with all the picked columns (and associated datasources) to a table. Later a polling service picks up the request, runs all the datasources asked for, picks out the selected columns, writes the file._
That's not a query, that's a command you're talking about. It's a command, since the operation does not return data but "writes [to] the file". This command might still use query/handlers on the background though, perhaps ran through the query processor as you show in your code.

This is what I imagine that it could look like:
public class SaveDataSourcesToFileCommand
{
    public ICollection<string> DataSources { get; set; }

    public string AccountNumber { get; set; }
}

public class SaveDataSourcesToFileCommandHandler : ICommandHandler<SaveDataSourcesToFileCommand>
{
    private IUserContext userContext;
    private IQueryProcessor processor;

    public SaveDataSourcesToFileCommandHandler(
        IUserContext userContext, IQueryProcessor processor)
    {
        this.userContext = userContext;
        this.processor = processor;
    }

    public void Handle(SaveDataSourcesToFileCommand command)
    {
        var cachedData = new Dictionary<string, DataTable>();
    
        foreach (string dataSource in command.DataSources)
        {
            var queryType = typeof(IQuery<>).Assembly.GetType(dataSource);
            var query = (IQuery<DataTable>)Activator.CreateInstance(queryType);
            
            // Don't pass on the AccountNumber, but inject a userContext in the query handler.
            DataTable table = this.processor.Execute(query);
                
            cachedData.Add(item + "|" + this.userContext.AccountNumber, table);
        }
        
        WriteToFile(cachedData);
    }
}
This command can quite easily be serialized to XML or JSON and put into a queue, and be dequeued and deserialized by the polling service.
Marked as answer by dot_NET_Junkie on 2/26/2014 at 1:20 PM
Oct 18, 2013 at 3:47 PM
Sorry I kind of led you astray a little bit. I was mostly concerned about handling all the different queries. I essentially had the command part down. Your solution is essentially the same as mine (with the addition of userContext [I like it better than arbitrary args]). Somehow it just felt a little rough because of the reflection.

Still compared to the 6 layers deep of 'magic strings' the current system uses this little bit of black magic is a dream.
Coordinator
Oct 18, 2013 at 4:54 PM
Your string-to-query magic feels a bit dirty, but I'm not sure there's another way in your scenario, since you seem to process some sort of batch operation. Converting it to a big switch-case won't make it better :-). When we send queries over the wire to a WCF service, it in the end does the same sort of string-to-query magic, so perhaps this is just a valid approach..
Oct 18, 2013 at 5:03 PM
Converting it to a big switch-case won't make it better :-)
absolutely it won't. Make it MUCH worse in my opinion. In a future version I'm sure we can clean it up even further to support the actual use case which is more like "Create a file of data using these accounts" instead of what we do now which is "for each account in this list create a row of data". unfortunately we don't have the time (or domain knowledge at this stage) to rewrite some 30+ stored procs.

Anyway thanks for the validation. Have a great weekend.