Spiga

NHibernate剖析:Mapping篇之Mapping-By-Code(2):运用ModelMapper

2011-04-10 16:33 by 李永京, 3738 visits, 收藏, 编辑

本节内容

Mapping-By-Code概览

上一篇文章介绍了Mapping-By-Code(代码映射)的原理,这篇文章结合上篇的原理运用Mapping-By-Code(代码映射)。为了更有效的学习代码映射,这篇文章使用单元测试的方式,每一个测试用例代表某一功能实现,并且为了 直观的认识HbmMapping对象,我把HbmMapping对象序列化为字符串写入标准输出流,为此定义一个名为ShowInConsole的扩展方法。

public static class HbmMappingExtensions
{
    public static void ShowInConsole(this HbmMapping mapping)
    {
        Console.WriteLine(mapping.AsString());
    }
}

运用Mapping-By-Code

我们先定义一个非常简单的Domain模型,一个int类型的Id属性和一个string类型的Something属性,用来代码映射:

public class MyClass
{
    public int Id { get; set; }
    public string Something { get; set; }
}

1.基本映射

ModelMapper提供一种基本映射方式:使用Class方法对实体类MyClass特定映射:

  • 属性Id映射为数据库主键,对应的列名称为MyClassId、主键生成策略是HighLow策略。
  • 属性Something映射为数据库普通字段,其长度为150。

最后调用CompileMappingForAllExplicitAddedEntities方法显式所有映射的实体(这里是MyClass)编译为HbmMapping对象并输出,也可以使用CompileMappingFor方法指定实体类型。

[Test]
public void BasicMappingRegistration()
{
    var mapper = new ModelMapper();
    mapper.Class<MyClass>(cm =>
    {
        cm.Id(myclass => myclass.Id, map =>
        {
            map.Column("MyClassId");
            map.Generator(Generators.HighLow);
        });
        cm.Property(myclass => myclass.Something, map => map.Length(150));
    });

    var hbmMapping = mapper.CompileMappingForAllExplicitAddedEntities();
    //var hbmMapping = mapper.CompileMappingFor(new[] {typeof (MyClass)});
    hbmMapping.ShowInConsole();
}

NHibernate对于代码映射提供很强的灵活性,你可以像你希望的那样随意去组织映射:例如class-by-class方式、不同的映射点在不同的地方等等。

例如下面代码映射,分开去配置映射,NHibernate对重复的属性不重复映射,去合并映射:

[Test]
public void WhenDuplicatePropertiesDoesNotDuplicateMapping()
{
    var mapper = new ModelMapper();
    mapper.Class<MyClass>(cm =>
    {
        cm.Id(myclass => myclass.Id, map => map.Column("MyClassId"));
        cm.Id(myclass => myclass.Id, map => map.Generator(Generators.HighLow));
        cm.Property(myclass => myclass.Something);
        cm.Property(myclass => myclass.Something, map => map.Length(150));
    });
    var hbmMapping = mapper.CompileMappingForAllExplicitAddedEntities();
    hbmMapping.ShowInConsole();
}

你甚至也可以在两个不同地方去映射整个实体类:

[Test]
public void WhenDuplicateClassDoesNotDuplicateMapping()
{
    var mapper = new ModelMapper();
    mapper.Class<MyClass>(cm =>
    {
        cm.Id(myclass => myclass.Id, map => map.Generator(Generators.HighLow));
        cm.Property(myclass => myclass.Something);
    });

    mapper.Class<MyClass>(cm =>
    {
        cm.Id(myclass => myclass.Id, map => map.Column("MyClassId"));
        cm.Property(myclass => myclass.Something, map => map.Length(150));
    });
    var hbmMapping = mapper.CompileMappingForAllExplicitAddedEntities();
    hbmMapping.ShowInConsole();
}

2.Conformist映射

ModelMapper提供另外一种Conformist映射方式:class-by-class方式,即每个类定义一个类去映射,然后调用AddMapping方法把映射加入ModelMapper对象。

private class MyClassMap : ClassMapping<MyClass>
{
    public MyClassMap()
    {
        Id(myclass => myclass.Id, map =>
        {
            map.Column("MyClassId");
            map.Generator(Generators.HighLow);
        });
        Property(myclass => myclass.Something, map => map.Length(150));
    }
}
[Test]
public void ConformistMappingRegistration()
{
    var mapper = new ModelMapper();
    mapper.AddMapping<MyClassMap>();
    var hbmMapping = mapper.CompileMappingForAllExplicitAddedEntities();
    hbmMapping.ShowInConsole();
}

上面的映射如果查看其输出结果,都是一样:

SimpleClassMapping

3.约定

ModelMapper提供了很多事件监听器,可以通过它扩展ModelMapper。其中就是自定义约定。其实上面定义的映射从设计思想上面说也是一种约定,暂时可以称作特定约定(Specific-Convetions)

以Before开头的事件监听称作前置约定(Pre-Conventions)。从人性化角度看前置约定(Pre-Conventions)比较民主(democratic),我们映射时可以使用特定约定(Specific-Convetions)

以After开头的事件监听称作后置约定(Post-Conventions)或者称作Hard-Conventions。从人性化角度看后置约定(Post-Conventions)就比较共和(republican),不管前面怎么特定,到最后一律使用后置约定(Post-Conventions)所规定的"条约"。

例如下面例子使用前置约定(Pre-Conventions):

[Test]
public void MapClassWithConventions()
{
    var mapper = new ModelMapper();
    //option:Pre-Conventions
    mapper.BeforeMapClass +=
        (mi, t, map) => map.Id(x => x.Column((t.Name + "id").ToUpper()));
    mapper.BeforeMapProperty +=
        (mi, propertyPath, map) => map.Column(propertyPath.ToColumnName().ToUpper());

    mapper.Class<MyClass>(cm =>
    {
        cm.Id(myclass => myclass.Id, map => map.Generator(Generators.HighLow));
        cm.Property(myclass => myclass.Something);
    });
    var hbmMapping = mapper.CompileMappingForAllExplicitAddedEntities();
    hbmMapping.ShowInConsole();
}

使用后置约定(Post-Conventions):

[Test]
public void MapClassWithHardConventions()
{
    var mapper = new ModelMapper();
    //option:Hard-Conventions
    mapper.AfterMapClass +=
        (mi, t, map) => map.Id(x => x.Column((t.Name + "id").ToUpper()));
    mapper.AfterMapProperty +=
        (mi, propertyPath, map) => map.Column(propertyPath.ToColumnName().ToUpper());

    mapper.Class<MyClass>(cm =>
    {
        cm.Id(myclass => myclass.Id, map =>
                             {
                                 map.Column("MyClassId");
                                 map.Generator(Generators.HighLow);
                             });
        cm.Property(myclass => myclass.Something, map => map.Column("Whatever"));
    });
    var hbmMapping = mapper.CompileMappingForAllExplicitAddedEntities();
    hbmMapping.ShowInConsole();
}

这个例子最终主键的映射的列名称为MYCLASSID,Something映射的列名称为SOMETHING。但是其思想有些不同。

结语

NHibernate3.2新增的Mapping-By-Code(代码映射),这篇文章结合上篇的原理从整体大运用Mapping-By-Code(代码映射)功能,有个整体方向。

参考资料

Fabio Maulo:NHibernate 3.2 mapping by code

Fabio Maulo:NHibernate 3.2: (part 2) mapping by code

标签: NHibernate
Add your comment

23 条回复

  1. #1楼 查小广      2011-04-10 16:46
    代码映射的编译性能很低下啊,好像不比xml强多少啊,觉得这样干没有什么意思啊。

    企业开发时,xml肯定是生成的,不然累死,一个项目300-500的数据表
     回复 引用 查看   
  2. #2楼 MSYXing      2011-04-10 17:01
    xml在处理时也是要另外转换成hbmMapping,反而用代码是直接返回的hbmMapping,所以代码的效率应该是优于xml的,另外xml是一直不建议生成的,如果又想用代码,又不想为每个属性配置,那就用conform吧,这样写的就更少喽
     回复 引用 查看   
  3. #3楼[楼主] 李永京      2011-04-10 17:04
    @查小广
    你的想法是以数据库为中心使用ORM框架的,当然DDD思路就很难行通了...
     回复 引用 查看   
  4. #4楼[楼主] 李永京      2011-04-10 17:09
    @MSYXing
    NHiberate3.2提供的Mapping-By-Code比较原生态,每个实体每个属性都需要手动配置。这个也是作者的意思,这样容易做扩展,ConfORM将来肯定为Mapping-By-Code原生态扩展,利用模式适配器匹配实体,如果匹配某种模式就做某种事情,这样做的目的就是减少映射代码。
     回复 引用 查看   
  5. #5楼 HCOONa      2011-04-11 20:25
    那个AsString方法是扩展方法么?我用的是NH3.2,但是没有这个方法啊@_@
     回复 引用 查看   
  6. #6楼 HCOONa      2011-04-11 20:30
    搞定了,是扩展方法,在NHibernate.Mapping.ByCode下
     回复 引用 查看   
  7. #7楼[楼主] 李永京      2011-04-11 20:32
    @HCOONa
    上面所有调用方法都是NH3.2原生自带的
     回复 引用 查看   
  8. #8楼 HCOONa      2011-04-11 20:34
    @李永京
    非常感谢,觉得这个东西不错,强类型的配置工具帮助还是非常大的
     回复 引用 查看   
  9. #9楼 碳素墨水      2011-04-28 14:47
    NHibernate 3.2 Alpha2 连SQLite时出错,改成MSSQL2005就可以。
                var config = new Configuration();
    
                config.SessionFactoryName("ORT")
                    .DataBaseIntegration(db =>
                    {
                        db.Timeout = 60;
                        db.BatchSize = 25;
                        db.Dialect<SQLiteDialect>();
                        db.Driver<SQLite20Driver>();
                        //db.Dialect<MsSql2005Dialect>();
                        //db.Driver<SqlClientDriver>();
                        db.ConnectionStringName = "ORT";
                    });
    

    错误信息:
    Callback routine requested an abort
    Execution was aborted by the user
    说明: 执行当前 Web 请求期间,出现未经处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。

    异常详细信息: System.Data.SQLite.SQLiteException: Callback routine requested an abort
    Execution was aborted by the user
    连接字符串:
    Data Source=|DataDirectory|ORT.sqlite.db;Version=3;New=True;
    有遇到过的吗?
     回复 引用 查看   
  10. #10楼 碳素墨水      2011-04-29 11:10
    今天把Bin下面的所有DLL手动删除,SQLite就可以连了,晕啊!
     回复 引用 查看   
  11. #11楼 碳素墨水      2011-04-29 14:26
    私有属性如何映射
    public class User{
        public string Id{get;set;}
        private string Password{get;set;}
    }
    

    如何映射Password
     回复 引用 查看   
  12. #12楼[楼主] 李永京      2011-04-29 14:52
    @碳素墨水
    //现在通过反射得到私有字段或者属性
    mapper.Class<SimpleEntity>(cm => cm.Property(typeof(SimpleEntity).GetProperty("_privateField"), map => { }));这样吧,你可以看看API定义
    下个版本将提供
    mapper.Class<SimpleEntity>(cm => cm.Property("_privateField", map => { })) 这种形式
     回复 引用 查看   
  13. #13楼 碳素墨水      2011-04-29 15:10
    @李永京
    我是这样写的:
    public class User{
        public string Id{get;set;}
        public string Password{get;set;}
    }
    public class UserMapping:ClassMapping<User>{
    
        public UserMapping(){
    
            base.Property(typeof(User).GetField("password"),mapping=>mapping.Column("Password"));
    //这样写会提示参数错误,第一个参数为FieldInfo
    //OR base.Property(typeof(User).GetProperty("password"),mapping=>mapping.Column("Password"));
        }
    }
    

    这样会抛出参数不能为Null的异常。
    VS2010升级SP1中等下再试试!
     回复 引用 查看   
  14. #14楼[楼主] 李永京      2011-04-29 15:16
    @碳素墨水
    使用1.基本映射形式
     回复 引用 查看   
  15. #15楼 碳素墨水      2011-04-29 15:54
    @李永京
    同样的
    第一个参数为:System.Reflection.FieldInfo
     回复 引用 查看   
  16. #16楼[楼主] 李永京      2011-04-29 16:00
    @碳素墨水
    等下个版本咯
     回复 引用 查看   
  17. #17楼 碳素墨水      2011-04-29 16:04
    @李永京
    晕!
     回复 引用 查看   
  18. #18楼 碳素墨水      2011-04-29 16:17
    @李永京
    现在貌似只能这样做了:
        public class MyClass
        {
            public int Id { get; set; }
            private string something = string.Empty;
        }
    
        public class MyClassMappingTest
        {
            [Fact]
            public void BasicMappingRegistration()
            {
                var mapper = new ModelMapper();
                mapper.Class<MyClass>(cm =>
                {
                    cm.Id(myclass => myclass.Id, map =>
                    {
                        map.Column("MyClassId");
                        map.Generator(Generators.HighLow);
                    });
    
                    cm.Property(typeof(MyClass).GetField("something"
                        , BindingFlags.Instance | BindingFlags.GetField | BindingFlags.IgnoreCase | BindingFlags.NonPublic)
                        , map => map.Length(150));
                });
    
                var hbmMapping = mapper.CompileMappingForAllExplicitAddedEntities();
                hbmMapping.ShowInConsole();
            }
        }
        public static class HbmMappingExtensions
        {
            public static void ShowInConsole(this HbmMapping mapping)
            {
                Console.WriteLine(mapping.AsString());
            }
        }
    
     回复 引用 查看   
  19. #19楼 永不言败      2011-08-17 19:06
    怎么配置存储过程,我用他提供的那个扩展方法不行。
    就是
    <sql-query name="findStockByStockCodeNativeSQL">
    <return alias="stock" class="com.mkyong.common.Stock"/>
    <![CDATA[select * from stock s where s.stock_code = :stockCode]]>
    </sql-query>

    怎么用代码写
     回复 引用 查看   
  20. #20楼[楼主] 李永京      2011-08-17 19:48
    @永不言败
    存储过程使用hbm混合映射
     回复 引用 查看   
  21. #21楼 永不言败      2011-08-17 21:23
    具体是怎么写的。我用 config.AddNamedQuery扩展方法不行
     回复 引用 查看   
  22. #22楼 天水三千      2011-08-18 20:47
    组长,如果我想要使用三层架构,即数据访问层,业务层这样的系统架构,数据访问层使用NH3来开发,我想要加个业务层,因为有可以一个操作影响多个数据,要在业务层开展事务,要怎样操作呢?
     回复 引用 查看   
  23. #23楼 ゞ 街角,代码工↘      2011-10-27 15:53
    永京大哥,问下,调用存储过程怎么调用,只能用IDbCommand去调用?3.2文档好像是这么说的,不能映射成比如currentSession.CreateSQLQuery(" EXEC ProcedureTest").List<Product>().ToList();
    这样?
     回复 引用 查看   
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 2011672 WDjTHhZjwKM=