IRepository from a singleton service

Topics: Customizing Orchard, Troubleshooting, Writing modules
Apr 11, 2012 at 3:12 PM
Edited Apr 11, 2012 at 3:21 PM

How can I safely use an IRepository from a singleton service?

Do I need to create my own transaction? Any core module that does such thing to peek at?

edit: Same question about accessing / modifying parts attached to the 'Site' (ie my global settings)

Apr 11, 2012 at 3:23 PM

You could inject a Work<IRepository<T>> into your singleton service and store it as a field/property.
Whenever you access that field/property, it will use the current work (http) context.
From the top of my head, the Work<T> class is actually just a wrapper around IWorkContext.Resolve.

Coordinator
Apr 11, 2012 at 6:47 PM

Why do you need a singleton service in the first place?

Apr 11, 2012 at 7:08 PM

It'll launch executables (our 'game' servers) and maintain a WCF connection with the launched executables to monitor it in the background.

sfmskywalker: And what if I need access to it on a non-http request thread (as in there is no http context available) as would be the case when accessed in one of my background threads.

Apr 11, 2012 at 7:22 PM
Edited Apr 11, 2012 at 7:22 PM

I don't know if it will work, but looking at the WorkContextImplementation source, it takes a dependency on an IComponentContext. My guess would be that if you injected an instance of that one into your singleton, you would be able to resolve IRepository instances using its Resolve method.

 
Apr 18, 2012 at 6:28 AM

Just to report back : got everything working!

As always, I could base myself on something already in Orchard : the SweepGenerator / BackgroundService

I now have a singleton service, automatically created by injecting it in the constructor of a 'IResourceManifestProvider' implementation (since I knew that is one of the things that is instantiated by default when a module is loaded)

Inside my background service singleton, I launch a 'background runner' thread that performs all the logic (handle existing connections, spawn new ones etc)

I made couple of helper methods that'll ensure that a valid work scope / transaction scope is available for the current thread, and I store them both in a static field marked with [ThreadStatic] (found out about its existence a week ago and already found a use for it)

Also, every X seconds I auto-complete the transaction scope + on request as well.

Right before I complete a transaction scope, I flush & clear the repositories.

Using this setup, I got everything working now! I simply enqueue new server launches in my database, that my background runner will pick up and execute.

One more hint for people that want to do something similar: In your background runner thread method, if you want to use the logger, do a short while (Logger is NullLogger) { Thread.Sleep(100); }

Since the thread is launched async, the logger isn't set right away (I wasn't aware at first)

Developer
Apr 18, 2012 at 9:00 PM

ThreadStatic is not necessarily a good idea in ASP.NET applications as threads are re-used for different requests. You may want to look up what others say about that.

Apr 19, 2012 at 6:57 AM

Well it is sufficient for me, as I'm currently only using it for my background runner thread (and in the future maybe one more 'runner' thread)

Developer
Apr 20, 2012 at 12:56 AM
Edited Apr 20, 2012 at 12:59 AM

Good you had it working, but speaking of a best practice, the Work<T> approach, as @sfmskywalker mentioned is the way to go, if you need a single unit-of-work-scoped object.

You could also use IWorkContextAccessor in the singleton object and create a new work context scope (using var scope = _accessor.CreateWorkContextScope()) for each thread. This way you'd be able to access all unit-of-work objects (like IContentManager and so on), not only the IRepository<T> from inside the thread. Oh, and remember to dispose it when the thread finishes:)

Multithreading inside ASP.NET app and especially having a long-running thread is not a good thing. You should also think about registering your singleton class in AppDomain (using IRegisteredObject), to perform a controlled abort of the thread if AppDomain gets torn down. If you don't do it - your thread can be aborted in the middle of some operation and corrupt your data. Of course transactions will keep the db state intact, but other things may go wrong (writing to a file, calling web services and so on).

Apr 20, 2012 at 6:23 AM

I'll look into IRegisteredObject since I was wondering how I could cleanly handle recycles - thanks.

Apr 20, 2012 at 11:13 AM

What does Work<T> do exactly / how does it work? 

Apr 20, 2012 at 12:21 PM

It's basically a wrapper class where T is any class to resolve when you invoke the Value property of the Work<T> instance. So when you inject a Work<IRepository<T>> and store it in a field named _work, you are not getting an actual IRepository<T> until you invoke _work.Value. The Value property resolves to an IRepository<T> for you for the current request. So even though you have a Work<T> field on your singleton, when your singleton class has some method that is invoked during an HTTP request, you will have access to the IRepository<T> in the scope of the current request:

 public class Work<T> where T : class {
        private readonly Func<Work<T>, T> _resolve;

        public Work(Func<Work<T>, T> resolve) {
            _resolve = resolve;
        }

        public T Value {
            get { return _resolve(this); }
        }
    }

AutoFac is setup with a custom registration source so that it injects some logic that resolves the actual type:

static IComponentRegistration CreateMetaRegistration<T>(Service providedService, IComponentRegistration valueRegistration) where T : class {
            var rb = RegistrationBuilder.ForDelegate(
                (c, p) => {
                    var workContextAccessor = c.Resolve<IWorkContextAccessor>();
                    return new Work<T>(w => {
                        var workContext = workContextAccessor.GetContext();
                        if (workContext == null)
                            return default(T);

                        var workValues = workContext.Resolve<WorkValues<T>>();

                        T value;
                        if (!workValues.Values.TryGetValue(w, out value)) {
                            value = (T)workValues.ComponentContext.ResolveComponent(valueRegistration, p);
                            workValues.Values[w] = value;
                        }
                        return value;
                    });
                })
                .As(providedService)
                .Targeting(valueRegistration);

            return rb.CreateRegistration();
        }

I don't know the exact purpose of the WorkValues yet, but I assume it's some sort of dictionary that caches resolved instances.

 

Mar 20, 2013 at 7:33 AM
Work<T> won't work in context of IOrchardShellEvents.Activated event (during Orchard launching). In the context of this event there is no WorkContext. So Work.Value returns null. Please advise how to get an instance of IRepository inside a singleton in any context.
Developer
Mar 20, 2013 at 9:14 PM
Have you tried Lazy<IRepositor<TRecord>>?
Mar 21, 2013 at 3:31 PM
Lazy<T> provides resolving only for the first access. And I want to resolve IRepository every time it is accessed. Problems with resolving appear (that I faced with) in IOrchardShellEvents.Activated and in callback methods in case of asynchronous WCF service invocation.
Mar 21, 2013 at 4:20 PM
Edited Mar 21, 2013 at 4:22 PM
Hmm, why the spam?

Also there is no 'WorkContext' if you are for example trying to get one @ an async callback.

You'll have to roll your own WorkContext if you need one & handle cleaning up yourself.

Look @ SweepGenerator & BackgroundService for an idea what you need to do :)
Mar 21, 2013 at 9:43 PM
Edited Mar 21, 2013 at 9:43 PM
Thanks for your answer.
AimOrchard wrote:
Hmm, why the spam?
I've refreshed the page twice after posting and this caused duplicate messages. I don't know how to remove these extra posts.
Mar 22, 2013 at 12:35 PM
SweepGenerator approach doesn't work(. This code (called on singleton from IOrchardShellEvents.Activated)
    var wcScope = wca.CreateWorkContextScope();
    var repository =  wcScope.Resolve<IRepository<MyRecord>>();
    repository.Get(rec => rec.Name == "something")
causes an exception
The connection object can not be enlisted in transaction scope

All works fine without singletons. So I think that autofac can resolve instances even in IOrchardShellEvents.Activated method. Is it posibble to resolve instances directly from autofac container?
Mar 22, 2013 at 12:47 PM
Edited Mar 22, 2013 at 12:48 PM
Well you're not doing what you should do.

You need to mimick what SweepGenerator AND the BackgroundService does.

ie within the created workscope 'demand' the transaction manager, then create a new transaction scope and then in there do your stuff + do not forget to 'complete' the transaction if needed.

I know it must work since we have plenty of those 'special' workscopes running on other threads @ our heavily modified orchard 'app' ;)

Just don't forget to clean up the scopes whenever you're done with them, or it might lock your tables.
Mar 22, 2013 at 1:12 PM
Edited Mar 22, 2013 at 1:40 PM
It works! Thanks!
Mar 22, 2013 at 1:34 PM
slimjack wrote:
SweepGenerator approach doesn't work(. This code (called on singleton from IOrchardShellEvents.Activated)
    var wcScope = wca.CreateWorkContextScope();
    var repository =  wcScope.Resolve<IRepository<MyRecord>>();
    repository.Get(rec => rec.Name == "something")
causes an exception
The connection object can not be enlisted in transaction scope

All works fine without singletons. So I think that autofac can resolve instances even in IOrchardShellEvents.Activated method. Is it posibble to resolve instances directly from autofac container?
Stop with the refreshing & reposting... :/
Mar 25, 2013 at 8:46 AM
Some things are not clear for me. Could you please explain why do we need to call TransactionManager.Demand since it's called during Repositry initialization? And why do we need to create one more Transaction before Repository resolving?