• NBearV3 Step by Step教程——IoC篇


    版本

    1.2 [2006-11-12]

    简介

    本教程演示如何基于NBearV3IoC模块开发一个Web应用程序的基本过程。本教程同时演示使用NBear.Tools.DbToEntityDesign.exe工具从现有的数据库生成设计实体的过程。

    注:在阅读本文之前,建议读者先阅读《NBearV3 Step by Step教程——ORM》以掌握NBearV3中有关ORM的基本知识。

    目标

    通过本教程,读者应能够掌握使用NBearV3IoC模块的基本过程,以及使用NBear.Tools.DbToEntityDesign.exe工具,对已有数据库结构的项目,使用NBearV3ORM组件进行数据持久化的过程。

    代码

    本教程演示创建的所有工程和代码,包含于可以从sf.net下载的NBearV3最新源码zip包中的tutorials\IoC_Tutorial目录中。因此,在使用本教程的过程中如有任何疑问,可以直接参考这些代码。

    时间

    <45分钟。

    正文

    Step 1 下载NBearV3最新版本

    1.1 访问http://sf.net/projects/nbear,下载NBearV3的最新版本到本地目录。

    1.2 将下载的zip文件解压至C:\,您将看到,加压后的NBearV3目录中包括:distdoccasessrc等目录。其中,在本教程中将会使用的是dist目录,该目录下包含所有release编译版本的dllexe

    Step 2 创建应用程序解决方案

    2.1 打开VS2005开发环境,新建一个空的解决方案sln

    2.2 sln中添加两个新建的C#类库工程,两个类库工程的名称分别为EntityDesignsEntities,删除IDE自动创建的Class1.cs文件。

    2.3 sln中再添加两个新建的C#类库工程,两个类库工程的名称分别为ServiceInterfacesServiceImpls,删除IDE自动创建的Class1.cs文件。

    2.4 sln中新建一个名叫websiteASP.NET Web应用程序,为website添加一个Web.config文件。

    Step 3 设计实体、关系及元数据

    3.1 运行dist\ NBear.Tools.DbToEntityDesign.exe,在Connection String文本框中输入下面的连接子串:

    Server=(local);Database=Northwind;Uid=sa;Pwd=sa

    我们将从SQL Server 2000自带的演示数据库Northwind,为我们需要的某些表和视图生成设计实体。

    点击Connect按钮,连接上数据库后,从左边的TablesViews列表中分别选择下面这些表或视图(因为这里主要是演示,我们只选择和CategoryProduct相关的几个表和视图):Categories Products Products by Category。点击Generate Entities Design按钮,在代码生成文本框内的任意位置右键单击鼠标,并选择Copy to ClipBoard,这将能把所有的代码复制到剪贴板。

    3.2 EntityDesigns工程添加到dist目录下的NBear.Common.Design.dll的引用,因为每一个设计实体接口必须继承自NBear.Common.Design.Entity这个接口。在EntitiyDesigns工程中新建一个代码文件EntityDesigns.cs,添加using Systemusing NBear.Common.Design设置namespaceEntityDesigns。并将刚才从DbToEntityDesign复制的代码粘贴至该文件中。

    此时,EntityDesigns.cs文件中的内容应该象下面这样:

    using System;
    using NBear.Common.Design;

    namespace EntityDesigns
    {
        
    public interface Categories : Entity
        
    {
            [PrimaryKey]
            
    int CategoryID get; }
            [SqlType(
    "nvarchar(15)")]
            
    string CategoryName getset; }
            [SqlType(
    "ntext")]
            
    string Description getset; }
            
    byte[] Picture getset; }
        }


        
    //
    }

    您会注意到,生成的代码中,数据库中的表或视图名称对应设计实体的名称,字段名称对应设计实体的属性名称,如果名称中包含空格,空格会被转换为_nbsp_。同时,是主键的标字段对应的属性,会自动包含PrimaryKey这个Attribute,而所有的文本类型的字段,则会自动包含从数据库中继承的数据类型和长度。另外,所有从视图生成的实体接口会包含ReadOnly这个Attribute,代表,该实体是一个只能用来从数据库取数据,不能用来保存数据到数据库的实体。

    3.3 下面,我们可以对这些生成的代码做一下改造,让我们看着更舒服。比如,用_nbsp_代表空格多少影响视觉审美,我们可以给设计实体添加MappingName这个Attribute,来修改实体接口名称,但是,保证实体还是对应数据库中的这个表或视图。例如,对于Products_nbsp_by_nbsp_Category视图,我们可以将它修改为下面的样子:

        [ReadOnly]
        [MappingName(
    "Products by Category")]
        
    public interface ProductsByCategory : Entity
        
    {
            [SqlType(
    "nvarchar(15)")]
            
    string CategoryName get; }
            [SqlType(
    "nvarchar(40)")]
            
    string ProductName get; }
            [SqlType(
    "nvarchar(20)")]
            
    string QuantityPerUnit get; }
            
    short UnitsInStock get; }
            
    bool Discontinued get; }
        }

    我们可能同样不喜欢实体名称是复数的英文单词,所以,我们也可以象下面这样把Categories实体的名称改为Category

        [MappingName("Categories")]
        
    public interface Category : Entity
        
    {
            [PrimaryKey]
            
    int CategoryID get; }
            [SqlType(
    "nvarchar(15)")]
            
    string CategoryName getset; }
            [SqlType(
    "ntext")]
            
    string Description getset; }
            
    byte[] Picture getset; }
        }

    我们可以用同样的方法,修改所有的实体名称,和属性名称。属性名称同样支持MappingName这个Attribute。我们将所有的实体名称的复数改为单数,并去掉_nbsp_。现在,所有的设计实体应该象下面这个样子:

    using System;
    using NBear.Common.Design;

    namespace EntityDesigns
    {
        [MappingName(
    "Categories")]
        
    public interface Category : Entity
        
    {
            [PrimaryKey]
            
    int CategoryID get; }
            [SqlType(
    "nvarchar(15)")]
            
    string CategoryName getset; }
            [SqlType(
    "ntext")]
            
    string Description getset; }
            
    byte[] Picture getset; }

            [Query(Where
    ="{CategoryID} = @CategoryID", OrderBy="{ProductName}", LazyLoad=true)]
            [Contained]
            Product[] Products
            
    {
                
    get;
                
    set;
            }

        }


        [MappingName(
    "Products")]
        
    public interface Product : Entity
        
    {
            [PrimaryKey]
            
    int ProductID get; }
            [SqlType(
    "nvarchar(40)")]
            
    string ProductName getset; }
            
    int SupplierID getset; }
            
    int CategoryID getset; }
            [SqlType(
    "nvarchar(20)")]
            
    string QuantityPerUnit getset; }
            
    decimal UnitPrice getset; }
            
    short UnitsInStock getset; }
            
    short UnitsOnOrder getset; }
            
    short ReorderLevel getset; }
            
    bool Discontinued getset; }

            [Query(Where
    ="{CategoryID} = @CategoryID", LazyLoad=false)]
            Category Category
            
    {
                
    get;
                
    set;
            }

        }


        [ReadOnly]
        [MappingName(
    "Products by Category")]
        
    public interface ProductsByCategory : Entity
        
    {
            [SqlType(
    "nvarchar(15)")]
            
    string CategoryName get; }
            [SqlType(
    "nvarchar(40)")]
            
    string ProductName get; }
            [SqlType(
    "nvarchar(20)")]
            
    string QuantityPerUnit get; }
            
    short UnitsInStock get; }
            
    bool Discontinued get; }
        }

    }

    3.4 实体和属性名称我们改造完了,下面还可以给设计实体添加一点关联。我们可以注意到,CategoryProduct是一个明显的1对多关联。因此,我们可以像下面这样,为Category实体添加一个Products属性,1对多关联到Product表。

        [MappingName("Categories")]
        
    public interface Category : Entity
        
    {
            [PrimaryKey]
            
    int CategoryID get; }
            [SqlType(
    "nvarchar(15)")]
            
    string CategoryName getset; }
            [SqlType(
    "ntext")]
            
    string Description getset; }
            
    byte[] Picture getset; }

            [FkQuery(
    "Category", OrderBy="{ProductName}", Contained=true, LazyLoad=true)]
            Product[] Products
            
    {
                
    get
    ;
                
    set
    ;
            }

        }

    如果您看过之前的ORM教程,您应该能理解加粗的代码的意思。它表示Category实体的CategoryID属性和Product实体的CategoryID关联,Products属性的ProductProductName正序排序,同时,该属性延迟载入(即到第一次访问该属性才载入)。Contained同时代表Products属性在Category保存或删除时,会自动级联保存或删除。

    3.5 我们同时也可以给Product添加到Category的引用,因为,在查看一个Product信息时,查看相关的Category是非常常见的。注意,此时我们可以删掉Product中原来的CategoryID属性,将它合并到Category属性中:

        [MappingName("Products")]
        
    public interface Product : Entity
        
    {
            [PrimaryKey]
            
    int ProductID get; }
            [SqlType(
    "nvarchar(40)")]
            
    string ProductName getset; }
            
    int SupplierID getset; }
            [SqlType(
    "nvarchar(20)")]
            
    string QuantityPerUnit getset; }
            
    decimal UnitPrice getset; }
            
    short UnitsInStock getset; }
            
    short UnitsOnOrder getset; }
            
    short ReorderLevel getset; }
            
    bool Discontinued getset; }

            [FkReverseQuery(LazyLoad 
    = true)]
            [MappingName(
    "CategoryID"
    )]
            Category Category
            
    {
                
    get
    ;
                
    set
    ;
            }

        }

    注意,我们没有添加ContainedAttribute,因为,我们并不希望一个Category跟随他的一个Product的更新级联更新。同时,我们将Category属性设置为非延迟载入(即实例化Product的同时就载入Category属性的数据)。

    这里要特别引起注意的是,当两个实体互相关联,或者,多个实体循环关联时,千万注意不要同时将互相关联的属性全都设为LazyLoad=fasle,否则将可能导致循环载入,造成程序死循环。在NBearV3的后续有版本将会引入缓存机制来解决这个问题,但是在这之前,请大家自己注意小心避免。

    Step 4 从实体设计代码生成实体代码、实体配置文件

    4.1 至此,实体的设计就完毕了。编译EntityDesigns工程。下面我们将从设计实体生成实际的实体代码和配置文件。注意,这里和之前的ORM教程不同的是,我们不生成数据库创建脚本,而直接使用一个已经存在的数据库Northwind

    4.2 运行dist目录中的NBear.Tools.EntityDesignToEntity.exe工具,载入EntityDesigns工程编译生成的EntityDesigns.dll

    4.3 点击Generate Entities按钮,将生成的代码保存到Entities工程中的一个名叫Entities.cs的新代码文件。并为Entities工程添加到dist\NBear.Common.Common.dll的引用。

    4.4 点击Generate Configuration按钮,将生成的代码保存到website工程下的名为EntityConfig.xml的新文件中。

    Step 5 使用实体及NBear.Data.Gateway访问数据库

    5.1 现在我们就可以使用前面生成的实体了。我们先要让website工程引用Entities工程,以及dist/NBear.Data.dll

    5.2 我们还需要设置websiteWeb.config文件,添加一个entityConfig section以包含EntityConfig.xml这个实体配置文件,并设置数据库连接字串。下面是设置完的Web.config,注意,粗体的部分都是我们添加的代码(注意修改数据库登录密码。):

    <?xml version="1.0"?>
    <configuration>
     
    <configSections>
        
    <section name="entityConfig" type="NBear.Common.EntityConfigurationSection, NBear.Common" />
     
    </configSections>
     
    <entityConfig>
        
    <includes>
          
    <add key="Sample Entity Config" value="~/EntityConfig.xml" />
        
    </includes>
     
    </entityConfig>
     
    <appSettings/>
     
    <connectionStrings>
        
    <add name=" Northwind" connectionString="Server=(local);Database=Northwind;Uid=sa;Pwd=sa" providerName="NBear.Data.SqlServer.SqlDbProvider"/>
     
    </connectionStrings>
     
    <system.web>
            
    <compilation debug="false" />
            
    <authentication mode="Windows" />
     
    </system.web>
    </configuration>

    5.3 好了,到目前为止,实体设置和配置完毕了。下面我们将开始讨论IoC模块的使用。

    Step 6 定义Service接口和Service实现

    6.1 下面我们开始定义一个基于NBear.IoCService。我们先要为ServiceInterfaces工程添加到dist\NBear.Common.dlldist\NBear.IoC.dll的引用。一个Service由一个接口定义。我们这个Service的功能很简单,就是我们想获得一些需要的CategoryProduct。所以,我们还需要为ServiceInterfaces工程添加到Entities工程的引用。在ServiceInterfaces工程中定义接口ICategoryServiceIProductService如下:

    using System;
    using NBear.IoC.Service;
    using Entities;

    namespace ServiceInterfaces
    {
        
    public interface ICategoryService : IService
        
    {
            Category[] GetAllCategories();
            Category GetCategoryByID(
    int categoryID);
        }

    }

    using System;
    using NBear.IoC.Service;
    using Entities;

    namespace ServiceInterfaces
    {
        
    public interface IProductService
        
    {
            Product[] GetAllProducts();
            Product GetProductByID(
    int productID);
        }

    }

    注意,基于NBear.IoCService,必须从NBear.IoC.Service.IServiceInterface这个接口继承

    6.2 定义完Service接口,我们还需要实现它。在ServiceImpls工程中,添加到EntitiesServiceInterfaces和到dist\NBear.Common.dlldist\NBear.Data.dlldist\NBear.IoC.dll的引用,分别实现这两个接口如下:

    using System;
    using NBear.Common;
    using NBear.Data;
    using Entities;
    using ServiceInterfaces;

    namespace ServiceImpls
    {
        
    public class CategoryService : ICategoryService
        
    {
            
    ICategoryService Members
        }

    }

    using System;
    using NBear.Common;
    using NBear.Data;
    using Entities;
    using ServiceInterfaces;

    namespace ServiceImpls
    {
        
    public class ProductService : IProductService
        
    {
            
    IProductService Members
        }

    }

    Step 7 配置Service,使用ServiceFactory,调用Service

    7.1 编译ServiceImpls。我们就可以准备在website中使用Service了。为website添加到EntitiesServiceInterfacsdist\NBear.Common.dlldist\NBear.IoC.dll的引用。

    注意,这里无需为website添加到ServiceImpls的引用。想想为什么?如果website依赖ServiceImpls的实现代码,那么就不叫IoC了。IoC的核心是基于容器的依赖注入。换句话说,我们只需要在配置文件中指定一个Service的接口和对应的实现类的位置,从而使得Service的调用者与Service的实现者松散耦合。但是,为了能让我们的website能根据配置文件找到Service的实现类,我们还是需要复制ServiceImpls编译输出的ServiceImpls.dll到website的Bin目录中

    7.2 接着,我们需要在Web.config中配置IoC容器。NBearV3IoC组件使用castle作为IoC容器,因此,可以使用标准的castle配置与法进行配置。不过一般,只需要使用下面这样最简单的语法就行了:

    <?xml version="1.0"?>
    <configuration>
        
    <configSections>
            
    <section name="entityConfig" type="NBear.Common.EntityConfigurationSection, NBear.Common"/>
           
    <section name="castle" type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor"/>
       
    </configSections>
        
    <entityConfig>
            
    <includes>
                
    <add key="Sample Entity Config" value="~/EntityConfig.xml"/>
            
    </includes>
        
    </entityConfig>
      
    <castle>
        
    <components>
          
    <!--You can use standard castle component decleration schema to define service interface impls here-->
          
    <component id="category service" service="ServiceInterfaces.ICategoryService, ServiceInterfaces" type="ServiceImpls.CategoryService, ServiceImpls"/>
          
    <component id="product service" service="ServiceInterfaces.IProductService, ServiceInterfaces" type="ServiceImpls.ProductService, ServiceImpls"/>
        
    </components>
      
    </castle>
      
    <appSettings/>
        
    <connectionStrings>
            
    <add name="Northwind" connectionString="Server=(local);Database=Northwind;Uid=sa;Pwd=sa" providerName="NBear.Data.SqlServer.SqlDbProvider"/>
        
    </connectionStrings>
        
    <system.web>
            
    <compilation debug="true">
                
    <assemblies>
                    
    <add assembly="System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
                    
    <add assembly="System.Data.OracleClient, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
                    
    <add assembly="System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/></assemblies></compilation>
            
    <authentication mode="Windows"/>
        
    </system.web>
    </configuration>

    注意加粗的部分,这里我们添加了一个castle的配置块,并且配置了两个我们定义的Service和他们对应的实现。 

    7.3 接着,在Default.aspx.cs文件中的PageLoad中,添加下面的代码,访问Service

    注意,所有的Service只需简单地从ServiceFactory.GetService<ServiceType>()得到。然后就能使用了。

        protected void Page_Load(object sender, EventArgs e)
        
    {
            ServiceFactory factory 
    = ServiceFactory.Create();

            IProductService ps 
    = factory.GetService<IProductService>();
            Product[] products 
    = ps.GetAllProducts();
            WriteLine(
    string.Format("Got all products, {0} in total.", products.Length));

            ICategoryService cs 
    = factory.GetService<ICategoryService>();
            Category[] categories 
    = cs.GetAllCategories();
            WriteLine(
    string.Format("Got all categories, {0} in total.", categories.Length));

            WriteLine(
    "In each category:");
            
    foreach (Category item in categories)
            
    {
                WriteLine(
    string.Format("ID={0}, Name={1}, Products in category: {2}.", item.CategoryID, item.CategoryName, item.Products.Length));
            }

        }


        
    private void WriteLine(string str)
        
    {
            Response.Write(Server.HtmlEncode(str) 
    + "<br /><br />");
        }

    正文结束。

    附录

    1 关于ServiceFactory.Create()

    website中,您一定注意到,我们使用NBear.IoC.Service.ServiceFactory.Create()方法获得了ServiceFactory实例,并通过他获得Service接口的实现类实例。

    在实际的项目中,您也无需在一个统一的地方定义全局的ServiceFactory实例引用,可以在每一个需要ServiceFactory实例的地方直接调用ServiceFactory.Create(),因为ServiceFactory.Create()内部实际上使用了Singleton模式,它总是返回的唯一的一个ServiceFactory实例

    同时,除了在website中通过ServiceFactory访问service之外,在某一个Service的实现代码中,也可以访问ServiceFactory.Create(),从而访问另一个同样在Web.configcastle块中配置的service。这样,当不同的Service实现程序集之间互相调用Service时,只需要互相引用Service InterfacesService的实现代码互相就能避免任何依赖,从而将模块间的耦合度降至最低。

    //本文结束

  • 相关阅读:
    Mysql分布式事务
    Mysql锁
    Mysql事务隔离级别
    java 资源监控
    Mysql子查询
    javaWeb四大域对象
    KVM 迁移
    KVM 虚拟化
    网络基础
    系统简单启动过程
  • 原文地址:https://www.cnblogs.com/teddyma/p/551558.html
Copyright © 2020-2023  润新知