I have two tables defining a parent-child relationship at the database level. The parent table has a single column primary key, and the child table has a composite primary key where one column refers to the parent table:
application table tasks table
= task_id (CK) -------> = taskid (PK)
= user_id (CK)
= transaction_id (CK)
Just for reference, the user_id
and transaction_id
columns do not refer to other tables.
I'm trying to set up a Fluent NHibernate mapping for both entities in C#:
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; }
}
One Application
has one Task
, but one Task
has many Application
s. It is this relationship I'm having fits with.
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);
}
}
Upon inserting a new Application
into the database, I get this exception:
NHibernate.QueryException: could not resolve property: TaskId of: Application.
I tried adding an explicit mapping to ApplicationMap
for the TaskId
property, but I got the super helpful "Index was out of range. Must be non-negative and less than the size of the collection." exception from 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
}
}
After reading Fluent NHibernate compositeid to mapped class, I'm not sure what else to try. The difference between that question and this one is that the foreign key column on the child table does need to be mapped in the entity (Application.TaskId
).
I've been searching the Fluent NHibernate docs for while, and anything referring to composite primary keys has been hard to come by, especially when relationships to other tables are involved.
Why both TaskId
and Task
are needed
I do need Application.Task
once in a while, but not very often. However, the composite key on the application table is used as a composite foreign key reference for all the other tables related to the application table. 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.
The "Failing" Unit Test
I had written a unit test for this mapping and repository in NHibernate, and it was failing:
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");
The real problem seems to be that the Task
property of the newly inserted record is null, and is not being lazy loaded after retrieving from the same NHibernate session (which after some research is the intended behavior).
I had gone through many iterations of mappings, and actually did have a problem with the mapping initially. I just "kept having problems" because I didn't understand how NHibernate behaves on inserting new records.