首页 文章

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

提问于
浏览
2

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

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 有多个 Application . 这是我正在适应的这种关系 .

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:Application .

我尝试为 TaskId 属性添加一个显式映射到 ApplicationMap ,但我从NHibernate获得了超级有用的"Index was out of range. Must be non-negative and less than the size of the collection."异常:

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文档,并且很难找到任何涉及复合主键的内容,特别是涉及与其他表的关系时 .

为什么需要TaskId和Task

我偶尔需要 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 属性为null,并且在从相同的NHibernate会话(在一些研究是预期的行为之后)检索之后没有被延迟加载 .

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

2 回答

  • 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 ) . 因此,如果你要访问这个字段,就像你在评论中说的那样,这个值已经存储在 Application 表中,因此在您尝试访问仅在该表中的值之前,nhibernate将不会查询 Task 表 . .

  • 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");
            }
        }
    

    并且任务的映射应该是,

    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);
            }
        }
    

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

相关问题