如何将一对一关系映射为 Fluent NHibernate 中复合键的一部分
How to map one-to-one relation as part of composite key in Fluent NHibernate
我有两个 table 在数据库级别定义父子关系。父 table 具有单列主键,子 table 具有复合主键,其中一列引用父 table:
application table tasks table
= task_id (CK) -------> = taskid (PK)
= user_id (CK)
= transaction_id (CK)
仅供参考,user_id
和 transaction_id
列不涉及其他 table。
我正在尝试为 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: could not resolve property: TaskId of: 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 后,我不确定还能尝试什么。这个问题和这个问题的区别在于,子 table 上的外键列确实需要映射到实体 (Application.TaskId
).
我一直在搜索 Fluent NHibernate 文档,但很难找到任何涉及复合主键的内容,尤其是涉及到与其他 table 的关系时。
为什么 TaskId
和 Task
都需要
我确实偶尔需要 Application.Task
,但不是很频繁。但是,应用程序 table 上的复合键用作与应用程序 table 相关的所有其他 table 的复合外键引用。 TaskId
属性 将被访问 很多 ,我想避免对应用程序和任务 table 进行 JOIN 查询获取应用程序中已有的值 table.
"Failing"单元测试
我在 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");
real的问题好像是新插入记录的Task
属性为null,从中获取后没有被懒加载相同的 NHibernate 会话(经过一些研究后是预期的行为)。
我经历了多次映射迭代,实际上最初的映射确实存在问题。我只是 "kept having problems" 因为我不明白 NHibernate 在插入新记录时的行为方式。
我已经看过你的映射,当你用复合键做映射时,如果你使用一个键对象,它就可以工作,像这样,
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);
}
}
this问题中有一些关于如何解决第二个问题的提示,
我认为 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
):
Index was out of range. Must be non-negative and less than the size of the collection.
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");
The TaskId property is going to be access a lot, and I'd like to avoid a JOIN query on the application and tasks table just to get a value already in the application table.
还要评论上面的声明,我会说,如果您只访问 Application.Task.Id
,nhibernate 将不会查询数据库。在执行延迟加载时,nhibernate 会为这种类型的关系创建一个代理对象,其中存储在内存中的唯一字段是主键 (Task.Id
)。因此,如果您要访问该字段,它实际上不会访问数据库。如果您访问 id 之外的任何其他字段,它将触发对数据库的查询以获取剩余的值。就像你在评论中说的那样,这个值已经存储在 Application
table 中,因此 nhibernate 不会查询 Task
table 直到你尝试访问一个值那只是在那 table.
我有两个 table 在数据库级别定义父子关系。父 table 具有单列主键,子 table 具有复合主键,其中一列引用父 table:
application table tasks table
= task_id (CK) -------> = taskid (PK)
= user_id (CK)
= transaction_id (CK)
仅供参考,user_id
和 transaction_id
列不涉及其他 table。
我正在尝试为 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: could not resolve property: TaskId of: 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 后,我不确定还能尝试什么。这个问题和这个问题的区别在于,子 table 上的外键列确实需要映射到实体 (Application.TaskId
).
我一直在搜索 Fluent NHibernate 文档,但很难找到任何涉及复合主键的内容,尤其是涉及到与其他 table 的关系时。
为什么 TaskId
和 Task
都需要
我确实偶尔需要 Application.Task
,但不是很频繁。但是,应用程序 table 上的复合键用作与应用程序 table 相关的所有其他 table 的复合外键引用。 TaskId
属性 将被访问 很多 ,我想避免对应用程序和任务 table 进行 JOIN 查询获取应用程序中已有的值 table.
"Failing"单元测试
我在 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");
real的问题好像是新插入记录的Task
属性为null,从中获取后没有被懒加载相同的 NHibernate 会话(经过一些研究后是预期的行为)。
我经历了多次映射迭代,实际上最初的映射确实存在问题。我只是 "kept having problems" 因为我不明白 NHibernate 在插入新记录时的行为方式。
我已经看过你的映射,当你用复合键做映射时,如果你使用一个键对象,它就可以工作,像这样,
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);
}
}
this问题中有一些关于如何解决第二个问题的提示,
我认为 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
):
Index was out of range. Must be non-negative and less than the size of the collection.
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");
The TaskId property is going to be access a lot, and I'd like to avoid a JOIN query on the application and tasks table just to get a value already in the application table.
还要评论上面的声明,我会说,如果您只访问 Application.Task.Id
,nhibernate 将不会查询数据库。在执行延迟加载时,nhibernate 会为这种类型的关系创建一个代理对象,其中存储在内存中的唯一字段是主键 (Task.Id
)。因此,如果您要访问该字段,它实际上不会访问数据库。如果您访问 id 之外的任何其他字段,它将触发对数据库的查询以获取剩余的值。就像你在评论中说的那样,这个值已经存储在 Application
table 中,因此 nhibernate 不会查询 Task
table 直到你尝试访问一个值那只是在那 table.