《Pro ASP.NET MVC 3 Framework》学习笔记之十二【示例项目SportsStore及MyBatis.NET的使用】

距上次的笔记已经有2个多星期了,之所以没保持前面的笔记频率,是因为书中后面的例子是基于EF实体框架的。我有点不愿意使用EF框架,不是它不好,而是我打算在操作完书中讲的例子后能够试着将这个MVC3的项目移植到mono里面,当然数据库也换了,我这里会用MySQL。最终我打算将iBatisnet学着用一下,我还是比较喜欢自己写SQL,对我自己而言比较容易掌控。前面学过NHibernate感觉比较复杂。而且自己写SQL能够不断加强自己的SQL基础,也不至于用框架用多了,基本的SQL都不熟悉了。呵呵!

接下来的三章会完成一个项目SportsStore.书里面用到了EF框架,我会用IBastisnet代替EF来实现数据访问,其实本质还是直接写SQL。IBastisnet并不是O/M框架。首先我们创建整个项目的一个框架结构:SportsStore.Domain,SportsStore.UnitTests,SportsStore.WebUI。需要另外的准备几个DLL文件:IBatisnet.Common.dll,IBatisnet.DataMapper.dll,Moq.dll,MySql.Data.dll,Ninject.dll。

几个项目之间的引用关系如下图所示:

我们会使用Ninject来创建我们MVC程序的Controllers,并通过它来处理DI(依赖注入)。我们在SportsStore.WebUI里面创建一个文件夹Infrastructure。然后创建一个类NinjectControllerFactory,代码如下所示:

    public class NinjectControllerFactory : DefaultControllerFactory
    {
        private IKernel ninjectKernel;
        public NinjectControllerFactory()
        {
            ninjectKernel = new StandardKernel();
            AddBindings();
        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType);
        }

        private void AddBindings()
        {
            //Mock IProductRepository接口的实现
            //Mock<IProductsRepository> mock = new Mock<IProductsRepository>();
            //mock.Setup(m => m.Products).Returns(new List<Product> { 
            // new Product { Name = "Football", Price = 25 }, 
            // new Product { Name = "Surf board", Price = 179 }, 
            // new Product { Name = "Running shoes", Price = 95 } 
            //});
            //ninjectKernel.Bind<IProductsRepository>().ToConstant(mock.Object);
            //ninjectKernel.Bind<IProductsRepository>().To<ProductsRepository>();

        }
    }

我们还没有添加任何Ninject的绑定,但是我在这里已经做好了准备。接下来我们需要告诉MVC需要创建的Controller对象。需要在Global.asax.cs里面添加如下代码:

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default", // 路由名称
                "{controller}/{action}/{id}", // 带有参数的 URL
                new { controller = "Product", action = "List", id = UrlParameter.Optional } // 参数默认值
            );

        }

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            RegisterGlobalFilters(GlobalFilters.Filters);
            RegisterRoutes(RouteTable.Routes);

            ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
        }

接下来我们开始添加Domain Model(领域模型),几乎整个MVC程序都是围着它转,从它开始我们的程序再好不过了。

我们创建的是一个电子商务的网站,Product是必须的。先添加一个Product.cs。关于SportsStore.Domain的机构如下图所示,里面包含了iBatisnet的部分,所以你只要先建立对应的文件夹放着就行,代码后面来加。可能这里这么放置不合理,请路过的大牛能够给我指导下。

    public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public string Category { get; set; }
        public decimal? Price { get; set; }
    }

添加一个接口IProductsRepository.cs

    public interface IProductsRepository
    {
        IList<Product> Products { get; }
    }

接着添加一个Controller:

    public class ProductController : Controller
    {
        //public int PageSize = 4;
        private IProductsRepository repository;
        public ProductController(IProductsRepository productRepository)
        {
            repository = productRepository;
        }

        public ViewResult List(int page = 1)
        {
            //return View(repository.Products.OrderBy(p => p.ProductID).Skip((page - 1) * PageSize).Take(PageSize));
            return View(repository.Products);
        }
    }

这些操作都是前面介绍过的,代码也很简单。如果你第一次来到这里,建议从按顺序看过来。
接着添加一个强类型的View–List.cshtml,如下所示:

@model IEnumerable<SportsStore.Domain.Entities.Product>
@{
    ViewBag.Title = "Products";
}
<h2>
    Product List</h2>
@foreach (var p in Model)
{
    <div class="item">
        <h3>@p.Name</h3>
        @p.Description
        <h4>@p.Price.ToString()</h4>
    </div> 
}

接着我们在MySQL里面创建数据库SportsStore,并创建一张Products表。如下所示:


接下来跟书上的内容就没有什么关系了,下面主要介绍使用iBatisnet里的DataMapper来做数据访问。虽然IBastisnet比较容易上手,但我还是花不少时间在上面。今天才刚刚能够使用,如果你对IBastisnet比较熟悉,希望能多多指导。如果你第一次听说iBatisnet,那我下面也有一些简单的介绍,希望能够对你有点帮助。

关于iBatisnet
iBatisnet包含两个主要的部分:DataAccess和DataMapper。这里只是用DataMapper(也是IBastisnet的亮点),iBatisnet DataMapper框架让我们在.net里面很容易的使用数据库。它让我们可以将sql语句或存储过程写到XML文件里面,DataMapper负责解析这些XML,并执行对应的SQL语句。当我们更改了XML里面的sql语句时,并不需要重新编译程序。当然我只是说了IBastisnet提供的主要的功能,其实还有一些非常方便的功能,比如事务,sql语句的重用等等。

具体的你可以猛击这里下载相应的文档来学习。下面我直接介绍项目里面如何通过iBatisnet DataMapper框架实现数据访问,具体点就是这里读取Products表里面的数据。

1.配置三个重要的文件,这是DataMapper必须的。分别是providers.config,SqlMap.config,Product.xml(这是对应我们这里的Product实体)
我使用的MySQL数据库,providers.config配置如下所示:

<?xml version="1.0" encoding="utf-8"?>
<providers
  xmlns="http://ibatis.apache.org/providers"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <clear/>
  <provider
      name="MySql"
      description="MySQL"
      enabled="true"
      assemblyName="MySql.Data, Version=6.4.4.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" 
      connectionClass="MySql.Data.MySqlClient.MySqlConnection"
      commandClass="MySql.Data.MySqlClient.MySqlCommand"
      parameterClass="MySql.Data.MySqlClient.MySqlParameter"
      parameterDbTypeClass="MySql.Data.MySqlClient.MySqlDbType"
      parameterDbTypeProperty="MySqlDbType"
      dataAdapterClass="MySql.Data.MySqlClient.MySqlDataAdapter"
      commandBuilderClass="MySql.Data.MySqlClient.MySqlCommandBuilder"
      usePositionalParameters="false"
      useParameterPrefixInSql="true"
      useParameterPrefixInParameter="true"
      parameterPrefix="?"/>
</providers>

SqlMap.config配置如下所示:

<?xml version="1.0" encoding="utf-8" ?>
<sqlMapConfig
  xmlns="http://ibatis.apache.org/dataMapper"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <settings>
    <setting useStatementNamespaces="false"/>
    <setting cacheModelsEnabled="true"/>
    <setting validateSqlMap="true"/>
  </settings>

  <providers resource="../SportsStore.Domain/providers.config"/>

  <!-- Database connection information -->
  <database>
    <provider name="MySql"/>
    <dataSource name="conn" connectionString="server=localhost;uid=root;pwd=不能告诉你;database=sportsstore;"/>
  </database>

  <sqlMaps>
    <sqlMap resource="../SportsStore.Domain/Maps/Product.xml"/>
  </sqlMaps>

</sqlMapConfig>

Product.xml如下所示:

<?xml version="1.0" encoding="utf-8" ?>
<sqlMap namespace="Product"
        xmlns="http://ibatis.apache.org/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
  <alias>
    <typeAlias alias="Product" type="SportsStore.Domain.Entities.Product,SportsStore.Domain"></typeAlias>
  </alias>

  <resultMaps>
    <resultMap id="Product-Result" class="Product">
      <result property="ProductID" column="ProductID"/>
      <result property="Name" column="Name"/>
      <result property="Description" column="Description"/>
      <result property="Category" column="Category"/>
      <result property="Price" column="Price" nullValue="0.00"/>//如果Price为null,查询出来是0.00.你可能已经注意到了在我们定义的Product.cs里面
      并没有将Price定义为Decimal?类型。DataMapper会自动处理,方便吧,呵呵!
    </resultMap>
  </resultMaps>

  <statements>
    <select id="Product-Select" parameterClass="Product" resultMap="Product-Result">
      select * from products
    </select>
    <!--后面的可以先不用管,现在只用到了查询-->
    <insert id="Product-Insert" parameterClass="Product">
      insert into products(Name,Description,Category,Price) values
      (
      #Name#,#Description#,#Category#,#Price#
      )
      <selectKey resultClass="int" property="ProductID" type="post">
        select @@IDENTITY
      </selectKey>
    </insert>

    <update id="Product-Update" parameterClass="Product">
      update Product set Name=##,Description=##,Category=##,Price=##
      where
      ProductID=#ProductID#
    </update>

    <delete id="Product-Delete" parameterClass="Product">
      delete from products where ProductID=#ProductID#
    </delete>
  </statements>

</sqlMap>

我简单介绍下这个几个配置文件的关系,SqlMap.config是总的配置文件,引用了另外两个配置文件。providers.config配置了数据库访问的信息,Product.xml就是用来与数据库里面的列对应的,并且所有的sql语句也写在这个里面。创建DataMapper对象时只要传入SqlMapper.config这个配置文件就行了,因为DataMapper会根据它里面的信息去寻找另外两个配置文件。

配置好了以后就可以使用DataMapper了,前面我们定义了一个IProductsRepository接口,里面有一个Products属性,现在就是实现这个接口的时候了。在SportsStore.Domain里面添加一个新文件夹Concrete,接着添加一个实现类ProductsRepository,代码如下所示:

    public class ProductsRepository : IProductsRepository
    {

        public IList<Product> Products
        {
            get
            {
                ISqlMapper mapper = MapperHelper.Instance();
                return mapper.QueryForList<Product>("Product-Select", ""); //因为查询所有的,所有第二个参数为“”
            }
        }

    }

MapperHelper是我定义的一个辅助的类,同样在SportsStore.Domain里面添加一个Common的文件夹,并添加MapperHelper类。代码如下所示:

    public class MapperHelper
    {

        public static volatile ISqlMapper mapper = null;
        protected static void Configure(object obj)
        {
            mapper = null;
        }
        protected static void InitMapper()
        {
            ConfigureHandler handler = new ConfigureHandler(Configure);
            DomSqlMapBuilder builder = new DomSqlMapBuilder();
            mapper = builder.ConfigureAndWatch(@"../SportsStore.Domain/SqlMap.config", handler);
        }
        public static ISqlMapper Instance()
        {
            //保证只有一个实例
            if (mapper == null)
            {
                lock (typeof(SqlMapper))
                {
                    if (mapper == null)
                    {
                        InitMapper();
                    }
                }
            }
            return mapper;
        }
        public static ISqlMapper Get()
        {
            return Instance();
        }
    }

好了辛苦了这么久,看下运行结果吧:

好了,今天的笔记就到这里,希望路过的大牛多多指导帮助,谢谢!

晚安!

作者:张雪飞
出处:https://zhangxuefei.site/p/265
版权说明:欢迎转载,但必须注明出处,并在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

发表评论

电子邮件地址不会被公开。 必填项已用*标注