如何在 Fluent NHibernate 中将一对一关系映射为复合键的一部分

Posted

技术标签:

【中文标题】如何在 Fluent NHibernate 中将一对一关系映射为复合键的一部分【英文标题】:How to map one-to-one relation as part of composite key in Fluent NHibernate 【发布时间】:2015-03-27 18:37:31 【问题描述】:

我有两个表在数据库级别定义父子关系。父表有一个单列主键,子表有一个复合主键,其中一列引用父表:

application table       tasks table
= task_id (CK) -------> = taskid (PK) 
= user_id (CK)
= transaction_id (CK)

仅供参考,user_idtransaction_id 列不引用其他表。

我正在尝试为 C# 中的两个实体设置 Fluent NHibernate 映射:

public class Application

    public virtual string UserId  get; set; 
    public virtual string TransactionId  get; set; 
    public virtual string TaskId  get; set; 

    public virtual Task Task  get; set; 


public class Task

    public string Id  get; set; 

一个Application 有一个Task,但一个Task 有多个Applications。我正在适应这种关系。

internal class ApplicationMap : ClassMap<Application>

    public ApplicationMap() : base()
    
        Schema(...);
        Table(...);

        CompositeId()
            .KeyProperty(app => app.UserId, "user_id")
            .KeyReference(app => app.Task, "task_id")
            .KeyProperty(app => app.TransactionId, "transaction_id");

        // No explicit mapping defined for "task_id"
        // Other columns mapped, but omitted for brevity
    


internal class TaskMap : ClassMap<Task>

    public TaskMap()
    
        Schema(DbSchema.SchemaName);
        Table(DbSchema.TableName);

        Id(task => task.Id, "taskid");

        // Other columns mapped, but omitted for brevity

        // Relations
        HasMany(task => task.Applications);
    

在数据库中插入一个新的Application 时,我得到了这个异常:

NHibernate.QueryException:无法解析属性:TaskId of:Application。

我尝试为TaskId 属性添加到ApplicationMap 的显式映射,但我得到了超级有用的“索引超出范围。必须是非负数并且小于集合的大小。” NHibernate 的异常:

internal class ApplicationMap : ClassMap<Application>

    public ApplicationMap() : base()
    
        Schema(...);
        Table(...);

        CompositeId()
            .KeyProperty(app => app.UserId, "user_id")
            .KeyReference(app => app.Task, "task_id")
            .KeyProperty(app => app.TransactionId, "transaction_id");

        Map(app => app.TaskId, "task_id");

        // Other columns mapped, but omitted for brevity
    

阅读Fluent NHibernate compositeid to mapped class 后,我不确定还能尝试什么。这个问题和这个问题的区别在于子表上的外键列确实需要映射到实体中(Application.TaskId)。

我一直在搜索 Fluent NHibernate 文档,但很难找到任何涉及复合主键的内容,尤其是涉及到与其他表的关系时。

为什么需要TaskIdTask

我偶尔需要Application.Task,但不是很频繁。但是,应用程序表上的复合键被用作与应用程序表相关的所有其他表的复合外键引用。 TaskId 属性将被访问很多,我想避免对应用程序和任务表进行 JOIN 查询,只是为了获取应用程序表中已有的值。

“失败”单元测试

我在 NHibernate 中为此映射和存储库编写了一个单元测试,但它失败了:

var app = new Application(user)

    TaskId = "...",
    // More properties being set...
;

db.Web.Applications.Create(app);
db.SaveChanges();

var actual = db.Web.Applications.Find(app.UserId, app.TaskId, app.TransactionId);

// Test was failing here
Assert.IsNotNull(actual.Task, "No task found");

真正的问题似乎是新插入记录的Task 属性为空,并且在从同一个 NHibernate 会话检索后没有被延迟加载(经过一些研究是预期的行为)。

我经历了多次映射迭代,实际上最初的映射确实存在问题。我只是“一直有问题”,因为我不明白 NHibernate 在插入新记录时的行为。

【问题讨论】:

【参考方案1】:

我认为您对TaskClassMap 的映射需要如下:

public class TaskClassMap : ClassMap<Task>

    public TaskClassMap()
    
        Table("Task");

        Id(task => task.Id, "taskid");
        HasMany(c => c.Applications)
            .KeyColumn("task_id");
    

如果您没有指定特定的列名 (.KeyColumn),nhibernate 会尝试使用在这种情况下为 TaskId 的约定。

另外你得到下面臭名昭著的错误的原因是因为你试图在同一个映射 (ApplicationMap) 中映射同一列 (task_id) 两次:

索引超出范围。必须为非负数且小于集合的大小。

    CompositeId()
        .KeyProperty(app => app.UserId, "user_id")
        .KeyReference(app => app.Task, "task_id")
        .KeyProperty(app => app.TransactionId, "transaction_id");

    Map(app => app.TaskId, "task_id");

TaskId 属性将被大量访问,我想避免对应用程序和任务表进行 JOIN 查询,只是为了获取应用程序表中已有的值。

还要评论上面的声明,我会说如果你只访问Application.Task.Id,nhibernate 不会查询数据库。当进行延迟加载时,nhibernate 会为这种类型的关系创建一个代理对象,其中存储在内存中的唯一字段是主键 (Task.Id)。因此,如果您要访问此字段,它实际上并没有访问数据库。如果您访问 id 之外的任何其他字段,它将触发对数据库的查询以获取剩余值。就像您在评论中说的那样,该值已经存储在 Application 表中,因此 nhibernate 不会查询 Task 表,直到您尝试访问仅在该表中的值。

【讨论】:

我最终选择了这个解决方案,因为当我查看代码时,ApplicationId 类和属性感觉有点混乱,尽管这种方法也很有效。所以 +1 无处不在! 玩了一些之后,我删除了Map(app =&gt; app.TaskId, "task_id");,一切正常,直到我去查询数据库:session.QueryOver&lt;Application&gt;().Where(app =&gt; app.Task.Id == taskId).And(...).And(...)。现在我在执行查询时遇到了这个异常:“InvalidCastException: Unable to cast object of type 'Task' to type 'System.String'”。【参考方案2】:

我已经完成了你的映射,当你使用复合键进行映射时,如果你使用一个键对象,它就可以工作,像这样,

public class ApplicationId
    
        public virtual string UserId  get; set; 
        public virtual string TransactionId  get; set; 
        public virtual Task Task  get; set; 

        public override bool Equals(object obj)
        
            ApplicationId recievedObject = (ApplicationId)obj;

            if ((Task.Id == recievedObject.Task.Id) &&
                (TransactionId == recievedObject.TransactionId) &&
                (UserId == recievedObject.UserId))
            
                return true;
            

            return false;
        

        public override int GetHashCode()
        
            return base.GetHashCode();
        
    

映射就像,

public class Application
    
        public virtual ApplicationId Id  get; set; 
    

    public class ApplicationClassMap : ClassMap<Application>
    
        public ApplicationClassMap()
        
            Table("Application");

            CompositeId<ApplicationId>(app => app.Id)
            .KeyProperty(key => key.UserId, "user_id")
            .KeyReference(key => key.Task, "task_id")
            .KeyProperty(key => key.TransactionId, "transaction_id");
        
    

Task 的映射应该是这样的,

public class Task
    
        public virtual string Id  get; set; 

        public virtual IList<Application> Applications  get; set; 
    

    public class TaskClassMap : ClassMap<Task>
    
        public TaskClassMap()
        
            Table("Task");

            Id(task => task.Id, "taskid");
            HasMany<Application>(c => c.Applications);
        
    

this问题中有一些关于如何解决第二个问题的提示,

【讨论】:

感谢您的回答。我会尽快尝试并通知您。

以上是关于如何在 Fluent NHibernate 中将一对一关系映射为复合键的一部分的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Fluent 设置 NHibernate.Burrow?

如何结合 NHibernate Fluent 和 WPF-NHibernate 工具包中的 VmWrapper-Classes?

如何在 Fluent NHibernate ClassMap 类中指定表名?

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

如何在Fluent NHibernate中映射受保护的集合?

Fluent NHibernate:如何将整个类映射为只读?