Shrinkr - Url Shrinking Service Developed with Entity Framework 4.0, Unity, ASP.NET MVC And jQuery (Part 2)

In the previous post we have created our initial domain model, in this post I will show you how the domain model  is mapped to database with Entity Framework 4.0. But before that I would like to discuss how I usually structure the Visual Studio Projects. Most often I prefer to have a one class library and one web project where each has its own unit test project and only one integration test project, for example:

  • Shrinkr.Core
  • Shrinkr.Core.UnitTest
  • Shrinkr.Web
  • Shrinkr.Web.UnitTest
  • Shrinkr.IntegrationTest

The Core Project is then further divided into following folders:

  • Shrink.Core
    • Common (Utility, Invariant etc etc)
    • EntityObjects (Contains both domain objects and DTOs)
    • Extensions (Extension methods)
    • Repositories (Contains both interface and implementation)
    • Services (Contains both interface and implementation)
    • Infrastructure
      • Caching
      • Database
      • Email
      • FileSystem
      • Http
      • IoC
      • Logging
      • etc etc etc.

But since it is an open source project, I should give privilege to community to replace any part of if with the their preferred technology. for example, replacing Entity Framework with NHibernate or may be Azure Storage, Unity with StructureMap or NInject etc etc. So I decided to structure it based upon the component dependency.

SoutionExplorer

As you can understand that the Shrinkr.Infrastructure.Microsoft.Practices will contain the Unity and other EntLib related codes and Shrinkr.Infrastructure.EntityFramework for the concrete repositories and other data access codes. Although Entity Framework Team has released few more add-ons (known as Features CTP1), but for the time being we will not use those. The first thing we will do is create a new class which inherits from the ObjectContext, lets name it as Database.

Database

namespace Shrinkr.Infrastructure.EntityFramework
{
    using System.Data.Objects;
    using System.Diagnostics;

    public class Database : ObjectContext
    {
        private IObjectSet<User> users;
        private IObjectSet<ShortUrl> shortUrls;
        private IObjectSet<Alias> aliases;
        private IObjectSet<Visit> visits;

        public Database(IConfigurationManager configurationManager, string connectionStringName) : base(GetConnectionString(configurationManager, connectionStringName), "ShrinkrEntities")
        {
            ContextOptions.DeferredLoadingEnabled = true;
        }

        public IObjectSet<User> Users
        {
            [DebuggerStepThrough]
            get
            {
                if (users == null)
                {
                    users = ObjectSet<User>();
                }

                return users;
            }
        }

        public IObjectSet<ShortUrl> ShortUrls
        {
            [DebuggerStepThrough]
            get
            {
                if (shortUrls == null)
                {
                    shortUrls = ObjectSet<ShortUrl>();
                }

                return shortUrls;
            }
        }

        public IObjectSet<Alias> Aliases
        {
            [DebuggerStepThrough]
            get
            {
                if (aliases == null)
                {
                    aliases = ObjectSet<Alias>();
                }

                return aliases;
            }
        }

        public IObjectSet<Visit> Visits
        {
            [DebuggerStepThrough]
            get
            {
                if (visits == null)
                {
                    visits = ObjectSet<Visit>();
                }

                return visits;
            }
        }

        public virtual IObjectSet<TEntity> ObjectSet<TEntity>() where TEntity : class, IEntity
        {
            return CreateObjectSet<TEntity>();
        }

        public virtual void Commit()
        {
            SaveChanges();
        }

        private static string GetConnectionString(IConfigurationManager configurationManager, string connectionStringName)
        {
            Check.Argument.IsNotNull(configurationManager, "configurationManager");
            Check.Argument.IsNotNullOrEmpty(connectionStringName, "connectionStringName");

            string connectionString = configurationManager.ConnectionString(connectionStringName);

            return connectionString;
        }
    }
}

Nothing complex, we are just exposing our Domain Entities as properties like Users, ShortUrls, Aliases etc. One important thing you should check in the above code is that instead of using the CreateObjectSet<T>() in the properties, I have created a virtual method ObjectSet<T>() which in turns calls the CreateObjectSet<T>. The reasons behind creating this new virtual method are:

  1. CreateObjectSet<T> does not return IObjectSet<T>, instead it returns the concrete ObjectSet<T>.
  2. CreateObjectSet<T> is not a virtual method, which means we cannot mock it the unit tests (Although it is debatable whether to write unit tests over any Linq provider, but that is an another story).

I am not sure why the Entity Framework team decided to return the concrete class instead of the interface also not making the method virtual, if they did, we do not have to write this workarounds and I am pretty sure many people will call it a design smell and finds it frustrating. Next, we will create the base repository which the UserRepository and ShortUrlRepository inherits. Although it is a common practise specially in the NHibernate world to have only one  Repository<T> instead of individual repository for each aggregate root which I will discuss in my next post.

RepositoryBase

namespace Shrinkr.Infrastructure.EntityFramework
{
    public abstract class RepositoryBase<TEntity> where TEntity : class, IEntity
    {
        protected RepositoryBase(Database database)
        {
            Check.Argument.IsNotNull(database, "database");

            Database = database;
        }

        protected Database Database
        {
            get;
            private set;
        }

        public virtual void Add(TEntity entity)
        {
            Check.Argument.IsNotNull(entity, "entity");

            Database.ObjectSet<TEntity>().AddObject(entity);
        }

        public virtual void Delete(TEntity entity)
        {
            Check.Argument.IsNotNull(entity, "entity");

            Database.ObjectSet<TEntity>().DeleteObject(entity);
        }
    }
}

Check that we have marked both Add and Delete method as virtual so that the concrete repository can override if it has some extra logic. Now, lets create the Unit Test for it.

RepositoryBaseTests

namespace Shrinkr.Infrastructure.EntityFramework.UnitTests
{
    using System.Collections.Generic;

    using Moq;
    using Xunit;

    public class RepositoryBaseTests
    {
        private readonly Mock<FakeObjectSet<Dummy>> objectSet;
        private readonly Mock<Database> database;
        private readonly DummyRepository repository;

        public RepositoryBaseTests()
        {
            var objects = new List<Dummy> {
                                            new Dummy { Id = 1},
                                            new Dummy { Id = 2},
                                            new Dummy { Id = 3}
                                          };

            objectSet = new Mock<FakeObjectSet<Dummy>>(objects);

            var configurationManager = new Mock<IConfigurationManager>();
            configurationManager.Setup(mgr => mgr.ConnectionString(It.IsAny<string>())).Returns("Dummy Connection String");

            database = new Mock<Database>(configurationManager.Object, "Dummy");
            database.Setup(db => db.ObjectSet<Dummy>()).Returns(objectSet.Object);

            repository = new DummyRepository(database.Object);
        }

        [Fact]
        public void Should_be_able_to_add()
        {
            objectSet.Setup(set => set.AddObject(It.IsAny<Dummy>())).Verifiable();

            repository.Add(new Dummy());

            objectSet.Verify();
        }

        [Fact]
        public void Should_be_able_to_delete()
        {
            objectSet.Setup(set => set.DeleteObject(It.IsAny<Dummy>())).Verifiable();

            repository.Delete(new Dummy());

            objectSet.Verify();
        }
   } 

    public class Dummy : IEntity
    {
        public long Id
        {
            get;
            set;
        }
    }

    public class DummyRepository : RepositoryBase<Dummy>
    {
        public DummyRepository(Database database) : base(database)
        {
        }
    }
}

To test the RepositoryBase we have to create few fake classes and in the unit test we are using those. The reason is, if we create mock of RepositoryBase which methods are virtual the mock framework(Moq) will replace those, so the codes of Add and Delete will not be executed. By using the DummyRepository we are making sure the RepositoryBase methods are called. Another important thing you might have noticed that when setting up expectations on the database.ObjectSet (line 28) method we are using another new class FakeObjectSet. This is the another frustrating part of Entity Framework, you cannot pass/set collection of objects in the ObjectSet<T>. This is what the FakeObjectSet does, allowing us to pass the collection of objects so that our unit test can run properly.

FakeObjectSet

namespace Shrinkr.Infrastructure.EntityFramework.UnitTests
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Data.Objects;
    using System.Linq;
    using System.Linq.Expressions;

    public abstract class FakeObjectSet<T> : IObjectSet<T> where T : class
    {
        private readonly IEnumerable<T> objects;

        protected FakeObjectSet(IEnumerable<T> objects)
        {
            this.objects = objects;
        }

        public Expression Expression
        {
            get
            {
                return objects.AsQueryable().Expression;
            }
        }

        public IQueryProvider Provider
        {
            get
            {
                return objects.AsQueryable().Provider;
            }
        }

        public Type ElementType
        {
            get
            {
                return typeof(T);
            }
        }

        public abstract void AddObject(T entity);

        public abstract void Attach(T entity);

        public abstract void DeleteObject(T entity);

        public IEnumerator<T> GetEnumerator()
        {
            return objects.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }
    }
}

Now, creating the concrete repositories are plain and simple.

UserRepository

namespace Shrinkr.Infrastructure.EntityFramework
{
    using System.Linq;

    public class UserRepository : RepositoryBase<User>, IUserRepository
    {
        public UserRepository(Database database) : base(database)
        {
        }

        public User GetById(long id)
        {
            Check.Argument.IsNotZeroOrNegative(id, "id");

            return Database.Users.SingleOrDefault(user => user.Id == id);
        }

        public User GetByName(string name)
        {
            Check.Argument.IsNotNullOrEmpty(name, "name");

            return Database.Users.SingleOrDefault(user => user.Name == name);
        }

        public User GetByApiKey(string apiKey)
        {
            Check.Argument.IsNotNullOrEmpty(apiKey, "apiKey");

            return Database.Users.SingleOrDefault(user => user.ApiSetting.Key == apiKey);
        }
    }
}

UserRepositoryTests

namespace Shrinkr.Infrastructure.EntityFramework.UnitTests
{
    using System;
    using System.Collections.Generic;

    using Moq;
    using Xunit;

    public class UserRepositoryTests
    {
        private const long UserId = 1;
        private const string UserName = "http://kazimanzurrashid.myopenid.com";
        private readonly static string ApiKey = Guid.NewGuid().ToString().ToUpperInvariant();

        private readonly Mock<Database> database;
        private readonly UserRepository repository;

        public UserRepositoryTests()
        {
            var users = new List<User> { new User { Id = UserId, Name = UserName } };

            users[0].ApiSetting.Key = ApiKey;

            var userSet = new Mock<FakeObjectSet<User>>(users);

            var configurationManager = new Mock<IConfigurationManager>();
            configurationManager.Setup(mgr => mgr.ConnectionString(It.IsAny<string>())).Returns("Dummy Connection String");

            database = new Mock<Database>(configurationManager.Object, "Dummy");

            database.Setup(db => db.ObjectSet<User>()).Returns(userSet.Object);

            repository = new UserRepository(database.Object, queryFactory);
        }

        [Fact]
        public void Should_be_able_to_get_by_id()
        {
            var user = repository.GetById(UserId);

            Assert.Equal(UserName, user.Name);
        }

        [Fact]
        public void Should_be_able_to_get_by_name()
        {
            var user = repository.GetByName(UserName);

            Assert.Equal(UserId, user.Id);
        }

        [Fact]
        public void Should_be_able_to_get_by_api_key()
        {
            var user = repository.GetByApiKey(ApiKey);

            Assert.Equal(UserId, user.Id);
        }
    }
}

ShortUrlRepository

namespace Shrinkr.Infrastructure.EntityFramework
{
    using System.Collections.Generic;
    using System.Linq;

    public class ShortUrlRepository : RepositoryBase<ShortUrl>, IShortUrlRepository
    {
        public ShortUrlRepository(Database database) : base(database)
        {
        }

        public ShortUrl GetById(long id)
        {
            Check.Argument.IsNotZeroOrNegative(id, "id");

            return Database.ShortUrls.SingleOrDefault(shortUrl => shortUrl.Id == id);
        }

        public ShortUrl GetByHash(string hash)
        {
            Check.Argument.IsNotNullOrEmpty(hash, "hash");

            return Database.ShortUrls.SingleOrDefault(shortUrl => shortUrl.Hash == hash);
        }

        public ShortUrl GetByAliasName(string aliasName)
        {
            Check.Argument.IsNotNullOrEmpty(aliasName, "aliasName");

            return Database.ShortUrls.SingleOrDefault(shortUrl => shortUrl.Aliases.Any(alias => alias.Name == aliasName));
        }

        public PagedResult<ShortUrl> FindByUserId(long userId, int start, int max)
        {
            Check.Argument.IsNotZeroOrNegative(userId, "userId");
            Check.Argument.IsNotNegative(start, "start");
            Check.Argument.IsNotNegative(max, "max");

            int total = Database.Aliases.Count(alias => alias.User.Id == userId);

            IQueryable<ShortUrl> result = Database.Aliases.Where(alias => alias.User.Id == userId)
                                                  .OrderByDescending(alias => alias.CreatedAt)
                                                  .Select(alias => alias.ShortUrl)
                                                  .Skip(start)
                                                  .Take(max);

            return new PagedResult<ShortUrl>(result, total);
        }
    }
}

ShortUrlRepositoryTests

namespace Shrinkr.Infrastructure.EntityFramework.UnitTests
{
    using System.Collections.Generic;

    using Moq;
    using Xunit;

    public class ShortUrlRepositoryTests
    {
        private readonly Mock<Database> database;
        private readonly ShortUrlRepository repository;

        public ShortUrlRepositoryTests()
        {
            var shortUrls = new List<ShortUrl> {
                                                    new ShortUrl { Id = 1, Title = "Shrinkr.com",Url = "http://shrinkr.com", Hash = "http://shrinkr.com".Hash() },
                                                    new ShortUrl { Id = 2, Title = "DotNetShoutout.com", Url = "http://dotnetshoutout.com", Hash = "http://dotnetshoutout.com".Hash() }
                                               };

            shortUrls[1].Aliases.Add(new Alias { Name = "dtntshtt" });

            var shortUrlSet = new Mock<FakeObjectSet<ShortUrl>>(shortUrls);

            var user = new User { Id = 1 };

            var aliases = new List<Alias>{
                                            new Alias { Id = 1, User = user, ShortUrl = shortUrls[0] },
                                            new Alias { Id = 2, User = user, ShortUrl = shortUrls[1] },
                                         };

            var aliasSet = new Mock<FakeObjectSet<Alias>>(aliases);

            var configurationManager = new Mock<IConfigurationManager>();
            configurationManager.Setup(mgr => mgr.ConnectionString(It.IsAny<string>())).Returns("Dummy Connection String");

            database = new Mock<Database>(configurationManager.Object, "Dummy");
            database.Setup(db => db.ObjectSet<ShortUrl>()).Returns(shortUrlSet.Object);
            database.Setup(db => db.ObjectSet<Alias>()).Returns(aliasSet.Object);

            repository = new ShortUrlRepository(database.Object);
        }

        [Fact]
        public void Should_be_able_to_get_by_id()
        {
            var shortUrl = repository.GetById(1);

            Assert.Equal(1, shortUrl.Id);
        }

        [Fact]
        public void Should_be_able_to_get_by_hash()
        {
            var shortUrl = repository.GetByHash("http://shrinkr.com".Hash());

            Assert.Equal(1, shortUrl.Id);
        }

        [Fact]
        public void Should_be_able_to_get_by_alias_name()
        {
            var shortUrl = repository.GetByAliasName("dtntshtt");

            Assert.Equal(2, shortUrl.Id);
        }

        [Fact]
        public void Should_be_able_to_find_by_user_id()
        {
            var shortUrls = repository.FindByUserId(1, 0, 10);

            Assert.Equal(2, shortUrls.Total);
            Assert.Equal(2, shortUrls.Result.Count);
        }
    }
}

and that’s it, we have completed the initial implementation of our Repositories. What do you think, what it is currently lacking? Yes we are not taking the advantages of Compiled Queries. In the next post, I will show how you can use the both compiled and regular queries in your repositories.

Stay tuned!!!

Shout it

7 Comments

  • public ShortUrl GetById(long id)
    {
    Check.Argument.IsNotZeroOrNegative(id, "id");

    return Database.ShortUrls.SingleOrDefault(shortUrl => shortUrl.Id == id);
    }

    public User GetById(long id)
    {
    Check.Argument.IsNotZeroOrNegative(id, "id");

    return Database.Users.SingleOrDefault(user => user.Id == id);
    }

    Why not use something more general, like

    public T GetById(object id)
    {

    var itemParameter = Expression.Parameter(_type, "item");

    var whereExpression = Expression.Lambda<Func>
    (
    Expression.Equal(
    Expression.Property(
    itemParameter,
    LinqToSqlUtil.GetIdPropertyName(_type)
    ),
    Expression.Constant(id)
    ),
    new[] { itemParameter }
    );

    return GetAll().FirstOrDefault(whereExpression);

    }

    public IQueryable GetAll()
    {
    return context.GetTable();
    }

  • @Guest123: Pls wait for my next post, where I will show how to use CompiledQuery.

  • How about moving all the text strings (like error messages etc.) from the code itself to .resx files and also moving all data related code into seporate project - Shrinkr.Data

  • Thanks for the brilliant sharing! Do keep us updated with such informative posts.

  • @Koistya: Yes I have this in my mind, but unfortunately Vs2010 does not support the Resource Refactoring tool, &nbsp; Don't worry I will implement it prior we hit v1.0.

  • Hi Kazi,

    Brilliant as usual ;) Just one question - have you previously published the code for all your Check.Argument.DoesntDoThisOrThat() methods somewhere? It is pretty obvious what they do, but I would like to see how you implemented it.

    Thanks!

    // Tomas

  • Hi again,

    Whenever I run the constructor of the Database class, I get an error saying "System.ArgumentException: Format of the initialization string does not conform to specification starting at index 0..".

    My code for the Database class is identical with yours, except I have the following parameters (not hard coded, but verified when debugging) to the inherited constructor:

    : base("Dummy connStr", "BookingEntities")

    What is the problem with this code, and how do I solve it?

    Thanks!

    // Tomas

Comments have been disabled for this content.