Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Nabin Prajapati 2 posts 72 karma points
    Mar 06, 2019 @ 05:42
    Nabin Prajapati
    0

    Alternative solution to use "ApplicationContext.Current.DatabaseContext.Database;" in Umbraco 8

    I have been using
    // var db = ApplicationContext.Current.DatabaseContext.Database; for accessing my db in Umbraco 7 , but it can't be done on Umbraco 8

  • Søren Gregersen 288 posts 1150 karma points MVP c-trib
    Mar 06, 2019 @ 08:26
    Søren Gregersen
    0

    use DI

    In your constructor you take IScopeAccessor, that holds an AmbientScope that has a Database-property

    public class MyWhatever{
        private readonly IScopeAccessor _scopeAccessor
        public MyWhatever(IScopeAccessor scopeAccessor){
            _scopeAccessor=scopeAccessor;
        }
        public int Count(){
            var count = _scopeAccessor.AmbientScope.Database.ExecuteScalar<int>("SELECT COUNT(*) FROM table");
            return count;
        }
    }
    
  • Sebastiaan Janssen 4834 posts 14219 karma points MVP admin hq
    Mar 06, 2019 @ 08:28
    Sebastiaan Janssen
    3

    You should use something like this:

            using (var scope = _scopeProvider.CreateScope())
            {
                //DB Stuff
                scope.Database.Fetch<Foo>();
    
                //Always complete scope
                scope.Complete();
            }
    

    You will notice the _scopeProvider there, where does it come from?

    This is where dependency injection comes in, the class that I'm working in is asking for a _scopeProvider in the constructor, so like this:

        private IScopeProvider _scopeProvider;
    
        public BlogPostController(IScopeProvider scopeProvider)
        {
            _scopeProvider = scopeProvider;
        }
    

    To give you the full picture, my full class looks like this:

    using System.Web.Mvc;
    using Cultiv8.Core.Models;
    using Umbraco.Core.Scoping;
    using Umbraco.Web.Models;
    using Umbraco.Web.Mvc;
    
    namespace Cultiv8.Core.Controllers
    {
        public class BlogPostController : RenderMvcController
        {
            private IScopeProvider _scopeProvider;
    
            public BlogPostController(IScopeProvider scopeProvider)
            {
                _scopeProvider = scopeProvider;
            }
    
            public ActionResult BlogPost(ContentModel model)
            {
                using (var scope = _scopeProvider.CreateScope())
                {
                    //DB Stuff
                    scope.Database.ExecuteScalar<int>("SELECT COUNT(*) FROM table");
    
                    //Always complete scope
                    scope.Complete();
                }
    
                var blogPostModel = new BlogPostModel(model.Content);
                return CurrentTemplate(blogPostModel);
            }
        }
    }
    

    Your question depends a little bit on what kind of class you're working in but this would be the general pattern.

    Does this make sense at all?

  • Stephen 758 posts 2230 karma points hq
    Mar 06, 2019 @ 08:37
    Stephen
    2

    To complement Sebastiaan's excellent answer: a Scope should always be obtained from a IScopeProvider. This provider manages nesting scopes, scope completion, etc.

    Now, internally, getting a scope makes it the "ambient" scope (until it is disposed) and that is what IScopeAccessor manages: the "ambient" scope. So _scopeAccessor.AmbientScope is indeed going to get you a scope... in some cases. It's going to get you the ambient scope, if you already are in a scope. But it's not going to create a scope, ie it could very well return null.

    So... you don't want to use IScopeAccessor. Use IScopeProvider.

  • Stephen 758 posts 2230 karma points hq
    Mar 06, 2019 @ 08:44
    Stephen
    2

    And then, about scope completion. Scopes wrap a database transaction, and this allows us to, for instance, database-lock the content tree or other things, for the duration of the transaction, thus bringing some concurrency-safety that was missing in v7.

    When you nest scopes, each scope of the chain relies on the unique database connection (and transaction) that was created for the top scope. And the connection is closed when this top scope is disposed.

    What happens then depends on whether the scope has been completed. If it's been completed, the transaction is committed, else it's rolled back.

    For nested scopes: if any nested scope is not completed, then the top scope is considered as not-completed and the transaction is rolled back. So you always want to complete any scope you use, even if you're only doing reads (not writing anything).

    If you know you're only going to read, you can use:

    using (scopeProvider.CreateScope(autoComplete: true))
    {
      // no need to scope.Complete()
    }
    

    Be careful though: the scope will be completed no matter what, even if an exception is thrown from within the using block.

  • Dan Diplo 1457 posts 5606 karma points MVP 3x c-trib
    Mar 13, 2019 @ 14:38
    Dan Diplo
    0

    Hi Guys,

    How would you go about this if you had an extra layer, such as a simple repository? So would you pass an IScope to the repository to initialise it?

    Say it was like this very simplified example:

    public class MyRepo
    {
        private readonly IScope _scope;
    
        public MyRepo(IScope scope)
        {
            this._scope = scope;
        }
    
        public IEnumerable<string> GetUserNames()
        {
            return this._scope.Database.Fetch<string>("SELECT userName FROM umbracoUser ORDER BY userName");
        }
    
        public IEnumerable<int> GetIds()
        {
            return this._scope.Database.Fetch<int>("SELECT id FROM cmsContent");
        }
    }
    

    How would you initialise the repository once? Could you do it in the Controller's constructor like this, which doesn't seem right?

    public class MyController : RenderMvcController
    {
        private IScopeProvider _scopeProvider;
        private MyRepo _myRepo;
    
        public MyController(IScopeProvider scopeProvider)
        {
            this._scopeProvider = scopeProvider;
    
            using (var scope = _scopeProvider.CreateScope())
            {
                _myRepo = new MyRepo(scope);
            }
        }
    
        public ActionResult GetSomeUserNames(ContentModel model)
        {
            var names = _myRepo.GetUserNames();
    
            return CurrentTemplate(model);
        }
    
        public ActionResult GetSomeId(ContentModel model)
        {
            var ids = _myRepo.GetIds();
    
            return CurrentTemplate(model);
        }
    }
    

    Or would you need to initialise the scope in every call in the controller eg.

    public ActionResult GetSomeUserNames(ContentModel model)
    {
        using (var scope = _scopeProvider.CreateScope())
        {
            _myRepo = new MyRepo(scope);
            var names = _myRepo.GetUserNames();
            scope.Complete();
        }
    
        return CurrentTemplate(model);
    }
    

    Or is there some other better way? Can you access this AmbientScope you mentioned? Maybe someway of using DI to register the repo?

  • Søren Gregersen 288 posts 1150 karma points MVP c-trib
    Mar 06, 2019 @ 09:49
    Søren Gregersen
    0

    ... so much for learning by reading the code.

    Thx for clarifying guys :-)

  • Bjarne Fyrstenborg 1136 posts 3218 karma points MVP 3x c-trib
    Mar 07, 2019 @ 11:23
    Bjarne Fyrstenborg
    0

    Should the ScopeProvider always be accessed via the parameter or can it be accessed via Current as well, in e.g. SurfaceController? Not sure if there is a different?

    using (var scope = Current.ScopeProvider.CreateScope())
    {
        //DB Stuff
        scope.Database.ExecuteScalar<int>("SELECT COUNT(*) FROM table");
    
        //Always complete scope
        scope.Complete();
    }
    
  • Sebastiaan Janssen 4834 posts 14219 karma points MVP admin hq
    Mar 07, 2019 @ 12:13
    Sebastiaan Janssen
    0

    If you can avoid using Current at all then try to avoid it.

  • Stephen 758 posts 2230 karma points hq
    Mar 13, 2019 @ 14:47
    Stephen
    1

    Core's repository look like

    public class MyRepository : IMyRepository
    {
      private readonly IScopeAccessor _scopeAccessor;
    
      public MyRepository(IScopeAccessor scopeAccessor)
      {
        _scopeAccessor = scopeAccessor;
      }
    
      private IUmbracoDatabase Database = _scopeAccessor.AmbientScope.Database;
    
      private void DoSomething()
      {
        Database.Execute(...);
      }
    }
    

    And they are registered in DI. So you can directly inject the repository in your service or (god forbit) controller constructor:

    public MyController(IMyRepository myRepository)
    { ... }
    

    In order to use it, you'll need to ensure there is an ambient scope:

    using (var scope = _scopeProvider.CreateScope())
    {
      _myRepository.DoSomething();
      scope.Complete();
    }
    

    Because the repository does not capture anything but the scope accessor, which is a singleton, it can also be registered as a singleton, thus reducing allocations.

    Making sense?

  • Dan Diplo 1457 posts 5606 karma points MVP 3x c-trib
    Mar 13, 2019 @ 15:45
    Dan Diplo
    0

    Hi Stephen,

    OK, so I've created MyRespository like you suggested, so it takes a IScopeAccessor. I've then registered it as a singleton like this:

    public class MyRepoComposer : IUserComposer
    {
        public void Compose(Composition composition)
        {
            composition.RegisterUnique<IMyRepository, MyRepository>();
        }
    }
    

    I've then injected the IMyRepository into the constructor of a controller (just to test!).

    However, in MyRepository the _scopeAccessor.AmbientScope is null.

    The bit I don't quite get is how you create the ambient scope as in your example. You say "in order to user it you'll need to create an ambient scope" but I'm not clear where _scopeProvider is coming from in your example.

    Assume I have a controller that injects my repo like this:

    private readonly IMyRepository _myRepo;
    
    public MyController(IMyRepository myRepo)
    {
       this._myRepo = myRepo;
    }
    

    How do I then use that repository in controller methods so I have an ambient scope? eg. how would the following work?

     public ActionResult DoSomething()
     {
          _myRepo.DoSomething();
     }
    

    PS. Sorry for all the questions!

  • Stephen 758 posts 2230 karma points hq
    Mar 13, 2019 @ 15:56
    Stephen
    0

    You need an ambient scope:

    public ActionResult DoSomething()
    {
      using (var scope = _scopeProvider.CreateScope())
      {
        _myRepo.DoSomething();
      }
    }
    
  • Dan Diplo 1457 posts 5606 karma points MVP 3x c-trib
    Mar 13, 2019 @ 16:00
    Dan Diplo
    0

    Thanks, but where does _scopeProvider come from? How does that get into the controller?

  • Stephen 758 posts 2230 karma points hq
    Mar 13, 2019 @ 16:16
    Stephen
    0

    Inject!

    public class MyController
    {
      private readonly IScopeProvider _scopeProvider;
    
      public MyController(IScopeProvider scopeProvider)
      {
        _scopeProvider = scopeProvider;
      }
    }
    
  • Kevin Jump 1424 posts 9137 karma points MVP 3x c-trib
    Mar 13, 2019 @ 16:26
    Kevin Jump
    1

    Hi Dan,

    my copied from the core, and simplified a bit serivce / repo looks like this :

    So my services are inheriting something like :

    public class MyBaseService<TModel>
    {
            protected readonly ILogger logger;
            protected IMyBaseRepoRepository<TModel> baseRepo;
            protected IScopeProvider scopeProvider;
    
            public TranslationServiceBase(
                IScopeProvider scopeProvider,
                IMyBaseRepoRepository<TModel> baseRepo,
                ILogger logger)
            {
                this.scopeProvider = scopeProvider;
                this.logger = logger;
                this.baseRepo = baseRepo;
            }
    
            public virtual TModel Get(int id)
            {
                using(scopeProvider.CreateScope(autoComplete: true))
                {
                    return baseRepo.Get(id);
                }
            }
    }
    

    and my repositories are inheriting this (slightly more complex and this is where the ambient scope is created ... inside the repository class

    protected MyRepositoryBase(
            IScopeAccessor scopeAccessor, 
            ILogger logger,
            string tableName)
        {
            this.scopeAccessor = scopeAccessor;
            this.logger = logger;
            this.tableName = tableName;
        }
    
        protected string tableName;
    
        protected readonly ILogger logger;
        protected readonly IScopeAccessor scopeAccessor;
    
        protected IScope AmbientScope
        {
            get
            {
                var scope = scopeAccessor.AmbientScope;
                if (scope == null)
                    throw new InvalidOperationException("Cannot run repository without an Ambient scope");
                return scope;
            }
        }
    
        protected IUmbracoDatabase Database => AmbientScope.Database;
    
        protected ISqlContext SqlContext => AmbientScope.SqlContext;
        protected Sql<ISqlContext> Sql() => SqlContext.Sql();
        protected ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax;
    
        /// ... repository methods ...
    

    then something like get looks like (using custom GetBaseQuery / GetBaseWhereClause functions)

     public virtual TModel Get(int id)
        {
            var sql = GetBaseQuery(false)
                .Where(GetBaseWhereClause(), new { Id = id });
    
            var dto = Database.FirstOrDefault<TDTOModel>(sql);
    
            if (dto == null) return default(TModel);
    
            return Mapper.Map<TModel>(dto);
        }
    

    I think this is right (taking into consideration the autoclose stuff stephen mentioned)

  • Dan Diplo 1457 posts 5606 karma points MVP 3x c-trib
    Mar 13, 2019 @ 16:38
    Dan Diplo
    0

    Thanks, just after posting I did just that and it does work. So thanks for your help! :)

    But.... but....

    It doesn't seem quite right to me that you need to remember to wrap every call to the repo in a scope otherwise it fails. What if someone else injects it and doesn't do that?

    So another developer comes along, injects IMyRepository and then calls _myRepo.DoSomething() and boom it blows up and they've no idea why. Isn't there a way you can just call MyRepo without needing to know or worry about scope?

  • Søren Gregersen 288 posts 1150 karma points MVP c-trib
    Mar 13, 2019 @ 17:10
    Søren Gregersen
    0

    You are also able to control how a database Connection behaves this way in .Net Framework, so it’s not unheard of.

    But I agree it’s not very SOLID

  • Stephen 758 posts 2230 karma points hq
    Mar 13, 2019 @ 18:05
    Stephen
    1

    The general pattern is that you don't expose your repository to "general other developers" - you expose services, which internally create scopes and invoke repositories.

    Whoever uses the repository directly should know about the constraint.

    At least that's how it works in Core.

  • Dan Diplo 1457 posts 5606 karma points MVP 3x c-trib
    Mar 13, 2019 @ 20:12
    Dan Diplo
    0

    OK, yeah, I get that to an extent. You might not make your repository layer public but only expose it internally via a service layer (I was trying to keep this example simple). But it still feels wrong to me that the consumer of a class needs to know to wrap it before calling it. Isn't this known as temporal coupling, which I thought should be avoided?

    In my case I'm just looking at a small package that has a few database calls, and having multiple tiers seems a little OTT, as basically the service layer would just be calling each method in the repo, but having to wrap each call in a scope, which just seems like a lot of unnecessary code to me.

    Is having a repo DI'd in this instance even a good idea? Maybe the service later should be, but maybe not the repo? I guess I'd just like some guidance on the best way for package makers to proceed with stuff like this.

  • Stephen 758 posts 2230 karma points hq
    Mar 13, 2019 @ 14:49
    Stephen
    1

    By the way, this:

    using (var scope = _scopeProvider.CreateScope())
    {
      _myRepo = new MyRepo(scope);
    }
    

    would cause interesting errors. You are capturing scope in _myRepo but when exiting the using block, scope is disposed = any time you try to use your repository, you'll get an object disposed exception of some sort.

  • Kevin Jump 1424 posts 9137 karma points MVP 3x c-trib
    Mar 13, 2019 @ 14:54
    Kevin Jump
    0

    Just checking - is this OK.

    using (var scope = _scopeProvider.CreateScope(autoComplete: true))
    {
      _myRepository.DoSomething();
    }
    

    The autoComplete parameter makes it a bit neater - but i am sort of assuming it works

  • Dan Diplo 1457 posts 5606 karma points MVP 3x c-trib
    Mar 13, 2019 @ 14:56
    Dan Diplo
    0

    Thanks, Stephen, I could see that initialising the repo with the scope was wrong, just wasn't sure of the best alternative. I did try and look at some of the core stuff, but it was quite complex for my needs. I'll try your approach you mentioned.

  • Stephen 758 posts 2230 karma points hq
    Mar 13, 2019 @ 14:57
    Stephen
    1

    No problem - following this thread, as it may turn into a blog post - lots of "obviously not obvious" things that you are discovering here.

  • Stephen 758 posts 2230 karma points hq
    Mar 13, 2019 @ 14:56
    Stephen
    0

    This means that whatever happens inside of the using block, the scope will complete - if that is ok, for instance because you are only reading, or you are ok completing even if you are doing writes which may throw, then fine.

    In Core, we use it for reads, but never for writes.

Please Sign in or register to post replies

Write your reply to:

Draft