Quantcast
Channel: Fairway Technologies
Viewing all articles
Browse latest Browse all 53

SharpRepository: Configuration

$
0
0

If you haven’t read my introductory post on SharpRepository you should give it a quick read as it provides a nice overview of the basic usage of SharpRepository.  In that post, I use the simplest method for creating my repository, I just new it up like so:

var orderRepository = new InMemoryRepository<Order>();

While this definitely works, it isn’t the best way.  Why?  Well, if you want to switch to an Ef5Repository or want to add caching, you will need to change the code and recompile.  Plus, you are using dependency injection and inversion of control right?  Well this doesn’t work with those patterns of course.   In this post I will cover some of the various ways to create your repository.

Configuration Overview

Before getting into the code for loading up a repository via configuration settings it will be helpful to understand the SharpRepository hierarchy.  Each repository has a specific type of backend that it queries (e.g. InMemory, Entity Framework, RavenDb, MongoDb, Db4o,  Xml, etc.), a caching strategy (e.g. None, Standard, Timeout), and a caching provider (e.g. Memory, memcached, Azure, etc.).

The caching strategy tells the repository the rules for caching.  The Timeout Strategy uses a simple time based rule, for example, cache the query results for 30 seconds, while the Standard Strategy uses Generational and Write-Through caching techniques to provide a more sophisticated caching logic.  And the No Caching Strategy does not do any caching at all (it’s not just a clever name).

The caching provider on the other hand is the specific cache technology that is used.  For simple websites the InMemory caching provider works great and uses the ASP.NET built-in System.Runtime.Caching.MemoryCache. For more advanced needs like distributed caching you would use the memcached provider or the Windows Azure provider and store the cache across multiple servers.

The beauty of this is that both of these can be extended by inheriting from ICachingStrategy or ICachingProvider if the need arises.

Create from Configuration Object

You can create a SharpRepositoryConfiguration object and use that to create your repository.  The SharpRepositoryConfiguration object allows you to build a “profile” and then when you create a specific repository you can choose which “profile” to use.  I think that the code below will help show what I mean.

            var config = new SharpRepositoryConfiguration();
            config.AddRepository(new Ef5RepositoryConfiguration("ef5", "DefaultConnection")); // EF repository named ef5 using the DefaultConnection connection string
            config.AddRepository(new InMemoryRepositoryConfiguration("inmemory", "standard", "memcached")); // InMemoryRepository named default using the none CachingStrategy

            config.AddCachingStrategy(new TimeoutCachingStrategyConfiguration("timeout", 30)); // Timeout strategy named timeout with 30 second cache time
            config.AddCachingStrategy(new StandardCachingStrategyConfiguration("standard")); // Standar d strategy named standard
            config.AddCachingStrategy(new NoCachingStrategyConfiguration("none")); // No caching named none

            config.AddCachingProvider(new InMemoryCachingProviderConfiguration("inmemory")); // InMemory provider name inmemory
            config.AddCachingProvider(new MemCachedCachingProviderConfiguration("memcached", "memcached"));  // memcached provider named memcached and using the memcached section in config file

            config.DefaultRepository = "ef5";
            config.DefaultCachingStrategy = "timeout";
            config.DefaultCachingProvider = "inmemory";

            var userRepos = RepositoryFactory.GetInstance<User, int>(config); // uses the Ef5Repository
            var orderRepos = RepositoryFactory.GetInstance<Order, int>(config, "inmemory"); // uses InMemoryRepository with Standard caching strategy and memcached

As you can see above, the SharpRepositoryConfiguration gets loaded with multiple repositories that can use the various caching strategies and caching providers.  Note that everything is referred to by name.  The specific repository configuration (e.g. Ef5RepositoryConfiguration and InMemoryRepositoryConfiguration) always takes a name first, then anything specific to that type (like the connection string name for EF5), and optionally a caching strategy and provider can be provided last.  If no caching strategy or provider is used, the DefaultCachingStrategy and DefaultCachingProvider are used, and if those aren’t set then no caching is used by default.

This does provide the benefit of being able to setup your configuration in a single location and then pass that configuration in when loading up your repository.  You can then make changes in a single location and it will effect everywhere that you create repositories.  That being said, there may be some cases where you would need programmatic access to the configuration, but most likely this is not what you should be doing.  Well, then why did you show me this?  Good question.  It leads me into the next way of doing things … Web.config (or App.config).

Configuration File

The same basic structure as the configuration object can be used in your Web.config (or App.config).  Note: if you install the NuGet packages it will stub out these sections for you.  For example, if you install SharpRepository.Ef5Repository it will include the proper configSection at the top of the file and create the specific repository using the DefaultConnection connectionstring name.  You can then go in and edit this, but it allows you to not have to code out the assembly type paths, which are kind of a pain.

Here is an example of what the configuration used above would look like in your configuration file:

  <sharpRepository>
    <repositories default="ef5">
      <repository name="ef5" connectionString="DefaultConnection" factory="SharpRepository.Ef5Repository.Ef5ConfigRepositoryFactory, SharpRepository.Ef5Repository"  />
      <repository name="inmemory" factory="SharpRepository.Repository.InMemoryRepositoryFactory, SharpRepository.Repository" cachingStrategy="standard" cachingProvider="memcached" />
    </repositories>
    <cachingProviders default="inmemory">
      <cachingProvider name="inmemory" factory="SharpRepository.Repository.Caching.InMemoryConfigCachingProviderFactory, SharpRepository.Repository" />
      <cachingProvider name="memcached" factory="SharpRepository.Caching.Memcached.MemCachedConfigCachingProviderFactory, SharpRepository.Caching.Memcached" />
    </cachingProviders>
    <cachingStrategies default="timeout">
      <cachingStrategy name="standard" generational="true" writeThrough="true" factory="SharpRepository.Repository.Caching.StandardConfigCachingStrategyFactory, SharpRepository.Repository" />
      <cachingStrategy name="timeout" timeout="30" factory="SharpRepository.Repository.Caching.TimeoutConfigCachingStrategyFactory, SharpRepository.Repository" />
      <cachingStrategy name="none" factory="SharpRepository.Repository.Caching.NoCachingConfigCachingStrategyFactory, SharpRepository.Repository" />
    </cachingStrategies>
  </sharpRepository>

Then in your C# code, you would load them up with the similar calls to RepositoryFactory.GetInstance like so:

var userRepos = RepositoryFactory.GetInstance<User, int>(); // uses the Ef5Repository since it's the default
var orderRepos = RepositoryFactory.GetInstance<Order, int>("inmemory"); // uses InMemoryRepository with Standard caching strategy and memcached

This is a much better approach than using the configuration objects because if you need to make a change, like changing your caching or changing from an InMemoryRepository during your prototyping phase to an Ef5Repository, you can just edit your Web.config file and there is no need for a recompile.

But what if I want to add custom methods to my repository or use constructor injection, how would I do that?  Wow, you are great at helping me move to my next section, thanks.

Custom repositories

There are situations where you will want to create a custom repository so you can add your own methods to it, or just because you like programming against an IUserRepository instead of IRepository<User, int>.  So how would you do that?

    public interface IUserRepository : IRepository<User, int>
    {
        User GetAdminUser();
    }

    public class UserRepository : InMemoryRepository<User, int>, IUserRepository
    {
        public User GetAdminUser()
        {
            return Find(x => x.Email == "admin@admin.com");
        }
    }

Here we create your own UserRepository that has a custom GetAdminUser() method (side note: personally I would put these kind of methods in my Service layer but not everyone would do that, especially on simpler applications).  This isn’t ideal because as mentioned before we are now hard-coding the use of an InMemoryRepository and we would need to change this and recompile if we switch to an Ef5Repository or if we want to add caching logic to this repository.  So here is a cleaner way to handle this:

    public interface IUserRepository : IRepository<User, int>
    {
        User GetAdminUser();
    }

    public class UserRepository : ConfigurationBasedRepository<User, int>, IUserRepository
    {
        public User GetAdminUser()
        {
            return Find(x => x.Email == "admin@admin.com");
        }
    }

Looks pretty much the same huh?  Well there is one big difference, instead of inheriting from InMemoryRepository we are inheriting from ConfigurationBasedRepository.  ConfigurationBasedRepository will use the configuration files to decide which type of repository to create .  Behind the scenes it is calling RepositoryFactory.GetInstance<User, int>() like we used above.

This setup gives you the ability code against IUserRepository while controlling the details of the repository and caching, etc. from your configuration file, allowing for easier maintenance (especially when in development and testing things).

What about dependency injection?

Personally my favorite Ioc container is StructureMap so that is what I’ll be using in my examples.  The same can be done in the Ioc container of your choice.

Let’s say you are creating an MVC application and you have a UserController that will need access to your IUserRepository (or IRepository<User, int> if you haven’t created a custom repository).  You will want to inject the repository into the constructor like this:

        private readonly IUserRepository _repository;
        public UserController(IUserRepository repository)
        {
            _repository = repository;
        }

Or like this:

        private readonly IRepository<User, int> _repository;
        public UserController(IRepository<User, int> repository)
        {
            _repository = repository;
        }

The only thing left to do is wire up StructureMap to be able to handle this.  Let’s just say that getting StructureMap to handle the generic arguments and use them to load the repository from the configuration file took a little Googling.  To make this easy to implement we have created some extension methods to use in your StructureMap registry.  They are located in the source code under the Ioc directory in the SharpRepository.Ioc.StructureMap project.  I will include the code here for convenience:

        public static LambdaInstance<object> ForRepositoriesUseSharpRepository(this IInitializationExpression initialization, string repositoryName = null)
        {
            initialization.Scan(scan => scan.IncludeNamespaceContainingType<IAmInRepository>());

            return initialization.For(typeof(IRepository<,>))
                                 .Use(context =>
                                 {
                                     var entityType = context.BuildStack.Current.RequestedType.GetGenericArguments()[0];
                                     var keyType = context.BuildStack.Current.RequestedType.GetGenericArguments()[1];

                                     return RepositoryFactory.GetInstance(entityType, keyType, repositoryName);
                                 }
                );
        }

        public static LambdaInstance<object> ForRepositoriesUseSharpRepository(this IInitializationExpression initialization, ISharpRepositoryConfiguration configuration)
        {
            initialization.Scan(scan => scan.IncludeNamespaceContainingType<IAmInRepository>());

            return initialization.For(typeof(IRepository<,>))
                                 .Use(context =>
                                 {
                                     var entityType = context.BuildStack.Current.RequestedType.GetGenericArguments()[0];
                                     var keyType = context.BuildStack.Current.RequestedType.GetGenericArguments()[1];

                                     return RepositoryFactory.GetInstance(entityType, keyType, configuration);
                                 }
                );
        }

Then all you have to do is include the call to ForRepositoriesUserSharpRepository() extension method in your Initialize method like so:

ObjectFactory.Initialize(x =>
            {
                x.Scan(scan =>
                {
                    scan.TheCallingAssembly();
                    scan.WithDefaultConventions();
                });

                x.ForRepositoriesUseSharpRepository();
            });

That’s it.  Now StructureMap will load up the proper repository based on your configuration file when you request an IRepository<User, int> or a custom repository that inherits from it like IUserRepository in our example above.  If you use a different Ioc, I’m sure that something similar can be done to wire it up to handle requests for IRepository<T, TKey>, and if you do get that working we’d love to include your extension methods in the source code for others, just let us know.

Conclusion

Sorry for the long winding path to getting where I wanted to go, but hopefully showing the flexibility allowed is helpful and will allow you to use SharpRepository however you are most comfortable.  Personally, I like to use StructureMap to handle my dependency injections and will code against IRepository<User, int> unless there is a good reason to create my own custom repository.  In that case, I will have UserRepository inherit from ConfigurationBasedRepository and IUserRepository and code against the IUserRepositry interface.


Viewing all articles
Browse latest Browse all 53

Trending Articles