Click here to Skip to main content
14,981,762 members
Articles / General Programming / Algorithms
Article
Posted 27 Aug 2016

Stats

24.4K views
880 downloads
25 bookmarked

Repository Pattern For .Net

Rate me:
Please Sign up or sign in to vote.
4.71/5 (16 votes)
28 Aug 2016CPOL5 min read
Repository Pattern sample over Multiple Datasource or Resource on Domain Object Model

Introduction

Everybody .Net developers heard repository pattern for Entity Framework. But we cannot use only Entity Framework every time. Sometimes we wants to store data in different ways e.g XML file, Web service, No sql database. Well! What do we to do, should not we use repository pattern? No we have to adapt other sources in Repository pattern. This article subject is how make multiple source in repository pattern. 

Background

The point of I want to draw attention to how to using single repository pattern over multiple datasource or resource. Therefore i cant give you entity framework or another context technology detail. The context layers creating simply. Also usually that technology using web application or service application. But we use console application to facilitate.

Using the code

The solution contains five project. These Core, EFSource, XMLSource, Repository and Console names. 

The first project look at name Core

Image 1

There are two code file. Now create IEntity.cs file

C#
namespace RepositoryPattern.Core.DomainObjecs
{
    public interface IEntity<TKey>
    {
        TKey Id { get; set; }
    }
}

What is mean TKey Id ... We have to use that over all domain objects. Because we dont know kind of Id (primary key can be int, uniqueidentifier or etc.) therefore we defines same function return value in Repository classes.

Now creating Student.cs domain object file.

C#
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace RepositoryPattern.Core.DomainObjecs
{
    [Serializable]
    [Table("Student")]
    public partial class Student: IEntity<int>
    {
        [Key]
        public int Id { get; set; }

        [Required]
        [StringLength(50)]
        public string Name { get; set; }

        [Required]
        [StringLength(50)]
        public string SurName { get; set; }

        [Required]
        [StringLength(5)]
        public string Classroom { get; set; }
    }
}

There are two important points here. These EF Annotations for EF and Serialization Annotations for XML serializtion. If you want use another data sources or resources, you have to add their annotations in to right here. 

Complete that project, next to source projects. Now looking to EFSource named project.

Image 2

There is one file the SchoolContext.cs name. That file a EF context class. Create new one in your new project.

C#
namespace RepositoryPattern.EFSource
{
    using System.Data.Entity;
    using Core.DomainObjecs;

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

        public virtual DbSet<Student> Student { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
        }
    }
}

There is a DbSet from Student domain object, because we have a domain object.

Complete that project in solution. Now looking to the XMLSource named project.

Image 3

There is one file XMLSet.cs name file. That file is same as Context class. Create new one for your new project.

C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;


namespace RepositoryPattern.XMLSource
{
    public class XMLSet<TModel> where TModel : class
    {
        private string m_filename;
        private ICollection<TModel> m_models;

        public XMLSet(string FileName)
        {
            this.m_filename = FileName;
        }

        public ICollection<TModel> Data
        {
            get
            {
                try
                {
                    if (m_models == null) Load();
                }
                catch (Exception)
                {
                    m_models = new List<TModel>();
                }
                return m_models;
            }
            set
            {
                m_models = value;
                Save();
            }
        }

        public void Save()
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<TModel>));
            StreamWriter sw = new StreamWriter(this.m_filename);
            serializer.Serialize(sw, this.m_models);
            sw.Close();
            sw.Dispose();
        }

        public void Load()
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<TModel>));
            StreamReader sr = new StreamReader(this.m_filename);
            this.m_models = serializer.Deserialize(sr) as List<TModel>;
            sr.Close();
            sr.Dispose();
        }

    }
}

This file contains base context methods to save and load operations. Therefore cannot contains model property same as Entity Framework Context. Whatever, the class multiply as context and model container. If you want to develop severally objects XMLContext and XMLSet, is it possible. But this is out of topic, maybe later.

The class want to know two information. First is model class from T, second save file name from constructor function parameter FileName. We will give it the repository.

Complete this project in solution. Now looking to Repository name project.

Image 4

The project contains a lot of files. We looking to files in level order.

IRepository.cs :

This is a interface of repository classes. There are a few function templates for CRUD operations.

C#
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace RepositoryPattern.Repository
{
    public interface IRepository<TEntity, TKey> where TEntity : class
    {
        ICollection<TEntity> GetAll();
        ICollection<TEntity> Find(Expression<Func<TEntity, bool>> predicate);
        TEntity Get(TKey id);
        TKey Insert(TEntity model);
        bool Update(TEntity model);
        bool Remove(TKey id);
        bool Delete(TKey id);
    }
}

I mentioned earlier TKey is used here.

IStudentRepository.cs :

This is a interface Student domain model and also specify params from IRepository. Notice that inheritance point.

C#
using RepositoryPattern.Core.DomainObjecs;

namespace RepositoryPattern.Repository
{
    public interface IStudentRepository: IRepository<Student, int>
    {
    }
}

its purpose is to customize the repository.

EFRepositoryBase.cs :

This is a Entity Framework base repository. There are a lot of functions for managing context class as CRUD.

C#
using RepositoryPattern.Core.DomainObjecs;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace RepositoryPattern.Repository
{
    public abstract class EFRepositoryBase<TContext, TEntity, TKey> : IRepository<TEntity, TKey> where TEntity : class where TContext : DbContext
    {
        private readonly DbContext m_dbContext;

        public EFRepositoryBase()
            : this(Activator.CreateInstance<TContext>())
        {

        }

        public EFRepositoryBase(DbContext context)
        {
            this.m_dbContext = context;
        }

        public virtual bool Delete(TKey id)
        {
            try
            {
                var model = this.Get(id);
                this.GetSet().Remove(model);
                this.m_dbContext.SaveChanges();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public ICollection<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
        {
            try
            {
                return this.GetSet().Where(predicate).ToList();
            }
            catch (Exception ex)
            {
                return new List<TEntity>();
            }
        }

        public TEntity Get(TKey id)
        {
            try
            {
                return this.GetSet().Find(id);
            }
            catch (Exception)
            {
                return null;
            }
        }

        public ICollection<TEntity> GetAll()
        {
            try
            {
                return this.GetSet().ToList();
            }
            catch (Exception)
            {
                return new List<TEntity>();
            }
        }

        public TKey Insert(TEntity model)
        {
            try
            {
                this.GetSet().Add(model);
                this.m_dbContext.SaveChanges();
                IEntity<TKey> entity = model as IEntity<TKey>;
                return entity.Id;
            }
            catch (Exception ex)
            {
                return Activator.CreateInstance<TKey>();
            }
        }

        public bool Remove(TKey id)
        {
            try
            {
                var entity = this.GetSet().Find(id);
                GetSet().Remove(entity);
                this.m_dbContext.SaveChanges();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public bool Update(TEntity model)
        {
            try
            {
                this.GetSet().Attach(model);
                this.m_dbContext.Entry(model).State = EntityState.Modified;
                this.m_dbContext.SaveChanges();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        protected DbSet<TEntity> GetSet()
        {
            return this.m_dbContext.Set<TEntity>();
        }
    }
}

There are three template parameter. First TContext for our context class. Looking the end of line, see where TContext : DbContext . This is mean TContext type is inherited DbContext. You know mean Second and Third params. We already know from other codes.

We looking at insert method. Return inserted object identifier. Here comes into IEntity interface. Because IEntity is only know Id field type. Therefore convert to our entity to IEntity interface for return TKey type.

This class provides convenience for all domain objects. Now you see it

StudentEFRepository.cs :

This file mean is Student domain object repository. There are only some params.

C#
using RepositoryPattern.Core.DomainObjecs;
using RepositoryPattern.EFSource;

namespace RepositoryPattern.Repository
{

    public class StudentEFRepository: EFRepositoryBase<SchoolContext, Student, int>, IStudentRepository
    {

    }
}

Its simple! Which give context, domain model and key type. You think, do you have a hundred domain models? You dont be afraid, its easy that way.

XMLRepositoryBase.cs :

This file same as EFRepositoryBase.cs. But there are tiny difference. First looking file contents

C#
using RepositoryPattern.Core.DomainObjecs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace RepositoryPattern.Repository
{
    public class XMLRepositoryBase<TContext, TEntity, TKey> : IRepository<TEntity, TKey> where TContext : XMLSource.XMLSet<TEntity> where TEntity : class
    {
        private XMLSource.XMLSet<TEntity> m_context;

        public XMLRepositoryBase(string fileName)
        {
            m_context = new XMLSource.XMLSet<TEntity>(fileName);
        }

        public bool Delete(TKey id)
        {
            try
            {
                List<IEntity<TKey>> items = m_context.Data as List<IEntity<TKey>>;
                items.Remove(items.First(f => f.Id.Equals(id)));
                m_context.Data = items as ICollection<TEntity>;
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public ICollection<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
        {
            try
            {
                var list = m_context.Data.AsQueryable().Where(predicate).ToList();
                return list as ICollection<TEntity>;
            }
            catch (Exception)
            {
                return null;
            }
        }

        public TEntity Get(TKey id)
        {
            try
            {
                List<IEntity<TKey>> items = m_context.Data as List<IEntity<TKey>>;
                return items.FirstOrDefault(f => f.Id.Equals(id)) as TEntity;
            }
            catch (Exception)
            {
                return null;
            }

        }

        public ICollection<TEntity> GetAll()
        {
            return m_context.Data;
        }

        public TKey Insert(TEntity model)
        {
            var list = m_context.Data;
            list.Add(model);
            m_context.Data = list;
            return default(TKey);
        }

        public bool Remove(TKey id)
        {
            return Delete(id);
        }

        public bool Update(TEntity model)
        {
            try
            {
                IEntity<TKey> imodel = model as IEntity<TKey>;
                List<IEntity<TKey>> items = m_context.Data as List<IEntity<TKey>>;
                items.Remove(items.FirstOrDefault(f => f.Id.Equals(imodel.Id)));
                items.Add(imodel);
                m_context.Data = items as ICollection<TEntity>;
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }
    }
}

Looking the methods bodies. Our XMLSource doesnt have the context techonogy. Therefore we made filtering, cropping and adding to save and load operations. If you remember these operations EF makes its own. We only call SaveChanges() methods. But we must give back saving data to XMLSource.

StudentXmlRepository.cs :

This file same as StudentEFRepository.cs. There is only one difference in constructor for file name.

C#
using RepositoryPattern.Core.DomainObjecs;
using RepositoryPattern.XMLSource;

namespace RepositoryPattern.Repository
{
    public class StudentXMLRepository: XMLRepositoryBase<XMLSet<Student>, Student, int>, IStudentRepository
    {
        public StudentXMLRepository()
            :base("student.xml")
        {

        }
    }
}

ContextTypes.cs :

We create factory class to next time. This file contains source types for used source type in factory class.

C#
namespace RepositoryPattern.Repository
{
    public enum ContextTypes
    {
        EntityFramework,
        XMLSource
    }
}

RepositoryFactory.cs :

The purpose of this file, which create source type object. Looking contents

C#
namespace RepositoryPattern.Repository
{
    public static class RepositoryFactory
    {

        public static TRepository Create<TRepository>(ContextTypes ctype) where TRepository: class
        {
            switch (ctype)
            {
                case ContextTypes.EntityFramework:
                    if (typeof(TRepository) == typeof(IStudentRepository))
                    {
                        return new StudentEFRepository() as TRepository;
                    }
                    return null;
                case ContextTypes.XMLSource:
                    if (typeof(TRepository) == typeof(IStudentRepository))
                    {
                        return new StudentXMLRepository() as TRepository;
                    }
                    return null;
                default:
                    return null;
            }
        }
    }
}

We have single domain object, therefore check the one type. if you create some domain objects, you must into their repository checks.

Complete this project in solution. Now create console application for testing. The program.cs codes in this way below

C#
using RepositoryPattern.Core.DomainObjecs;
using RepositoryPattern.Repository;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RepositoryPattern.Console
{
    class Program
    {
        static void Main(string[] args)
        {
            bool isRun = true;
            while (isRun)
            {
                System.Console.Clear();
                System.Console.WriteLine("Select a process");
                System.Console.WriteLine("1 - List Students");
                System.Console.WriteLine("2 - Create a Student");
                System.Console.WriteLine("3 - Exit");
                string inputKey = System.Console.ReadLine();
                System.Console.Clear();
                if (inputKey == "1")
                {
                    System.Console.WriteLine("Select source");
                    System.Console.WriteLine("1 - Entity Framework");
                    System.Console.WriteLine("2 - XML Source");
                    inputKey = System.Console.ReadLine();
                    if (inputKey == "1")
                    {
                        var source = RepositoryFactory.Create<IStudentRepository>(ContextTypes.EntityFramework);
                        var items = source.GetAll();
                        foreach (var item in items)
                        {
                            System.Console.WriteLine(item.Name + " " + item.SurName + ": " + item.Classroom);
                        }
                    }
                    else if (inputKey == "2")
                    {
                        var source = RepositoryFactory.Create<IStudentRepository>(ContextTypes.XMLSource);
                        var items = source.GetAll();
                        foreach (var item in items)
                        {
                            System.Console.WriteLine(item.Name + " " + item.SurName + ": " + item.Classroom);
                        }
                    }
                    System.Console.Write("Press any key to continue...");
                    System.Console.ReadKey();
                }
                else if (inputKey == "2")
                {
                    Student student = new Student();
                    System.Console.Write("Name: ");
                    student.Name = System.Console.ReadLine();
                    System.Console.Write("SurName: ");
                    student.SurName = System.Console.ReadLine();
                    System.Console.Write("Classroom: ");
                    student.Classroom = System.Console.ReadLine();
                    System.Console.Clear();
                    System.Console.WriteLine("Select source");
                    System.Console.WriteLine("1 - Entity Framework");
                    System.Console.WriteLine("2 - XML Source");
                    inputKey = System.Console.ReadLine();
                    IStudentRepository source = null;
                    if (inputKey == "1")
                    {
                        source = RepositoryFactory.Create<IStudentRepository>(ContextTypes.EntityFramework);
                    }
                    else if (inputKey == "2")
                    {
                        source = RepositoryFactory.Create<IStudentRepository>(ContextTypes.XMLSource);
                    }
                    try
                    {
                        source.Insert(student);
                    }
                    catch (Exception ex)
                    {
                        System.Console.Write(ex);
                        System.Console.ReadKey();
                        continue;
                    }

                }
                else if (inputKey == "3")
                {
                    isRun = false;
                }
            }
        }
    }
}

Ok now start test.

Image 5

First screen want to get a operation number. We give 1 and look

Image 6

Now its want to get a source number. We give 1 and look

Image 7

There are no records. XmlSource give same result. We continue add record. Press any key to return first screen and give 2.

Image 8

Image 9

Image 10

Image 11

Now we select to want to resource. I choose EF mean 1. Now return the first screen and select 1 for list. And next select EF mean 1.

Image 12

Oh okay smith jhon in 8a classroom. This test over XMLSource return similiar result.

Attention

If you use downloaded project, you must edit connection string in app.config file. You find the student table create sql file in App_Data directory. You must set connectionstring to your environment.

History

This good method for multiple datasource, but you use some techonologies whit that. I am usually use dependency injection methods. And my projects usually have more than 100 domain objects and few datasource. For example EF, ADO, XML, Web Api etc. I sometimes need multiple datasource, this way made is easy.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Halit Yurttaş
Software Developer (Senior)
Turkey Turkey
Senior software developer working over 15+ years. Familiar wide range development experience as database, service, game, multiplatform etc applications. Have turkish blog is http://halityurttas.com.tr for looking somethings.

Comments and Discussions

 
QuestionXML - Update & Delete not working Pin
Yogeesha Naik1-Sep-18 8:49
professionalYogeesha Naik1-Sep-18 8:49 
QuestionExcellent article Pin
M,AqibShehzad18-Sep-16 18:35
professionalM,AqibShehzad18-Sep-16 18:35 
AnswerRe: Excellent article Pin
Halit Yurttaş18-Sep-16 20:10
MemberHalit Yurttaş18-Sep-16 20:10 
QuestionMost of the images are missing Pin
InbarBarkai27-Aug-16 19:25
MemberInbarBarkai27-Aug-16 19:25 
AnswerRe: Most of the images are missing Pin
Halit Yurttaş28-Aug-16 0:54
MemberHalit Yurttaş28-Aug-16 0:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.