• NHibernate: playing with mapping by code (2)


    NHibernate: playing with mapping by code (2)

    galletto-alla-mediterranea
    In the previous post you saw a simple example about a way to use the new mapping-by-code of NHibernate 3.2.0.
    The class-by-class mapping will be, probably, the most used way just because it is very similar to the XML mapping and the “feeling of loss of control” is near to zero (the quiet of sense).
    If you want experiment more adrenaline you can try ConfORM but if you like just a little bit of more adrenaline you can use the NHibernate’s ConventionModelMapper.

    Inside the ConventionModelMapper

    To understand what really is the ConventionModelMapper let me show you a piece of its code:
    public class ConventionModelMapper : ModelMapper
    {
        public ConventionModelMapper()
            : base(new SimpleModelInspector())
        {
            AppendDefaultEvents();
        }
    clear! no? The ConventionModelMapper is just a specialization of a ModelMapper where, instead theExplicitlyDeclaredModel, we are injecting another implementation of IModelInspector. To “simplify” its usage, and obscure to you the real power of Dependency-Injection, the ConventionModelMapper exposes some methods as, for instance:
    public void IsPersistentProperty(Func<MemberInfoboolbool> match)
    What are those three parameters of the delegate ?
    The name of the method is the question : is it a persistent property ?
    The MemberInfo is the subject of the question. The bool parameter is an “help” to take a decision and it represent what was explicitly defined (we will see later where was defined). The last bool is the answer of the question.
    What happen if we have a look to the implementation of the above method ?
    public void IsPersistentProperty(Func<MemberInfoboolbool> match)
    {
        SimpleModelInspector.IsPersistentProperty(match);
    }
    As you can see the implementation is completely passed to the SimpleModelInspector, nothing more than an “simplification” to allow you the usage of just one class.

    ConventionModelMapper vs class-by-class mapping

    This is not a matter. All these ways to perform the mapping-task, in NHibernate 3.2.0, are not one versus others because you can use all together. You can organize your mapping as you feel more confortable. There will be users who prefer explicit-class-by-class for the whole mapping; there will be users who prefer the usage of ConventionModelMapper and the method-based-mapping to specify convention-exceptions/overrides; there will be users who prefer the usage of ConventionModelMapper and the class-by-class as an organization of convention-exceptions/overrides; there will be users who implements their IModelInspector based on attributes; there will be users who implements their IModelInspector based on a custom DSL.

    The new mapping

    Taking the domain of the previous post, and some conventions already defined there, we can start from something like this:
    1. var mapper = new ConventionModelMapper();
    2.  
    3. mapper.BeforeMapClass += (mi, t, map) => map.Table(t.Name.ToLowerInvariant());
    4. mapper.BeforeMapJoinedSubclass += (mi, t, map) => map.Table(t.Name.ToLowerInvariant());
    5. mapper.BeforeMapUnionSubclass += (mi, t, map) => map.Table(t.Name.ToLowerInvariant());
    6.  
    7. mapper.BeforeMapProperty += (mi, propertyPath, map) =>
    8. {
    9.     if (typeof(decimal).Equals(propertyPath.LocalMember.GetPropertyOrFieldType()))
    10.     {
    11.         map.Type(NHibernateUtil.Currency);
    12.     }
    13. };
    14.  
    15. mapper.BeforeMapBag += (mi, propPath, map) =>
    16. {
    17.     map.Cascade(Cascade.All.Include(Cascade.DeleteOrphans));
    18.     map.BatchSize(10);
    19. };
    20.  
    21. mapper.BeforeMapClass += (mi, type, map) =>
    22.     map.Id(idmap => idmap.Generator(Generators.HighLow,
    23.         gmap => gmap.Params(new
    24.         {
    25.             table = "NextHighVaues",
    26.             column = "NextHigh",
    27.             max_lo = 100,
    28.             where = string.Format("EntityName = '{0}'", type.Name.ToLowerInvariant())
    29.         })));
    30.  
    31. Func<Typeboolbool> matchRootEntity = (type, wasDeclared)=> typeof(Entity).Equals(type.BaseType);
    32. var entities = Assembly.GetExecutingAssembly().GetExportedTypes()
    33.     .Where(t => typeof(Entity).IsAssignableFrom(t) && !typeof(Entity).Equals(t)).ToList();
    34. mapper.IsEntity((type, wasDeclared) => entities.Contains(type));
    35. mapper.IsRootEntity(matchRootEntity);
    36. HbmMapping domainMapping = mapper.CompileMappingFor(entities);
    More then the starting point (line 1), where I’m using just the constructor of ConventionModelMapper, the difference, so far, is from line 31 to 36. Because I have completely removed any kind of explicit mapping (that “incomplete class-by-class mapping”) I have to inform the ConventionModelMapper about:
    1. given a System.Type how recognize if it is an entity or not (line 32,33,34)
    2. given a System.Type how recognize if it is a root-entity (the top class of a hierarchy); line 31,35
    3. which are the classes that the ConventionModelMapper have to map (line 32, 36)
    Which is the advantage ? With the mapping of the previous post each time I’m adding a class to the domain I have to add its mapping; using this new mapping I can add a class to the domain without touch the mapping.
    The mapping process is not completed because in the removed class-by-class mapping there was two specifications outside our required conventions: the Customer.TaxId as natural-id and the Order.EmissionDay as Date. To specify only these two convention-exceptions I can use the method-based-mapping in this way:
    mapper.Class<Customer>(map => map.NaturalId(nm => nm.Property(c => c.TaxId)));
    mapper.Class<Order>(map => map.Property(o=> o.EmissionDay, pm=> pm.Type(NHibernateUtil.Date)));
    You can put the two lines in any place before call mapper.CompileMappingFor(entities).

    The result

    The integration is pretty the same:
    1. var configuration = new Configuration();
    2. configuration.DataBaseIntegration(c =>
    3. {
    4.     c.Dialect<MsSql2008Dialect>();
    5.     c.ConnectionString = @"Data Source=localhost\SQLEXPRESS;Initial Catalog=IntroNH;Integrated Security=True;Pooling=False";
    6.     c.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
    7.     c.SchemaAction = SchemaAutoAction.Create;
    8. });
    9. configuration.AddMapping(domainMapping);
    10. configuration.AddAuxiliaryDatabaseObject(CreateHighLowScript(Assembly.GetExecutingAssembly().GetExportedTypes().Where(t => matchRootEntity(t, false))));
    The difference is just at line 10.
    After build the session-factory we will have again

    And the XML will be (please look it closer):
    <?xml version="1.0" encoding="utf-8"?>
    <hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                       xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                       namespace="PlayWithMappingByCode" 
                       assembly="PlayWithMappingByCode" 
                       xmlns="urn:nhibernate-mapping-2.2">
      <class name="Customer" table="customer">
        <id name="Id" type="Int32">
          <generator class="hilo">
            <param name="table">NextHighVaues</param>
            <param name="column">NextHigh</param>
            <param name="max_lo">100</param>
            <param name="where">EntityName = 'customer'</param>
          </generator>
        </id>
        <natural-id>
          <property name="TaxId" />
        </natural-id>
        <property name="CommercialName" />
        <bag name="Orders" cascade="all,delete-orphan" batch-size="10">
          <key column="Customer" />
          <one-to-many class="Order" />
        </bag>
      </class>
      <class name="Order" table="order">
        <id name="Id" type="Int32">
          <generator class="hilo">
            <param name="table">NextHighVaues</param>
            <param name="column">NextHigh</param>
            <param name="max_lo">100</param>
            <param name="where">EntityName = 'order'</param>
          </generator>
        </id>
        <many-to-one name="Customer" />
        <property name="EmissionDay" type="Date" />
        <bag name="Items" cascade="all,delete-orphan" batch-size="10">
          <key column="Order" />
          <one-to-many class="OrderItem" />
        </bag>
      </class>
      <class name="OrderItem" table="orderitem">
        <id name="Id" type="Int32">
          <generator class="hilo">
            <param name="table">NextHighVaues</param>
            <param name="column">NextHigh</param>
            <param name="max_lo">100</param>
            <param name="where">EntityName = 'orderitem'</param>
          </generator>
        </id>
        <many-to-one name="Order" />
        <property name="Product" />
        <property name="Price" type="Currency" />
      </class>
    </hibernate-mapping>
    The mapping is correct and it works but just for a “casualty”…
  • 相关阅读:
    第13课-信号通讯
    第12课-有名管道通讯
    第11课-无名管道通讯
    第10课-进程控制
    第9课-时间编程
    【JVM 知识体系框架总结】
    【深入浅出-口语】(3):自然发音
    【深入浅出-JVM】(76):classloader
    【深入浅出-JVM】(75):class 装载
    【深入浅出-JVM】(77):SPI
  • 原文地址:https://www.cnblogs.com/sunjie9606/p/2169738.html
Copyright © 2020-2023  润新知