3
\$\begingroup\$

When I started learning Repository Pattern with Unity few days ago I was under impression that the main benefit of this pattern is the separation of data layer from the business layer.

In other words, if there is a need to change the way, how application stores the data, it's very easy as only one main model takes care of the communication.

This means, that if application currently saves data into a serialized XML files, it would not be very difficult to change this logic to connect to database instead.

I have found few nice demos that are also using Unit Of Work layer, which seemed very handy. Let me show you a bit of the code I have.

public class UnitOfWork : IUnitOfWork
{
    private readonly RepositoryContext _context;
    public IEmployeeRepository Employees { get; set; }

    public UnitOfWork(RepositoryContext context)
    {
        _context = context;
        Employees = new EmployeeRepository(_context);
    }


    public int Complete()
    {
        return _context.SaveChanges();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

Main Repository Context:

public class RepositoryContext : DbContext
{
    public RepositoryContext() : base("name=RepositoryContext")
    {
    }

    public virtual DbSet<Employee> Employees { get; set; }
    public virtual DbSet<Equipment> Furniture { get; set; }
}

And here is the demo EmployeeRepository:

public class EmployeeRepository:Repository<Employee>, IEmployeeRepository
{
    public EmployeeRepository(RepositoryContext context) : base(context) { }

    public Employee GetEmployeeByName(string sName)
    {
        return MyContext.Employees.FirstOrDefault(n => n.Name == sName);
    }

    public RepositoryContext MyContext
    {
        get { return Context as RepositoryContext; }
    }
}

Employee Repository derives from a generic Repository which looks like this:

public class Repository<T> : Interfaces.Repositories.IRepository<T> where T : class
{
    protected readonly DbContext Context;

    public Repository(DbContext context)
    {
        Context = context;
    }

    public void Add(T item)
    {
        Context.Set<T>().Add(item);
    }

    public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
    {
        return Context.Set<T>().Where(predicate);
    }

    public T Get(int ID)
    {
        return Context.Set<T>().Find(ID);
    }

    public IEnumerable<T> GetAll()
    {
        return Context.Set<T>().ToList();
    }

    public void Remove(T item)
    {
        Context.Set<T>().Remove(item);
    }
}

Here is the question:

As far as my understanding goes, we are directly declaring, that under our Repository expects in it's constructor DbContext, which is afterwards used under all Add / Remove / Find functions under that particular class.

Currently this model is communicating with the database, but if I wanted (for whatever reason) to change this model to save data in the XML file, I would have to completely rewrite all my Repository classes? Or am I missing something here?

If I am wrong and it is easily doable, could anyone show me how to change the code so that we are serializing values into the XML files, please? I am trying to better understand this Repository Pattern, yet for now it's one big chaos for me.

Any help / suggestions regarding this matter would be highly appreciated.

\$\endgroup\$
  • 1
    \$\begingroup\$ @Nico 's answer is good. But also I recommend to change UOW "Complete" method name as SaveChanges. Because, when you say "Complete", it sounds like you are doing another things with savechanges. It's not a general word and its not easy to understand what is inside in it. And check if context is ok before disposing it... Like if (context != null) { context.Dispose(); } \$\endgroup\$ – Lost_In_Library Mar 28 '16 at 8:07
5
\$\begingroup\$

What we are looking at is a long tutorial on Inversion of Control and dependency injection. Basically at the most simpilest form you will be looking to abstract your data layer from you application layer and provide interfaces for your IoC container to deliver to the application.

This can get quite confusing however all we have to do is remeber our application will be using the interfaces to work with the data layer and not the actual implementation.

So for your Unit of work you would resolve IUnityOfWork interface instead. Now here is a quick example I put together. It is by no means a finished or tested product (and the xml side is very fragile) but shows how this can work.

First off I am using the interface IRepository<T> to access the data layer. This can be connected database through EntityFramework or Xml through serialization. This repository is simple.

public partial interface IRepository<T>
    where T : BaseEntity
{
    T GetById(int id);

    T Insert(T item);

    T Update(T item);

    void Delete(T item);

    IQueryable<T> Entities { get; }
}

As simple interface the has the standard insert\update\delete and queryable methods. It is worth noting that I am referencing BaseEntity which is a simple entity with an public int Id { get; set; } property.

Now for Database connections I have created an implementation called EfRepository<T>

public class EfRepository<T> : IRepository<T>
    where T : BaseEntity
{

    public EfRepository(DbContext dbContext)
    {
        if (dbContext == null)
            throw new ArgumentNullException("dbContext");
        _dbContext = dbContext;
    }

    readonly DbContext _dbContext;
    DbSet<T> _entities;

    /// <summary>
    /// Gets the entities as a querably
    /// </summary>
    public IQueryable<T> Entities { get { return DbEntities; } }

    protected virtual DbSet<T> DbEntities { get { return _entities ?? (_entities = _dbContext.Set<T>()); } }

    public void Delete(T item)
    {
        DbEntities.Remove(item);

        _dbContext.SaveChanges();
    }

    public T GetById(int id)
    {
        return DbEntities.Find(id);
    }

    public T Insert(T item)
    {
        DbEntities.Add(item);
        _dbContext.SaveChanges();
        return item;
    }

    public T Update(T item)
    {
        _dbContext.SaveChanges();
        return item;
    }
}

Now this is a very simple class that connects to my Data layer via entity framework. (We will talk about the constructor shortly).

Next I have an Xml Repository named XmlRepository<T>

public class XmlRepository<T> : IRepository<T>
    where T : BaseEntity
{
    public XmlRepository(XmlContext xmlContext)
    {
        _xmlContext = xmlContext;
    }

    readonly XmlContext _xmlContext;
    IList<T> _entities;

    public IQueryable<T> Entities
    {
        get
        {
            return XmlEntities.AsQueryable();
        }
    }

    protected IList<T> XmlEntities
    {
        get
        {
            return _entities ?? (_entities = _xmlContext.Set<T>());
        }
    }

    public void Delete(T item)
    {
        if (XmlEntities.Any(i => i.Id == item.Id))
        {
            XmlEntities.Remove(item);
            _xmlContext.SaveChanges(XmlEntities);
        }
    }

    public T GetById(int id)
    {
        return Entities.FirstOrDefault(x => x.Id == id);
    }

    public T Insert(T item)
    {
        if (item.Id == default(int)) //only add if id = default(int)
        {
            var lastEntity = Entities.LastOrDefault();
            if (lastEntity != null)
                item.Id = lastEntity.Id + 1;
            else
                item.Id = 1;

            XmlEntities.Add(item);
            _xmlContext.SaveChanges(XmlEntities);
        }
        return item;
    }

    public T Update(T item)
    {
        _xmlContext.SaveChanges((IList<T>)Entities);
        return item;
    }
}

Again really simple implementation and also has another constructor reference (XmlContext).

We will get to resolution and setup of Unity shortly but given this we now have a two generic data access repositories. One for EntityFramework and one for Xml File Store.

Now back to the EfRepository it has a constructor parameter DbContext dbContext this is required as we need access to the underlying data context and connections. Now this is actually an injected class of type EfDataContext which has an initializer and a few overloads (plus dependency injection).

public class EfDataContext : DbContext
{
    public static void Initialize()
    {
        var connectionFactory = new SqlConnectionFactory();
#pragma warning disable CS0618
        Database.DefaultConnectionFactory = connectionFactory;
#pragma warning restore CS0618
    }

    public EfDataContext(ApplicationSettings applicationSettings)
        : base(applicationSettings.DataConnectionString)
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new TestEntityMap());
        base.OnModelCreating(modelBuilder);
    }
}

The class above is really used to override the OnModelCreating method to add my Fluent configurations for entity mapping (out of scope here) and to Initialize my connection Factory. You will also note the ApplicationSettings constructor parameter.

Now for the XmlRepository has its own context that is used to load the xml files and save the entities to xml files. This is a very basic (not thread safe) example.

public partial class XmlContext
{
    public XmlContext(ApplicationSettings appSettings)
    {
        _xmlFileLocation = appSettings.DataConnectionString;
    }

    readonly string _xmlFileLocation;

    public void SaveChanges<T>(IList<T> entities)
        where T : BaseEntity
    {
        if (entities == null)
            return;

        string filePath = Path.Combine(_xmlFileLocation, $"{typeof(T).Name}.xml");
        using (var writer = new StreamWriter(filePath))
        {
            var serializer = new XmlSerializer(typeof(List<T>));
            serializer.Serialize(writer, entities);
            writer.Flush();
        }
    }

    public IList<T> Set<T>()
        where T : BaseEntity
    {
        string filePath = Path.Combine(_xmlFileLocation, $"{typeof(T).Name}.xml");
        IList<T> entities = new List<T>();
        if (File.Exists(filePath))
        {
            //XDocument doc = XDocument.Load(filePath);
            using (var stream = File.OpenRead(filePath))
            {
                var serializer = new XmlSerializer(typeof(List<T>));
                entities = serializer.Deserialize(stream) as List<T>;
            }
        }
        return entities.OrderBy(e => e.Id).ToList();
    }
}

This simple class uses the built in XmlSerializer to read\save files to disk by naming the files {Type.Name}.xml. You will also notice it has the ApplicationSettings constructor parameter.

So far we have setup our Contexts and Repositories for access both connected databases (via EntityFramework) and Xml file storage. The next step is to put it together and register everything in our Unity container. This is where the application settings come in handy.

public class ApplicationSettings
{
    [JsonProperty("dataProvider")]
    public DataProviderType DataProvider { get; set; }

    [JsonProperty("dataConnectionString")]
    public string DataConnectionString { get; set; }
}

public enum DataProviderType
{
    SqlServer,
    XmlFileStore
}

This is a simple Json text file that we can parse and load our settings (however I just did it manually in the example). Your implementation can be done any way. The important thing to note for the DataProvider.SqlServer the DataConnectionString is the database connection string and DataProvider.XmlFileStore is the root location to store the xml files.

static void Main(string[] args)
{
    var appSettings = new ApplicationSettings
    {
        DataConnectionString = @"D:\xmltest\",
        DataProvider = DataProviderType.XmlFileStore
    };

    RegisterContainer(appSettings);

    var repo = _container.Resolve<IRepository<TestEntity>>();
    var entity = new TestEntity
    {
        EntityName = "Hello Stack Oveflow"
    };

    repo.Insert(entity);
}

static UnityContainer _container;

public static void RegisterContainer(ApplicationSettings settings)
{
    _container = new UnityContainer();

    _container.RegisterInstance(settings);

    switch (settings.DataProvider)
    {
        case DataProviderType.SqlServer:
            EfDataContext.Initialize();
            _container.RegisterType<DbContext, EfDataContext>();
            _container.RegisterType(typeof(IRepository<>), typeof(EfRepository<>));

            break;

        case DataProviderType.XmlFileStore:
            _container.RegisterType<XmlContext>();
            _container.RegisterType(typeof(IRepository<>), typeof(XmlRepository<>));
            break;

        default:
            throw new Exception($"The DataProvider Type {settings.DataProvider} is unknown");
    }
}

Now the fun begins. Basically at this point we know our data access will be through the use of the generic interface IRepository<T>. We will use our ApplicationSettings class to dicate the provider and the connection string. From here we adapt our Unity Registration methods to register one instance of the other.

To walk through the RegisterContainer method we simply pass in the instance of the ApplicationSettings. We then register the instance of the ApplicationSettings (essentially a singleton). Next we then determine which type of provider we are going to load.

If the type is DataProvider.SqlServer we simply setup the EfDataContext with the Initialize() method and then register the DbContext dependency as EfDataContext (which requires the ApplicationSettings dependency already registered. Next we register the IRepository<T> dependency as EfRepository<T>. Where EfRepository<T> has the dependency DbContext which has been registered as EfDataContext.

Alternativly for the DataProvider.XmlFileStore type we register the dependency type XmlContext and then register the IRepository<T> dependency as XmlRepository<T>. Where XmlRepository<T> has the XmlContext dependency registered earlier.

Now that we have gotten this far we can answer the question. Finally moving back up to the Main method we can now call _container.Resolve<IRepository<TestEntity>>() which regardless of our data storage type gives us the same common interface.

Back to the original question

Currently this model is communicating with the database, but if I wanted (for whatever reason) to change this model to save data in the XML file, I would have to completely rewrite all my Repository classes? Or am I missing something here?

Following the strategy above and leveraging your IoC container all you need to rewrite is your IRepository<T> implementation to your different storage type. For example you could easily write a JSON serializer similar to the XmlRepository<T> and XmlContext. You could change your EfRepository<T> to directly call sql queries.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.