Dependency Injection with Massive ORM: dynamic trouble
Asked Answered
F

2

12

I've started working on an MVC 3 project that needs data from an enormous existing database.

My first idea was to go ahead and use EF 4.1 and create a bunch of POCO's to represent the tables I need, but I'm starting to think the mapping will get overly complicated as I only need some of the columns in some of the tables. (thanks to Steven for the clarification in the comments.

So I thought I'd give Massive ORM a try. I normally use a Unit of Work implementation so I can keep everything nicely decoupled and can use Dependency Injection. This is part of what I have for Massive:

public interface ISession
{
    DynamicModel CreateTable<T>() where T : DynamicModel, new();

    dynamic Single<T>(string where, params object[] args) 
        where T : DynamicModel, new();

    dynamic Single<T>(object key, string columns = "*") 
        where T : DynamicModel, new();

    // Some more methods supported by Massive here
}

And here's my implementation of the above interface:

public class MassiveSession : ISession
{
    public DynamicModel CreateTable<T>() where T : DynamicModel, new()
    {
        return new T();
    }

    public dynamic Single<T>(string where, params object[] args) 
        where T: DynamicModel, new()
    {
        var table = CreateTable<T>();
        return table.Single(where, args);
    }

    public dynamic Single<T>(object key, string columns = "*") 
        where T: DynamicModel, new()
    {
        var table = CreateTable<T>();
        return table.Single(key, columns);
    }
}

The problem comes with the First(), Last() and FindBy() methods. Massive is based around a dynamic object called DynamicModel and doesn't define any of the above method; it handles them through a TryInvokeMethod() implementation overriden from DynamicObject instead:

public override bool TryInvokeMember(InvokeMemberBinder binder, 
    object[] args, out object result) { }

I'm at a loss on how to "interface" those methods in my ISession. How could my ISession provide support for First(), Last() and FindBy()?

Put it another way, how can I use all of Massive's capabilities and still be able to decouple my classes from data access?

Fatally answered 23/6, 2011 at 17:11 Comment(5)
If Massive has LINQ support, take a look at this article: bit.ly/gAoDnO.Inherence
"but I'm starting to think the mapping will get overly complicated as I only need some of the columns in some of the tables.". This is a false argument. With EF you can use just what you need. There is no need to generate the complete database model when you use just a few tables and a few columns.Inherence
@Inherence - Really? I was under the (apparently false) impression that I needed 1:1 mapping with my model. No extra configuration/metadada needed? Just-leave-out-what-you-don't-need kinda thing?Fatally
With EF you certainly don't need a one-to-one mapping. You can leave out what you don't need. Of course, when you want to update your database it gets a bit harder, because the required columns without a default value will possibly have to be defined, but I think it is even possible to configure this in the mapping layer.Inherence
@Inherence - Thanks a lot. Updating is not required for those tables I only need partially so I'm good to go, it seems. I'm still curious on how to solve this "Massive" problem (har har) though. I can at least move forward now. Thanks! :)Fatally
B
4

The Interface

Basically you have a couple of options interface wise for the signature for your ISession's Find, Last and FindBy.

If you want to keep the same syntax with the dynamic argument names First, Last and Find should all be getters and return dynamic with a DynamicObject that Implements bool TryInvoke(InvokeBinder binder, object[] args, out object result) that will give you the same dynamic Find(column:val, otherColum:otherVal) syntax. Here is a rough basic example:

    public class MassiveSession : ISession
{ 

    ...

    public dynamic Find{
           get {
               return new DynamicInvoker(this,name:"Find");
           }
    }

    public class DynamicInvoker : DynamicObject{
        ...
        public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
        {
             ...
             return true;          
        }

    }
}

If you want completely statically defined methods you are just going to have to do a single parameter of IDictionary or something to give you the key pairs.

Forwarding the call to the Massive dynamic method

There are two ways to do this as well.

The easy way is to use the open source framework ImpromptuInterface that allows you to programmatically call dynamic methods just like the c# compiler (including dynamic named parameters).

var arg = InvokeArg.Create;
return Impromptu.InvokeMember(table, "Find", arg("column", val),arg("otherColum", otherVal));

Or you can just try to fake the parameters coming into the TryInvokeMember;

Bussard answered 24/6, 2011 at 16:39 Comment(2)
Thanks @jbtule. Could you develop a little more on what do you mean with the getters for First, Last, and so forth? I do want to maintain the original dynamic flexibility. Can you provide an example of what you mean?Fatally
I added example of using dynamic object off a property to give method syntax. The only issue is you can't use generic parameters in the method syntax it produces, so you'd need to pass a type in or refactor something where else.Bussard
M
8

I know this question has been answered - but every method in Massive is marked as virtual so you can Mock it easily. I might suggest that. OR - don't bother.

I'm doing this on my project currently for the MVC3 videos and taking a page from the Rails' playbook - offer my queries as static methods on my objects and go from there. I let my tests hit the database - it doesn't slow things down at all and is quite freeing to get rid of all the machinery.

There's no DI/IoC in Rails, and it's a happy feeling.

Mara answered 30/6, 2011 at 21:21 Comment(4)
I was hoping from an answer from you all along, so thanks ;) I'm following the MVC series on Tekpub and I kinda figured/hoped you'd come down to this topic sooner or later. The thing is, how do you avoid having for instance var table = new Products(); //... var users = new Users() all over the place? Doesn't this not-so-DRY, tight coupling bother you?Fatally
Static query methods :). User.FindBy(...) or creating properties on an Controller base class.Mara
Rob, How many tests do you have? I've got 2000+ tests in a medium sized application. If I refreshed and hit the db each time they would take forever.Graniah
Keen to know the answer to John's comment too. Out of interest John, how long do your tests take and how often are you running them?Technetium
B
4

The Interface

Basically you have a couple of options interface wise for the signature for your ISession's Find, Last and FindBy.

If you want to keep the same syntax with the dynamic argument names First, Last and Find should all be getters and return dynamic with a DynamicObject that Implements bool TryInvoke(InvokeBinder binder, object[] args, out object result) that will give you the same dynamic Find(column:val, otherColum:otherVal) syntax. Here is a rough basic example:

    public class MassiveSession : ISession
{ 

    ...

    public dynamic Find{
           get {
               return new DynamicInvoker(this,name:"Find");
           }
    }

    public class DynamicInvoker : DynamicObject{
        ...
        public override bool TryInvoke(InvokeBinder binder, object[] args, out object result)
        {
             ...
             return true;          
        }

    }
}

If you want completely statically defined methods you are just going to have to do a single parameter of IDictionary or something to give you the key pairs.

Forwarding the call to the Massive dynamic method

There are two ways to do this as well.

The easy way is to use the open source framework ImpromptuInterface that allows you to programmatically call dynamic methods just like the c# compiler (including dynamic named parameters).

var arg = InvokeArg.Create;
return Impromptu.InvokeMember(table, "Find", arg("column", val),arg("otherColum", otherVal));

Or you can just try to fake the parameters coming into the TryInvokeMember;

Bussard answered 24/6, 2011 at 16:39 Comment(2)
Thanks @jbtule. Could you develop a little more on what do you mean with the getters for First, Last, and so forth? I do want to maintain the original dynamic flexibility. Can you provide an example of what you mean?Fatally
I added example of using dynamic object off a property to give method syntax. The only issue is you can't use generic parameters in the method syntax it produces, so you'd need to pass a type in or refactor something where else.Bussard

© 2022 - 2024 — McMap. All rights reserved.