如何使用 FluentNHibernate N:N 映射与附加属性的关系

Posted

技术标签:

【中文标题】如何使用 FluentNHibernate N:N 映射与附加属性的关系【英文标题】:How to map with FluentNHibernate N:N Relationship with additional attribute 【发布时间】:2017-12-25 05:34:33 【问题描述】:

我有这个数据库模型:

Candidato 1N Telefone N1 TipoTelefone

我的表格在哪里有这些列

候选人

Id(int - 身份) 命名完成 DataNascimento

电话

Id(int - 身份) IdCandidato IdTipoTelefone 数字

TipoTelefone

Id(int - 身份) 名称

我的类属性是:

Candidato
=========
public virtual int Id  get; set; 
public virtual string NomeCompleto  get; set; 
public virtual DateTime DataNascimento  get; set; 
public virtual IList<Telefone> Telefones  get; set; 

Telefone
========
public virtual int Id  get; set; 
public virtual Candidato Candidato  get; set; 
public virtual TipoTelefone TipoTelefone  get; set; 
public virtual string Numero  get; set; 

TipoTelefone
============
public virtual int Id  get; set; 
public virtual string Descricao  get; set; 
public IList<Telefone> Telefones  get; set; 

我正在尝试保存一个 Candidato 及其 Telefones,因为我有一个 Candidato for Many Telefones。

但是当我尝试同时保存我的 Candidato 及其电话时,我遇到了错误。以下是我每次尝试某种尝试时都会遇到的一些错误:

对象引用未保存的瞬态实例 - 在刷新之前保存瞬态实例,或将属性的级联操作设置为使其自动保存的内容。类型:Entity.Telefone,实体:Entity.Telefone

对象是一个未保存的瞬态实例 - 合并前保存瞬态实例:Entity.Telefone

该类没有标识符属性:Entity.Telefone

当 IDENTITY_INSERT 设置为 OFF 时,无法在表“Telefone”中插入标识列的显式值。

Não é possível converter um objeto do tipo 'NHibernate.Collection.Generic.PersistentGenericSet1[Entity.Telefone]' no tipo 'System.Collections.Generic.IList1[Entity.Telefone]'。

对象引用未保存的瞬态实例 - 在刷新之前保存瞬态实例,或将属性的级联操作设置为使其自动保存的内容。类型:Entity.Telefone,实体:Entity.Telefone

无法插入:[Entity.Telefone][SQL: INSERT INTO Telefone (Numero, IdTipoTelefone, Id) VALUES (?, ?, ?);选择 SCOPE_IDENTITY()]

我的项目正在使用 NHibernate.Mapping.ByCode,但我怀疑我没有得到正确的地图解决方案。所以,我想知道,如何为这三个实体进行正确的映射。

重要的是要看到我的 N:N Telefone 表有一个属性 Numero,所以这个实体/表不是一个简单的 N:N 实体,而是一个真实的实体,因为它里面有一个自己的属性。

【问题讨论】:

【参考方案1】:

这是我的一个老例子。

这是我的“中间”表。

这是一个流利的地图

public class EmployeeToJobTitleMatchLinkMap : ClassMap<EmployeeToJobTitleMatchLinkNHEntity>


    public EmployeeToJobTitleMatchLinkMap()
    

        Schema("dbo");
        Table("EmployeeToJobTitleMatchLink");

        Id(x => x.LinkSurrogateUUID).GeneratedBy.GuidComb();
        Map(x => x.PriorityRank);
        Map(x => x.JobStartedOnDate);

        References(x => x.TheEmployee).Column("TheEmployeeUUID").Not.Nullable().Index("IX_ETJTMLM_TheEmployeeUUID_And_TheJobTitleUUID"); ;/*Bad naming convention with "The", but left here so it can be seen easily in the DDL*/
        References(x => x.TheJobTitle).Column("TheJobTitleUUID").Not.Nullable().Index("IX_ETJTMLM_TheEmployeeUUID_And_TheJobTitleUUID"); ;/*Bad naming convention with "The", but left here so it can be seen easily in the DDL*/

    

这里是(我认为和上面一样)一个hbm.xml文件。

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class xmlns="urn:nhibernate-mapping-2.2" schema="dbo" name="MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Data.NHibernateSetup.Domain.EmployeeToJobTitleMatchLinkNHEntity, MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Data.NHibernateSetup, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="EmployeeToJobTitleMatchLink">
    <id name="LinkSurrogateUUID" type="System.Nullable`1[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="LinkSurrogateUUID" />
      <generator class="guid.comb" />
    </id>
    <property name="PriorityRank" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="PriorityRank" />
    </property>
    <property name="JobStartedOnDate" type="System.DateTime, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
      <column name="JobStartedOnDate" />
    </property>
    <many-to-one class="MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Data.NHibernateSetup.Domain.EmployeeNHEntity, MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Data.NHibernateSetup, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="TheEmployee">
      <column name="TheEmployeeUUID" index="IX_ETJTMLM_TheEmployeeUUID_And_TheJobTitleUUID" not-null="true" />
    </many-to-one>
    <many-to-one class="MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Data.NHibernateSetup.Domain.JobTitleNHEntity, MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Data.NHibernateSetup, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="TheJobTitle">
      <column name="TheJobTitleUUID" index="IX_ETJTMLM_TheEmployeeUUID_And_TheJobTitleUUID" not-null="true" />
    </many-to-one>
  </class>
</hibernate-mapping>

PriorityRank 和 JobStartedOnDate 将是我的“关系的额外属性”。

这是我找到的另一个 hbm.xml 文件。

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Glue"
                   namespace="MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Glue.Domain">

  <class name="EmployeeToJobTitleMatchLink" lazy="false" schema="MySchema">

    <!-- Naming this "Id" might be a requirement, be careful -->


    <!-- 
      ISSUE If the generator class is changed from "assigned" to 
      guid
      guid.comb
      uuid.string

      It will break the ability to create the relationship

    -->


    <id name="LinkSurrogateUUID">
      <generator class="guid.comb"></generator>
    </id>

    <!-- 
    <id name="LinkSurrogateKey">
      <!- -
      <generator class="hilo">
            <param name="table">HiloHolder</param>
        <param name="column">HiloColumn</param>
      </generator>
      - ->
      <generator class="identity">
      </generator>
    </id>
    -->

    <many-to-one name="TheEmployee" column="EmployeeUUID" lazy="false" cascade="none" not-null="true" /> <!-- class="MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Glue.Domain.Employee, MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Glue" -->
    <many-to-one name="TheJobTitle" column="JobTitleUUID" lazy="false" cascade="none" not-null="true" /> <!-- class="MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Glue.Domain.JobTitle, MyCompany.MyTechnology.Prototypes.CompositeAppPOC.Glue" -->

    <!-- 
    <many-to-one name="TheEmployee" column="EmployeeKey" lazy="false" cascade="none" not-null="true" /> 
    <many-to-one name="TheJobTitle" column="JobTitleKey" lazy="false" cascade="none" not-null="true" /> 
    -->

    <!-- These are "scalar properties of the ~relationship~"-->
    <property name="PriorityRank" not-null="true" update="false"  />
    <property name="JobStartedOnDate" not-null="true" update="false" />

  </class>

</hibernate-mapping>

这是我的“中间”域对象

[Serializable]
public partial class EmployeeToJobTitleMatchLink

    public EmployeeToJobTitleMatchLink()
    
        //this.Id = Guid.NewGuid(); /* this works in conjuction with <generator class="assigned"></generator>   */
    


    //EF Tweaks
    public Guid TheEmployeeUUID  get; set; 
    public Guid TheJobTitleUUID  get; set; 

    public Guid? LinkSurrogateUUID  get; set; 

    /*  These are "scalar properties of the ~~relationship~~  */
    public int PriorityRank  get; set; 
    public DateTime JobStartedOnDate  get; set; 

    public Employee TheEmployee  get; set; 
    public JobTitle TheJobTitle  get; set; 

也许这会有所帮助。

我曾一度解决了这个问题,但现在已经好几年了。

关键是你有一个域对象,它有额外的标量,每个“边”都有一个对象。

Telefone
========
public virtual int Id  get; set; 
public virtual Candidato Candidato  get; set; 
public virtual TipoTelefone TipoTelefone  get; set; 
public virtual string Numero  get; set; 

我相信你的 Telefone 课程是正确的。所以我认为问题是你的映射。 Fluent 或 hbm.xml。也许我的“笔记”可以提供帮助。我不能把它放在 cmets 中。

我不得不将“中间”对象添加到“外部”对象中。

例如:


    [Serializable]
    public partial class Employee 
    

        public Employee()
        
            CommonConstructor();
        
        private void CommonConstructor()
        
            this.MyEmployeeToJobTitleMatchLinks = new EmployeeToJobTitleMatchLinkCollection();
        



        //EF Tweaks
        public Guid ParentDepartmentUUID  get; set; 


        public Guid? EmployeeUUID  get; set; 

        public byte[] TheVersionProperty  get; set; 

        //public int? EmployeeKey  get; set; 



//        public IDepartment ParentDepartment  get; set; 

        public string SSN  get; set; 
        public string LastName  get; set; 
        public string FirstName  get; set; 
        public DateTime CreateDate  get; set; 
        public DateTime HireDate  get; set; 
        //



        public EmployeeToJobTitleMatchLinkCollection MyEmployeeToJobTitleMatchLinks  get; set; 




        public void AddJobTitleLink(EmployeeToJobTitleMatchLink link)
        
            link.TheEmployee = this;
            if (!this.MyEmployeeToJobTitleMatchLinks.Contains(link))
            
                this.MyEmployeeToJobTitleMatchLinks.Add(link);
            

            if (!link.TheJobTitle.MyJobTitleToEmployeeMatchLinks.Contains(link))
            
                link.TheJobTitle.MyJobTitleToEmployeeMatchLinks.Add(link);
            
        

        public void RemoveJobTitleLink(EmployeeToJobTitleMatchLink link)
        
            link.TheEmployee = this;
            if (this.MyEmployeeToJobTitleMatchLinks.Contains(link))
            
                this.MyEmployeeToJobTitleMatchLinks.Remove(link);
            

            if (link.TheJobTitle.MyJobTitleToEmployeeMatchLinks.Contains(link))
            
                link.TheJobTitle.MyJobTitleToEmployeeMatchLinks.Remove(link);
            
        











        //public ICollection<IParkingAreaDeprecated> MyParkingAreas  get; set; 
        //public ICollection<ParkingArea> MyParkingAreas  get; set; 
        public IParkingAreaCollection MyParkingAreas  get; set; 





        //public void AddParkingArea(IParkingAreaDeprecated pa)
        public void AddParkingArea(ParkingArea pa)
        
            //pa.MyEmployees.Add(this);
            if (!pa.MyEmployees.Contains(this))
            
                //pa.AddEmployee(this);//Causes overflow situation
                pa.MyEmployees.Add(this);
            
            if (!this.MyParkingAreas.Contains(pa))
            
                this.MyParkingAreas.Add(pa);
            
        

        //public void RemoveParkingArea(IParkingAreaDeprecated pa)
        public void RemoveParkingArea(ParkingArea pa)
        
            if (pa.MyEmployees.Contains(this))
            
                pa.MyEmployees.Remove(this);
            
            if (this.MyParkingAreas.Contains(pa))
            
                this.MyParkingAreas.Remove(pa);
            
        


        public override string ToString()
        
           return string.Format("0:1,2", this.SSN , this.LastName , this.FirstName );
        



    





和另一个“外部”对象

   [Serializable]
    public partial class JobTitle : IJobTitle
    

        public JobTitle()
        
            CommonConstructor();
        
        private void CommonConstructor()
        
            //this.MyEmployees = new List<Employee>();
            //this.MyJobTitleToEmployeeMatchLinks = new List<EmployeeToJobTitleMatchLink>();
            this.MyJobTitleToEmployeeMatchLinks = new EmployeeToJobTitleMatchLinkCollection();
        

        public Guid? JobTitleUUID  get; set; 
        //public int? JobTitleKey  get; set; 


        //public byte[] TheVersionProperty  get; set; 


        public string JobTitleName  get; set; 
        public DateTime CreateDate  get; set; 

        //public ICollection<Employee> MyEmployees  get; set; 
        //public ICollection<IEmployeeToJobTitleMatchLink> MyJobTitleToEmployeeMatchLinks  get; set; 
        //public ICollection<EmployeeToJobTitleMatchLink> MyJobTitleToEmployeeMatchLinks  get; set; 
        public IEmployeeToJobTitleMatchLinkCollection MyJobTitleToEmployeeMatchLinks  get; set; 


        public void AddEmployeeLink(EmployeeToJobTitleMatchLink link)
        
            link.TheJobTitle = this;
            if (!this.MyJobTitleToEmployeeMatchLinks.Contains(link))
            
                this.MyJobTitleToEmployeeMatchLinks.Add(link);
            

            if (!link.TheEmployee.MyEmployeeToJobTitleMatchLinks.Contains(link))
            
                link.TheEmployee.MyEmployeeToJobTitleMatchLinks.Add(link);
            

        

        public void RemoveEmployeeLink(EmployeeToJobTitleMatchLink link)
        
            link.TheJobTitle = this;
            if (this.MyJobTitleToEmployeeMatchLinks.Contains(link))
            
                this.MyJobTitleToEmployeeMatchLinks.Remove(link);
            

            if (link.TheEmployee.MyEmployeeToJobTitleMatchLinks.Contains(link))
            
                link.TheEmployee.MyEmployeeToJobTitleMatchLinks.Remove(link);
            

        

    

这是我连接所有内容的代码。

请注意,我有一个以前没有讨论过的部门实体。 重要的部分是 Employee、JobTitle 和“链接”对象

            EmployeeController empRepo = null;
            JobTitleController jtRepo = null;

            Department idept = new Department()  DepartmentName = "Department One", CreateDate = DateTime.Now ;
            DepartmentController deptRepo = new DepartmentController();
            deptRepo.Add(idept);

            Employee emp = new Employee()  ParentDepartment = idept, SSN = "111111111", CreateDate = DateTime.Now, HireDate = DateTime.Now, LastName = "Smith", FirstName = "John" ;
            JobTitle jtDev = new JobTitle()  JobTitleName = "Developer", CreateDate = DateTime.Now ;
            JobTitle jtProjectManager = new JobTitle()  JobTitleName = "Project Manager", CreateDate = DateTime.Now ;
            JobTitle jtDba = new JobTitle()  JobTitleName = "DBA", CreateDate = DateTime.Now ;

            jtRepo = new JobTitleController();
            jtRepo.Add(jtDev);
            jtRepo.Add(jtProjectManager);

            empRepo = new EmployeeController();
            empRepo.AddEmployee(emp);

            EmployeeToJobTitleMatchLink link1ToAddThruJobTitleObject = new EmployeeToJobTitleMatchLink();
            link1ToAddThruJobTitleObject.TheEmployee = emp;
            link1ToAddThruJobTitleObject.TheJobTitle = jtDba;
            link1ToAddThruJobTitleObject.JobStartedOnDate = DateTime.Now.AddDays(-1);
            link1ToAddThruJobTitleObject.PriorityRank = 5;

            jtDba.AddEmployeeLink(link1ToAddThruJobTitleObject);

            jtRepo.Add(jtDba);

            EmployeeToJobTitleMatchLink link1ToAddThruEmployeeObject = new EmployeeToJobTitleMatchLink();
            link1ToAddThruEmployeeObject.TheEmployee = emp;
            link1ToAddThruEmployeeObject.TheJobTitle = jtDev;
            link1ToAddThruEmployeeObject.JobStartedOnDate = DateTime.Now.AddHours(-1);
            link1ToAddThruEmployeeObject.PriorityRank = 1;

            EmployeeToJobTitleMatchLink link2ToAddThruEmployeeObject = new EmployeeToJobTitleMatchLink();
            link2ToAddThruEmployeeObject.TheEmployee = emp;
            link2ToAddThruEmployeeObject.TheJobTitle = jtProjectManager;
            link2ToAddThruEmployeeObject.JobStartedOnDate = DateTime.Now.AddYears(5);
            link2ToAddThruEmployeeObject.PriorityRank = 2;

            emp.AddJobTitleLink(link1ToAddThruEmployeeObject);
            emp.AddJobTitleLink(link2ToAddThruEmployeeObject);

            empRepo.Upsert(emp);

【讨论】:

和@granadaCoder 如何正确插入这些实体?例如,您能否给我看一个保存候选人和 2 个电话的命令示例? 我附上了答案。现在我记得一些关于让所有对象排队的巫术......当您将中间对象添加到任何一个外部对象时。请参阅上面的添加和删除“链接”方法。 嗨@granadaCoder...非常感谢您的帮助。关于您的代码的一个问题...您的 EmployeeControlles 和 JobTitleController 是否有 NHibernate Add Method ? 该代码是非常简单的“NHibernate”代码。例如: public void Add(JobTitleNHEntity jt) using (ISession session = ISessionCreator.OpenSession()) using (ITransaction transaction = session.BeginTransaction()) session.Save(jt);事务.Commit(); 也许你可以从这里得到一些提示:marekblotny.blogspot.com/2009/02/…

以上是关于如何使用 FluentNHibernate N:N 映射与附加属性的关系的主要内容,如果未能解决你的问题,请参考以下文章

FluentNHibernate - 如何在没有任何身份字段的情况下进行映射

使用 FluentNhibernate 和 Autofac 的映射接口

Fluent nHibernate - 如何在联结表上映射非键列?

使用FluentNHibernate引用的CustomType

使用NHibernate和FluentNHibernate创建数据库和表?

FluentNHibernate:DateTime的默认精度是否改变了?