经典三层架构

The Repository Pattern with EF Code First & Dependency Injection in ASP.NET MVC3

Ray_Liang, 4 Jul 2011 GPL3

       4.67 (58 votes)
Rate this:
vote 1vote 2vote 3vote 4vote 5
In this article, I will explain how to implement Respository pattern with EF4.1 code first and how to enable dependency injection feature to your ASP.NET MVC3 Applications.
Download demo - 1.42 MB
Introduction
These days, I started using EF code first and thought about how could I take advantage from it for my recent project. My project is based on MVC3 and there are some challenges to continue updating this project. At that time, I must finish my job in a very short deadline so fast so good I just define some data access interfaces and implemented them with EF4.

There are some things in my project troubling me:

I have used the EF entities in my controllers anywhere that there is a connection translation error throwing infrequently.
Modules coupling degree becoming closed.
Manage services and contexts life cycle are difficult.
Obviously, it needs to refactor my project with a new architecture designing.

Preparing
Before starting my work, I need to do some researching and learning. I have read a large numbers of articles about DDD, SOA, EF code first, Dependency Injection pattern, Repository pattern even watch all web casts about MVC on ASP.NET and a week later...

I have really found some useful resources about those topics and I am sharing them below:

ASP.NET MVC Storefront Starter Kit videos
Repository Pattern - by Martin fowler
Microsoft Unity 2.0
A dependency injection library for .NET
"ASP.NET MVC 3 Service Location" - by Brad Wilson
Modeling
At first, I need to create the data model classes (the POCO objects) for this demo I will create Category and Product.

Hide   Shrink    Copy Code
namespace Demo.DAL
{
    public class Category
    {
        [Key]
        public int ID { get; set; }

        public virtual string Name { get; set; }

        public virtual string Title { get; set; }

        public virtual ICollection<Product> Products { get; set; }
    }

    public class Product
    {
        [Key]
        public int ID { get; set; }

        public int CategoryID { get; set; }

        [ForeignKey("CategoryID")]
        public virtual Category Category {get;set;}

        public string Name { get; set; }

        public string Title { get; set; }

        public string Description{get;set;}

        public decimal Price { get; set; }
    }

    public class DB : DbContext
    {
        public DB() : base("DemoDB") { }
        public DbSet<Category> Categories { get; set; }
        public DbSet<Product> Products { get; set; }
    }
}
model-diagram.png

Repository Pattern with EF Code First
When I finish my learning on EF code first and Repository, I couldn‘t find any solutions for implementing Repository pattern with EF code first. EF4.1 is so powerful for building the DAL that it allows us to use POCO objects to define our database and BO (business objects) instead of inheriting from Entity that could give me a big hand. I could very easily define the repository interface like below:

Hide   Shrink    Copy Code
public interface IRepository<T>: IDisposable  where T : class
{
        /// <summary>
        /// Gets all objects from database
        /// </summary>
        IQueryable<T> All();

       /// <summary>
        /// Gets objects from database by filter.
        /// </summary>
        /// <param name="predicate">Specified a filter</param>
        IQueryable<T> Filter(Expression<Func<T, bool>> predicate);

        /// <summary>
        /// Gets objects from database with filting and paging.
        /// </summary>
        /// <typeparam name="Key"></typeparam>
        /// <param name="filter">Specified a filter</param>
        /// <param name="total">Returns the total records count of the filter.</param>
        /// <param name="index">Specified the page index.</param>
        /// <param name="size">Specified the page size</param>
        IQueryable<T> Filter<Key>(Expression<Func<T, bool>> filter ,
            out int total, int index = 0, int size = 50);

        /// <summary>
        /// Gets the object(s) is exists in database by specified filter.
        /// </summary>
        /// <param name="predicate">Specified the filter expression</param>
        bool Contains(Expression<Func<T, bool>> predicate);

        /// <summary>
        /// Find object by keys.
        /// </summary>
        /// <param name="keys">Specified the search keys.</param>
        T Find(params object[] keys);

        /// <summary>
        /// Find object by specified expression.
        /// </summary>
        /// <param name="predicate"></param>
        T Find(Expression<Func<T, bool>> predicate);

        /// <summary>
        /// Create a new object to database.
        /// </summary>
        /// <param name="t">Specified a new object to create.</param>
        T Create(T t);

        /// <summary>
        /// Delete the object from database.
        /// </summary>
        /// <param name="t">Specified a existing object to delete.</param>
        void Delete(T t);

        /// <summary>
        /// Delete objects from database by specified filter expression.
        /// </summary>
        /// <param name="predicate"></param>
        int Delete(Expression<Func<T, bool>> predicate);

        /// <summary>
        /// Update object changes and save to database.
        /// </summary>
        /// <param name="t">Specified the object to save.</param>
        int Update(T t);

        /// <summary>
        /// Get the total objects count.
        /// </summary>
        int Count { get; }
}
Now create a general repository for EF code first that implement the IRepository:

Hide   Shrink    Copy Code
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Entity;
using System.Collections.Generic;
using System.Data;

namespace Demo.DAL
{
    public class Repository<TObject> : IRepository<TObject>

        where TObject : class
    {
        protected DB Context;
        protected DB Context = null;
        private bool shareContext = false;

        public Repository()
        {
            Context = new DB();
        }

        public Repository(DB context)
        {
            Context = context;
            shareContext = true;
        }

        protected DbSet<TObject> DbSet
        {
            get
            {
                return Context.Set<TObject>();
            }
        }

        public void Dispose()
        {
            if (shareContext && (Context != null))
                Context.Dispose();
        }

        public virtual IQueryable<TObject> All()
        {
            return DbSet.AsQueryable();
        }

        public virtual IQueryable<TObject>
        Filter(Expression<Func<TObject, bool>> predicate)
        {
            return DbSet.Where(predicate).AsQueryable<TObject>();
        }

        public virtual IQueryable<TObject> Filter(Expression<Func<TObject, bool>> filter,
         out int total, int index = 0, int size = 50)
        {
            int skipCount = index * size;
            var _resetSet = filter != null ? DbSet.Where(filter).AsQueryable() :
                DbSet.AsQueryable();
            _resetSet = skipCount == 0 ? _resetSet.Take(size) :
                _resetSet.Skip(skipCount).Take(size);
            total = _resetSet.Count();
            return _resetSet.AsQueryable();
        }

        public bool Contains(Expression<Func<TObject, bool>> predicate)
        {
            return DbSet.Count(predicate) > 0;
        }

        public virtual TObject Find(params object[] keys)
        {
            return DbSet.Find(keys);
        }

        public virtual TObject Find(Expression<Func<TObject, bool>> predicate)
        {
            return DbSet.FirstOrDefault(predicate);
        }

        public virtual TObject Create(TObject TObject)
        {
            var newEntry = DbSet.Add(TObject);
            if (!shareContext)
                Context.SaveChanges();
            return newEntry;
        }

        public virtual int Count
        {
            get
            {
                return DbSet.Count();
            }
        }

        public virtual int Delete(TObject TObject)
        {
            DbSet.Remove(TObject);
            if (!shareContext)
                return Context.SaveChanges();
            return 0;
        }

        public virtual int Update(TObject TObject)
        {
            var entry = Context.Entry(TObject);
            DbSet.Attach(TObject);
            entry.State = EntityState.Modified;
            if (!shareContext)
                return Context.SaveChanges();
            return 0;
        }

        public virtual int Delete(Expression<Func<TObject, bool>> predicate)
        {
            var objects = Filter(predicate);
            foreach (var obj in objects)
                DbSet.Remove(obj);
            if (!shareContext)
                return Context.SaveChanges();
            return 0;
        }
    }
}
The Repository has two running modes: exclusive mode and shared mode.

Exclusive mode: The data context is generated by Repository, the data objects only use in the Repository‘s data context (Update, Delete).
Shared mode: In many scenarios, we maybe use over 1 repository at the same time. If repositories have their own data context, it may cause the data duplicate issue. So we need to pass the shared data context to repositories in transaction on construction.
To make this example more close to a reality project, I have defined two repository interfaces for Category and Product.

Hide   Shrink    Copy Code
public interface ICategoryRepository:IRepository<Category>
{
    string GetUrl();
}

public interface IProductRepository : IRepository<Product>
{
    string ResolvePicture();
}

public class CategoryRepository : Repository<Category>, ICategoryRepository
{
    public CategoryRepository(DB context) : base(context) { }

    public string GetUrl()
    {
        return "";
    }
}

public class ProductRepostiroy : Repository<Product>, IProductRepository
{
    public ProductRepostiroy(DB context) : base(context) { }

    public string ResolvePicture()
    {
        return "";
    }
}
In order to share data context, I use the UnitOfWork design pattern to maintain the data context and interoperability repository life time.

UnitOfWork Pattern
According to Martin Fowler, the Unit of Work pattern "maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems."

We could implement the UnitOfWork pattern in two ways:

Implement a GeneralRepository<T> class and embed in UnitOfWork. Then define the wapper methods and expose them to invoke the GeneralRepository<T>. But I don‘t think it‘s the best way, because the UnitOfWork will become very common and I couldn‘t use any subclass of Repository.
Inject the IRepository descendant interfaces into UnitOfWork.
Hide   Copy Code
public interface IUnitOfWork:IDisposable
{
    int SaveChanges();
}

public interface IDALContext : IUnitOfWork
{
    ICategoryRepository Categories { get; }
    IProductRepository Products { get; }
}
IUnitOfWork interface is very simple, it is only used to save changes, the IDALContext is use to define the IRepository descendant interfaces, construct and maintain the data context and Repository classes.

Hide   Shrink    Copy Code
public class DALContext : IDALContext
{
    private DB dbContext;
    private ICategoryRepository categories;
    private IProductRepository products;

    public DALContext()
    {
        dbContext = new DB();
    }

    public ICategoryRepository Categories
    {
        get
        {
            if (categories == null)
                categories = new CategoryRepository(dbContext);
            return categories;
        }
    }

    public IProductRepository Products
    {
        get
        {
            if (products == null)
                products = new ProductRepostiroy(dbContext);
            return products;
        }
    }

    public int SaveChanges()
    {
        throw new NotImplementedException();
    }

    public void Dispose()
    {
        if (categories != null)
            categories.Dispose();
        if (products != null)
            products.Dispose();
        if (dbContext != null)
            dbContext.Dispose();
        GC.SuppressFinalize(this);
    }
}
Service Layer
The DAL has been done! Constantly we need to create the SL (Service Layer). Now I create a new service interface to get categories, products or create new product.

Through the service layer, used to encapsulate data level of implementation details, call to the interface should be designed to be simple as possible. Service layer is usually called by the Controller in the MVC, Controller only needs to use the service interface, and the DI is responsible for construction, such a Controller and data layer of coupling is greatly reduced.

Hide   Shrink    Copy Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Practices.Unity;
using Demo.DAL;

namespace Demo.Services
{
    public interface ICatalogService
    {
        List<Category> GetCategories();
        List<Product> GetProducts();
        Product CreateProduct(string categoryName, string productName, int price);
    }

    public class CatalogService : ICatalogService, IDisposable
    {
        private IDALContext context;

        public CatalogService(IDALContext dal)
        {
            context = dal;
        }

        public List<Category> GetCategories()
        {
            return context.Categories.All().ToList();
        }

        public List<Product> GetProducts()
        {
            return context.Products.All().ToList();
        }

        public Product CreateProduct(string categoryName, string productName, int price)
        {
            var category = new Category() { Name = categoryName };
            var product = new Product()
            { Name=productName,Price=price,Category=category };
            context.Products.Create(product);
            context.SaveChanges();
            return product;
        }

        public void Dispose()
        {
            if (context != null)
                context.Dispose();
        }
    }
}
Controller

Pass the ICatalogService instance to controller by used construction injection.

Hide   Copy Code
public class HomeController : Controller
{
    private ICatalogService service;

    public HomeController(ICatalogService srv)
    {
        service = srv;
    }

    public ActionResult Index()
    {
        ViewData.Model = service.GetCategories();
        return View();
    }
}
OK, that is what I want. Finally, we need to construct and "Inject" instances to this object model.

Dependency Injection in MVC3

In MVC3, we could use the DependencyResolver.SetResolver(IDependencyResolver resolver) to register a Dependency Injection Container and use IDependencyResolver.GetService() method to locate our registered service instance that is a very amazing feature in this version. For more about DI in MVC3, you could read the "ASP.NET MVC 3 Service Location" below.

I have read that many developers like to create a new ControllerFactory to inject the controller instance. Actually that is not necessary! Because the MVC3 will call DependencyResolver.GetService to construe the Controller, so I only need to do one thing: Implement the IDependencyResolver.

Unity

We could found many popular DI framework in Google such as Castle Windsor, Structuremap,ObjectBuilder, Managed Extensibility Framework (MEF) and Microsoft Unity, I‘d like to use Microsoft Unity 2.0 because the MVC DI features in comes from it that means we could very easy to implement DI in our MVC applications.

References from MSDN:

Unity is a lightweight, extensible dependency injection container that supports interception, constructor injection, property injection, and method call injection. You can use Unity in a variety of different ways to help decouple the components of your applications, to maximize coherence in components, and to simplify design, implementation, testing, and administration of these applications.

Unity is a general-purpose container for use in any type of Microsoft® .NET Framework-based application. It provides all of the features commonly found in dependency injection mechanisms, including methods to register type mappings and object instances, resolve objects, manage object lifetimes, and inject dependent objects into the parameters of constructors and methods and as the value of properties of objects it resolves.
In addition,

Unity is extensible. You can write container extensions that change the behavior of the container, or add new capabilities. For example, the interception feature provided by Unity, which you can use to add policies to objects, is implemented as a container extension.

Implement IDependencyResolver
The next step is create a DependencyResolver for MVC:

Hide   Shrink    Copy Code
namespace Demo.Web
{
    public class UnityDependencyResolver : IDependencyResolver
    {
        readonly IUnityContainer _container;

        public UnityDependencyResolver(IUnityContainer container)
        {
            this._container = container;
        }

        public object GetService(Type serviceType)
        {
            try
            {
                return _container.Resolve(serviceType);
            }
            catch
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                return _container.ResolveAll(serviceType);
            }
            catch
            {
                return new List<object>();
            }
        }
    }
}
Open the Global.asax and register types and set DependencyResolver in Application_Start:

Hide   Copy Code
protected void Application_Start() {
            AreaRegistration.RegisterAllAreas();
            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);

            var container = new UnityContainer();
            container.RegisterType<ICatalogService, CatalogService>
                (new PerThreadLifetimeManager())
                          .RegisterType<IDALContext, DALContext>();
            DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
run-result.png
That‘s all, enjoy!

License
This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

Share
时间: 2024-10-20 20:49:57

经典三层架构的相关文章

MVC框架模式和Javaweb经典三层架构

一.MVC设计模式 1.MVC的概念 首先我们需要知道MVC模式并不是javaweb项目中独有的,MVC是一种软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model).视图(View)和控制器(Controller),即为MVC.它是一种软件设计的典范,最早为Trygve Reenskaug提出,为施乐帕罗奥多研究中心(Xerox PARC)的Smalltalk语言发明的一种软件设计模式. 2.MVC的详解 虽然MVC并不是Java当中独有的,但是现在几乎所有的B/S的架构都

用java观察者模式解耦经典三层架构

三层架构是一个很经典的架构模式,依据系统的职责不同.将系统分成了表现层,逻辑层和数据訪问层,而且配合数据实体进行传输数据,能够大大的封装性和复用性. 经典的三层架构图: 我们再深入到架构图内部.看看详细的类图,用简单的登陆举例吧: 这里通过LoginUI.调用了LoginLogService和LoginVerificationService两个类.通过类图能够看得出,U层和Service层(本文把BLL层称为了Service层)直接採用了直接调用的方式. 在面向对象的设计中.一直强调要面向接口编

JAVA学习笔记(五十三)- 经典三层架构实例

UserDAO接口 /* * UserDAO接口 */ public interface UserDAO { //插入用户 public void insert(User user); //删除用户 public void delete(int id); //更新用户 public void update(User user); //查询所有用户 public List<User> getAllUsers(); //根据用户名或密码查询用户 public boolean checkUser(U

分享基于EF+WCF的通用三层架构及解析

本项目结合EF 4.3及WCF实现了经典三层架构,各层面向接口,WCF实现SOA,Repository封装调用,在此基础上实现了WCFContext,动态服务调用及一个分页的实例. 1. 项目架构图: 2. 项目解决方案: 在传统的三层架构上增加了WcfService(服务端),WcfClientProxy(客户端服务调用),及WcfExtension(一些扩展) 3. Wcf Service的实现: 工厂实现了RemoteServiceFactory(用于远程调用)和RefServiceFac

从三层架构到IoC的蜕变

经典三层 经典三层架构是U层调B层,B层调D层.代码形式如下: D层: package com.tgb.spring.dao; public interface UserDao { public void addUser(String username,String password); } package com.tgb.spring.dao; public class UserDao4MySqlImpl implements UserDao { @Override public void a

从三层架构迈向领域驱动设计

本文读者基本要求:从事信息管理系统开发,略懂GOF设计模式及SOLID设计原则,对三层面向过程机械编码厌倦,并且不知道出路在何方,如果还掌握代码坏味和重构手法,那是极好的. 1. 三层架构 理论介绍-->实际经验-->总结反思 1.1 简单介绍三层架构 严格分层架构模式的特点是上层只能访问相邻的下层,其他层次间的调用都不允许.三层架构就是一种严格分层模式,它把职责划分为界面展示.业务逻辑.数据访问三层,还有一个业务实体,前面三层都要依赖它,所以它并不构成一个层.结构如图1. 三层架构的特点是一

浅谈三层架构

三层架构并不是MVC,MVC是一个很早就有的经典的程序设计模式,M-V-C分为三层,M(Model)-V(View)-C(Control).而web开发中的三层架构是指:数据访问层(DAL-DatabaseAccessLayer),业务逻辑层(BLL-BusinessLoginLayer),以及用户界面层(UI-UserInterface,实际就是网页后台的具体调用BLL层).这个是基本概念.曾经我以为三层架构就是在AppCode中,分为三个大类与若干小类,各司其职.在经过一番洗礼后,才发觉多么

从零开始编写自己的C#框架(5)——三层架构介绍

原文:从零开始编写自己的C#框架(5)--三层架构介绍 三层架构对于开发人员来说,已经是司空见惯了,除了大型与超小型项目外,大多都是这种架构来进行开发. 在这里为初学者们简单介绍一下三层架构: (下面内容摘自<趣味理解:三层架构与养猪-<.NET深入体验与实战精要>>,这是以前看到的关于三层架构介绍,觉得挺经典的,大家有时间的话认真看看) 对比以上两图,我们可以看出: 1)数据库好比猪圈 ,所有的猪有序地按区域或编号,存放在不同的猪栏里. 2)DAL 好比是屠宰场 ,把猪从猪圈取出

浅谈.NET,C#三层架构(自己总结)

 三层架构 常见架构: 三层(经典) MVC MVVM MVP 开发中常见的23种设计模式: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 设计模式的六大原则 1.开闭原则 2.里氏代换原则 3.依赖倒转原则 4.接