MVC項目中使用Entity framework和Ninject 實現松耦合架構

關於Entity framework和Ninject是什麼,此處省略一萬個字。

這裏記錄下傳統代碼架構和使用Ioc工具後的松耦合架構:


以一個簡單的示例說明:

新建一個空解決方案,添加MVC項目(這裏用的是MVC4),爲了方便演示,直接選擇Internet應用程序:



再添加業務邏輯層(BLL)和數據訪問層(DAL)兩個類庫項目(也可以放在MVC的model下,因爲MVC的model層就類似三層架構中的BLL+DAL+Model) 如下:


然後在數據庫裏添加一個persons表,隨便手動輸入幾條測試數據:


在DAL項目下新建一個文件夾,加入一個實體數據庫模型edmx文件,把上面的表加進來(這裏爲了方便演示,就不用code first了):


修改HomeController中Index方法對應的視圖文件爲:

@model IEnumerable<DAL.EF_Model.PersonInfo>

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Age)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Sex)
        </th>
        <th></th>
    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Age)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Sex)
        </td>

    </tr>
}

</table>

然後,要做一個簡單的功能,就是查出滿足條件的人員名單,一般有如下幾種做法:

一.傳統做法:

在BLL中加入代碼:

    public class PersonBL<T> where T:class,new()
    {
        public IEnumerable<T> GetPersons(Expression<Func<T,bool>> perdicate)
        {
            return new PersonDA<T>().GetPersons(perdicate);
        }
    }
在DAL中加入代碼:
    public class PersonDA<T> where T:class,new()
    {
        private MyDbEntities dbContext = new MyDbEntities();
        public IEnumerable<T> GetPersons(System.Linq.Expressions.Expression<Func<T, bool>> perdicate)
        {
            return dbContext.Set<T>().Where(perdicate).Select(p => p).ToList();
        }
    }
最後在HomeController修改Index方法爲:

        public ActionResult Index()
        {
            IEnumerable<PersonInfo> table = new PersonBL<PersonInfo>().GetPersons(person => person.Sex == "男");
            return View(table);
        }
最後運行結果:



這種做法實現了controller層和BL,DA層的解耦,但是並沒有面向接口,也就是說所有的數據庫操作都在DA層進行。關於面向接口編程的好處,直接百度,此處省略一萬字。


二.第二種面向接口做法:

在解決方案下新增一個接口類庫項目,並新增一個公共接口文件IRepository(名字隨便取,最好以i開頭表示interface):


接口代碼爲(這裏只實現了部分增刪改查,針對不同情況可自行補充):

public interface IRepository<T> where T : class,new()
    {
        //增C
        void Create(T entity);
        void Create(IEnumerable<T> entities);

        //查R
        T Get(Expression<Func<T, bool>> predicate);
        IEnumerable<T> Fetch(Expression<Func<T, bool>> predicate);

        //改U
        void Update(T entity);
        void Update(IEnumerable<T> entities);

        //刪D
        void Delete(T entity);
        void Delete(IEnumerable<T> entities);
        void Delete(Expression<Func<T, bool>> predicate);
    }
然後新增一個繼承該接口的person接口:

    public interface IPerson<T>:IRepository<T> where T:class,new()
    {
        //TODO
        //此處添加其它接口方法
    }

最後在BLL下新增一個繼承IPerson接口的實現類PersonManager1:

    public class PersonManager1<T> : IPerson<T> where T : class,new()
    {
        private MyDbEntities dbContext = new MyDbEntities();

        public PersonManager1()
        {
            //dbContext.Database.Connection.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["mycon"].ConnectionString;
        }

        private DbSet<T> DbTable
        {
            get 
            {
                return dbContext.Set<T>();
            }        
        }

        #region IRepository<T> 成員

        #region 常規CRUD
        public void Create(T entity)
        {
            DbTable.Add(entity);
            dbContext.SaveChanges();
        }
        public void Create(IEnumerable<T> entities)
        {
            if (entities.ToList().Count > 0)
            {
                DbTable.AddRange(entities);
                dbContext.SaveChanges();
            }
        }

        public T Get(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
        {
            return Fetch(predicate).FirstOrDefault();
        }

        public IEnumerable<T> Fetch(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
        {
            return DbTable.Where(predicate);
        }

        public void Update(T entity)
        {
            DbTable.Attach(entity);
            dbContext.Entry(entity).State = EntityState.Modified;
            dbContext.SaveChanges();

        }
        public void Update(IEnumerable<T> entities)
        {
            foreach (var item in entities)
            {
                DbTable.Attach(item);
                dbContext.Entry(item).State = EntityState.Modified;
            }
            dbContext.SaveChanges();

        }

        public void Delete(T entity)
        {
            DbTable.Attach(entity);
            dbContext.Entry(entity).State = EntityState.Deleted;
            dbContext.SaveChanges();
        }
        public void Delete(IEnumerable<T> entities)
        {
            foreach (var item in entities)
            {
                DbTable.Attach(item);
                dbContext.Entry(item).State = EntityState.Deleted;
            }
            dbContext.SaveChanges();

        }
        public void Delete(Expression<Func<T, bool>> predicate)
        {
            var delEntities = Fetch(predicate).ToList();//讓其立即查詢
            foreach (var item in delEntities)
            {
                Delete(item);
            }
        }
        #endregion

        #endregion
    }
最後將HomerController的Index方法修爲:

        public ActionResult Index()
        {
            //IEnumerable<Persons> table = new PersonBL<Persons>().GetPersons(person => person.Sex == "男");

            IPerson<Persons> persons = new PersonManager1<Persons>();
            IEnumerable<Persons> table=persons.Fetch(person => person.Sex == "女");
            return View(table);
        }
查詢結果:



這種做法實現了面向接口方式,但是帶來一個問題就是和Controller層耦合了,也就是說如果以後我修改了PersonManager1類,那麼相應的Controller層也要對應修改。爲了實現和Controller層解耦,就要用到DI,也就是Ioc工具,這類工具有很多,如AutoFac,Spring.Net,這裏使用一個輕量級的名叫:Ninject

這個工具直接在NuGet下可以下載:


安裝到當前項目下,新建一個Factory項目,並增加一個NInjectFactory類:

Ninject可以從構造函數注入或者直接綁定:

1)構造函數注入:

    //構造函數注入(不支持Get取得綁定的類型,不推薦)
    public class NinjectControlFactory : DefaultControllerFactory
    {
        private IKernel ninectKernel;
        public NinjectControlFactory()
        {
            ninectKernel = new StandardKernel();
            AddBindings();
        }
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return controllerType == null ? null : (IController)ninectKernel.Get(controllerType);
        }
        private void AddBindings()
        {
            //ninectKernel.Bind<IProduct>().To<ProductNameA>(); //不帶泛型約束注入
            ninectKernel.Bind(typeof(IPerson<>)).To(typeof(PersonManager1<>));////帶泛型約束注入
        }
    }
還需要註冊綁定:
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            //構造函數注入(不推薦)
            ControllerBuilder.Current.SetControllerFactory(new NinjectControlFactory());
        }

修改Controller中的方法:

   private IPerson<Persons> _iPerson;
        public HomeController(IPerson<Persons> iPerson)
        {
            _iPerson = iPerson;
        }

        public ActionResult Index()
        {
            //IEnumerable<Persons> table = new PersonBL<Persons>().GetPersons(person => person.Sex == "男");

            //IPerson<Persons> persons = new PersonManager1<Persons>();
            //IEnumerable<Persons> table=persons.Fetch(person => person.Sex == "女");

            IEnumerable<Persons> table = _iPerson.Fetch(person=>person.Age>=40);
            return View(table);
        }

最後直接運行即可:


但是這種方式我不太喜歡,破壞構造函數,而且不能動態Get到具體類型,不是很好,下面使用另一種方式。

2)重寫NinjectModule的Load方式:

    //屬性、字段、參數式注入(支持Get取得綁定的類型)
    public class MyBindModule : NinjectModule
    {
        public override void Load()
        {
            Bind(typeof(IPerson<>)).To(typeof(PersonManager1<>));//帶泛型約束注入                                            
        }
    }
然後在controller中:

        private IPerson<Persons> _iPerson;
        public HomeController()
        {
            IKernel kernal = new StandardKernel(new MyBindModule());
            _iPerson = kernal.Get<IPerson<Persons>>();
        }

        public ActionResult Index()
        {
            //IEnumerable<Persons> table = new PersonBL<Persons>().GetPersons(person => person.Sex == "男");

            //IPerson<Persons> persons = new PersonManager1<Persons>();
            //IEnumerable<Persons> table=persons.Fetch(person => person.Sex == "女");

            IEnumerable<Persons> table = _iPerson.Fetch(person=>person.Age<=30);
            return View(table);
        }
結果:

這樣就實現了Controller層和具體業務層(PersonManager1)的解耦。如果下次要修改該業務,我只要新增IPerson接口的實現類,如:PersonManager2,PersonManager3(面向對象設計的開閉原則:對於擴展開放,對於修改封閉),然後在Ninject綁定的地方修改成:
Bind(typeof(IPerson<>)).To(typeof(PersonManager2<>));
即可,不需要求修改Controller層的任何代碼。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章