我有两个表在数据库级别定义父子关系。父表具有单列主键,子表具有复合主键,其中一列引用父表:
application table tasks table
= task_id (CK) -------> = taskid (PK)
= user_id (CK)
= transaction_id (CK)
仅供参考,user_id
和 transaction_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 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复合到映射类之后,我不确定还有什么可以尝试的。该问题与此问题之间的区别在于,子表上的外键列确实需要在实体中映射(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 在插入新记录时的行为。
我认为您的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
表,直到您尝试访问仅在该表中的值。
我已经浏览了您的映射,当您使用组合键进行映射时,如果您使用键对象,它会起作用,例如这样,
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);
}
}
这个问题中有一些关于如何解决第二个问题的提示,