加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

如何使用Fluent Nhibernate中的Automapping进行OR Mapping映射

发布时间:2020-12-15 21:16:02 所属栏目:asp.Net 来源:网络整理
导读:由于在项目中使用了NHibernate来作为ORMapping构建数据访问层,那么就必须要配置Object和DataTable的映射。最早的项目中,我们使用了最传统的XML配置文件的方式编写映射关系,但是这样太麻烦,每次修改class和表时都要去修改对应的XML文件,而且还容易出错,

由于在项目中使用了NHibernate来作为ORMapping构建数据访问层,那么就必须要配置Object和DataTable的映射。最早的项目中,我们使用了最传统的XML配置文件的方式编写映射关系,但是这样太麻烦,每次修改class和表时都要去修改对应的XML文件,而且还容易出错,一定有疏忽遗漏的地方,还不容易找出错误,所以在第二个项目中,我们使用了Fluent NHibernate的Mapping方式代替XML配置。使用Fluent NHibernate的最大好处是降低了出错的机会,因为Fluent Nhibernate的配置是使用C#来编写,可以智能感知,而且还能编译,不像原始的XML配置,写错了都不知道。

   ConfigMapping : ClassMap
    {
         ConfigMapping()
        {
            Table();
            Id(x => x.Id,).GeneratedBy.HiLo();
             Map(x => x.ConfigKey,);
             Map(x => x.ConfigValue,);
        }
    }

但是使用Fluent NHibernate的配置方式仍然是需要编写Mapping代码的,也就意味着,如果我更改class或者DataTable的时候,还要对应的更改该Mapping文件。更多的修改意味着更多的风险,为了减少这方面的风险,同时为了减少配置的工作量,所以在最新的项目中采用了Fluent NHibernate中的Automapping。我们只需要定义好映射的规则,就可以不对每个表和类分别编写映射配置,而是按照规则进行自动的Mapping工作。这样在修改class或者DataTable时,只需要修改类和表即可,不需要再修改配置文件。

要做到Automapping,就一定要定义好严格的命名规范,然后按照规范编写Automapping规则,实现自动化的映射。比如我们可以定义如下的规则:

  1. 类名和字段名采用每个单词首字母大写的方式而数据库表名和列名使用全部大写,单词之间下划线分割的方式。(比如CostCenter类对应表COST_CENTER)
  2. 类中的主键使用Id命名,表中的主键使用表名+“_ID”的命名方式。(比如CostCenter中有public virtual long Id{get;set;},对应表中的列COST_CENTER_ID)
  3. 对于一对多的关系,使用父方的类名作为属性名,表中使用父表的主键列名作为对应的外键列的列名。(比如一个班对应多个学生,在Class类中就有public virtual IList Students{get;set;},而在Student类中就必须使用Class作为属性名:public virtual Class Class{get;set;})
  4. 对于SubClass,采用将多个子对象都存在同一个表中的方式实现,使用“TYPE”列作为DiscriminatorColumn,使用之类的类名作为子类的唯一标识。
  5. 对于多对多的关系,把两个类对应的表名进行排序,将小的排前面,然后将两个表名连接起来,中间使用“_”分割。(比如Course和Student是多对多关系,那么产生的中间表表名为COURSE_STUDENT)
  6. 对于枚举,在数据库中使用tinyint也就是一个Byte来存储,枚举在Automapping中作为UserType进行处理。

下面就来编写Automapping的转换规则,首先对String写一个扩展方法,实现CostCenter到COST_CENTER的转换:

  ToDatabaseName(  s)
 {
         Regex.Replace(s,,match =>  + match.ToString()).ToUpper();
 }

对于1,需要实现IClassConvention实现如下:

  ClassNameConvention : IClassConvention
{
       Apply(IClassInstance instance)
    {
        var tableName = instance.EntityType.Name.ToDatabaseName();
        instance.Table(tableName);
    }
}

同时对于列,需要使用IPropertyConvention接口,实现如下:

  PropertyConvention : IPropertyConvention
{
      Apply(IPropertyInstance instance)
    {
        instance.Column(instance.Name.ToDatabaseName());
    }
}

对于2,需要实现IIdConvention接口,另外我们采用的是Hilo值的主键生成方式,使用一个表HIBERNATE_UNIQUE_KEY存储每个表的流水。具体实现如下:

  PrimaryKeyConvention : IIdConvention
{
       NextHiValueColumnName = ;
       NHibernateHiLoIdentityTableName = ;
       TableColumnName = ;
<span class="kwrd"&gt;public</span> <span class="kwrd"&gt;virtual</span> <span class="kwrd"&gt;void</span> Apply(IIdentityInstance instance)
{
    var tableName = instance.EntityType.Name.ToDatabaseName();
    instance.Column(tableName + <span class="str"&gt;"_ID"</span>);<span class="rem"&gt;//这里设置主键的命名为表名+“_ID”</span>
    <span class="kwrd"&gt;if</span> (instance.Type == <span class="kwrd"&gt;typeof</span>(<span class="kwrd"&gt;long</span>))<span class="rem"&gt;//接下来设置主键的生成方式为HiLo值方式</span>
    {
        instance.GeneratedBy.HiLo(
            NHibernateHiLoIdentityTableName,NextHiValueColumnName,<span class="str"&gt;"1000000000"</span>,builder => builder.AddParam(<span class="str"&gt;"where"</span>,<span class="kwrd"&gt;string</span>.Format(<span class="str"&gt;"{0} = '{1}'"</span>,TableColumnName,tableName)));
    }
}

}

对于3,一对多的情况,需要设置“一”的一方的Collection和“多”的一方的Reference,具体如下:

  CollectionConvention : ICollectionConvention
{
      Apply(ICollectionInstance instance)
    {
         colName;
        var entityType = instance.EntityType;
        var childType = instance.ChildType;
         (entityType == childType)
            colName = ;
        
        {
            colName = entityType.Name.ToDatabaseName() + ;
        }
        instance.Key.Column(colName);
        instance.Cascade.AllDeleteOrphan();
    }
}

<pre class="csharpcode"><span class="kwrd">public <span class="kwrd">class ReferenceConvention : IReferenceConvention
{
<span class="kwrd">public <span class="kwrd">void Apply(IManyToOneInstance instance)
{
<span class="kwrd">string colName = <span class="kwrd">null;
var referenceType = instance.Class.GetUnderlyingSystemType();
var entityType = instance.EntityType;
var propertyName = instance.Property.Name;
<span class="rem">//Self association
<span class="kwrd">if (referenceType == entityType)
colName = <span class="str">"PARENT_ID";
<span class="kwrd">else
colName = propertyName.ToDatabaseName() + <span class="str">"_ID";

    instance.Column(colName);
}

}

对于4SubClass的处理,需要涉及到指定要进行Discriminate的类,还有DiscriminateColumn,然后指定DiscriminateColumn中如何对Subclass进行Mapping。

这里就需要重写DefaultAutomappingConfiguration类,在该类中指定主键、Discriminate的类等,具体代码如下:

  AutoMapConfiguration : DefaultAutomappingConfiguration
{
       ShouldMap(Type type)
    {
         (type.IsClass && type.Namespace.StartsWith());
    }
<span class="kwrd"&gt;public</span> <span class="kwrd"&gt;override</span> <span class="kwrd"&gt;bool</span> IsId(Member member)
{
    <span class="kwrd"&gt;return</span> member.Name == <span class="str"&gt;"Id"</span>;<span class="rem"&gt;//指定了每个类中的Id属性就是该类的主键</span>
}

<span class="kwrd"&gt;public</span> <span class="kwrd"&gt;override</span> <span class="kwrd"&gt;bool</span> IsDiscriminated(Type type)<span class="rem"&gt;//指定了哪些类是需要进行SubClass继承,将其SubClass都存放在一个表中的。</span>
{
    <span class="kwrd"&gt;return</span> type.In(          
        <span class="kwrd"&gt;typeof</span>(Client),<span class="kwrd"&gt;typeof</span> (Classification),<span class="kwrd"&gt;typeof</span> (MultiClassification)
        );
}

<span class="kwrd"&gt;public</span> <span class="kwrd"&gt;override</span> <span class="kwrd"&gt;string</span> GetDiscriminatorColumn(Type type)
{
    <span class="kwrd"&gt;return</span> <span class="str"&gt;"TYPE"</span>;<span class="rem"&gt;//指定了SubClass的区分列就是有一个叫做TYPE的列</span>
}

}

然后就是关于DiscriminateColumn中的值如何映射成对应的Subclass,需要实现ISubclassConvention接口,代码如下:

  SubclassConvention : ISubclassConvention
{
      Apply(ISubclassInstance instance)
    {
        instance.DiscriminatorValue(instance.EntityType.Name);
    }
}

对于5多对多,就需要实现IHasManyToManyConvention接口,在这个接口中对两个表名进行排序,然后进行连接表示中间表。具体代码如下:

  HasManyToManyConvention : IHasManyToManyConvention
    {
          Apply(IManyToManyCollectionInstance instance)
        {
            var entityDatabaseName = instance.EntityType.Name.ToDatabaseName();
            var childDatabaseName = instance.ChildType.Name.ToDatabaseName();
            var name = GetTableName(entityDatabaseName,childDatabaseName);
        instance.Table(name);
        instance.Key.Column(entityDatabaseName + <span class="str"&gt;"_ID"</span>);
        instance.Relationship.Column(childDatabaseName + <span class="str"&gt;"_ID"</span>);
    }

    <span class="kwrd"&gt;private</span> <span class="kwrd"&gt;string</span> GetTableName(<span class="kwrd"&gt;string</span> a,<span class="kwrd"&gt;string</span> b)
    {
        var r = System.String.CompareOrdinal(a,b);
        <span class="kwrd"&gt;if</span> (r > 0)
        {
            <span class="kwrd"&gt;return</span> <span class="str"&gt;"{0}_{1}"</span>.Fill(b,a);
        }
        <span class="kwrd"&gt;else</span>
        {
            <span class="kwrd"&gt;return</span> <span class="str"&gt;"{0}_{1}"</span>.Fill(a,b);
        }
    }
}</pre>

对于6枚举的处理,需要指定枚举为UserType,实现接口IUserTypeConvention,具体代码如下:

  EnumConvention : IUserTypeConvention
{
      Accept(IAcceptanceCriteria criteria)
    {
        criteria.Expect(x => x.Property.PropertyType.IsEnum);
    }
<span class="kwrd"&gt;public</span> <span class="kwrd"&gt;void</span> Apply(IPropertyInstance instance)
{
    instance.CustomType(instance.Property.PropertyType);
}

}

实现了以上这几个接口,那么大部分情况下的Automapping都可以实现了。最后是将这些接口通知给FluentNhibernate,让其应用这些接口,导入指定Assembly中的DomainModel,具体的实现方法是:

  AutoPersistenceModel Generate([] domainAssemblies,[] dalAssemblies)
{
    var mappings = AutoMap.Assemblies(
         AutoMapConfiguration(),domainAssemblies.Select(Assembly.LoadFrom).ToArray());
<span class="kwrd"&gt;foreach</span> (var ignoredBaseType <span class="kwrd"&gt;in</span> IgnoredBaseTypes)
{
    mappings.IgnoreBase(ignoredBaseType);
}
<span class="kwrd"&gt;foreach</span> (var includeBaseType <span class="kwrd"&gt;in</span> IncludeBaseTypes)
{
    mappings.IncludeBase(includeBaseType);
}

mappings.Conventions.Setup(GetConventions());<span class="rem"&gt;//指定了Automapping转换的接口实现</span>


<span class="kwrd"&gt;foreach</span> (var dalAssembly <span class="kwrd"&gt;in</span> dalAssemblies)
{
    mappings.U<a href="https://www.52php.cn/tag/SEO/" title="SEO">SEO</a>verridesFromAssembly(Assembly.LoadFrom(dalAssembly));
}

<span class="kwrd"&gt;return</span> mappings;

}
<span class="kwrd">public <span class="kwrd">static IList IgnoredBaseTypes = <span class="kwrd">new List<span class="rem">//这里列出的类都是些Base类,不会Mapping到具体某个表
{
<span class="kwrd">typeof (Entity) <span class="rem">//该对象其实就只有Id这个属性,作为所有要Mapping的类的父类
};
<span class="kwrd">public <span class="kwrd">static IList IncludeBaseTypes = <span class="kwrd">new List<span class="rem">//默认情况下抽象类是不会Mapping成表的,所以这里需要指明这些类是要Mapping成表的
{
<span class="kwrd">typeof (Classification),<span class="kwrd">typeof (MultiClassification),<span class="kwrd">typeof(Client)
};
<span class="kwrd">protected Action GetConventions()
{
<span class="kwrd">return finder =>
{
finder.Add();
finder.Add();
finder.Add();
finder.Add();
finder.Add();
finder.Add();
finder.Add();
finder.Add();
finder.Add();
finder.Add();
};
}

该方法返回了一个AutoPersistenceModel,使用这个对象注册到NHibernate中即可。

PS:以上代码主要都是同事在前期实现的,我只是在后期接手了该工作,在此基础上做了一些简单的维护和修改。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读