NHibernate Exception “object references an unsaved transient instance - save the transient instance before flushing” when saving object with HasMany
-
14-11-2019 - |
Domanda
I'm trying to save an object with multiple HasMany relationships and I'm getting the exception: "object references an unsaved transient instance - save the transient instance before flushing".
Below are my simplified classes, their corresponding mappings and my "application" code.
The "Application Code" section shows what I want to do: add expense reports and time worked to an invoice, and then save the invoice.
However, the exception occurs in GetTimeWorked(). If I reverse the order (add time worked before expense reports), then the error occurs in GetExpenseReports().
If I save the invoice after I add expense reports, then save it again after I add the time worked, it works fine. However, this save needs to be transactional: expense reports and time worked must be saved together.
I have read a lot about this exception, but nothing I try works. The situations I have read about seem to be slightly different than this. I'm guessing this is a mapping issue, and I've tried some alternative mapping (on the HasMany side, with Cascade) but I'm at a loss.
Any idea what's going on here and how I can resolve it?
Thanks!
// Classes
public class TimeWorked {
public virtual long Id { get; private set; }
public virtual float Hours { get; set; }
public virtual Invoice Invoice { get; set; }
}
public class ExpenseReport {
public virtual long Id { get; set; }
public virtual IList<Expense> Expenses { get; set; }
public virtual Invoice Invoice { get; set; }
}
public class Invoice {
public virtual long Id { get; set; }
public virtual IList<ExpenseReport> ExpenseReports { get; set; }
public virtual IList<TimeWorked> BilledTime { get; set; }
public virtual void AddExpenseReport(List<ExpenseReport> expenseReports)
{
foreach (ExpenseReport er in expenseReports)
{
ExpenseReports.Add(er);
er.Invoice = this;
}
}
public virtual void AddTimeWorked(List<TimeWorked> timeWorked)
{
foreach (TimeWorked tw in timeWorked)
{
BilledTime.Add(tw);
tw.Invoice = this;
}
}
}
// Mapping
public class TimeWorkedMapping : ClassMap<TimeWorked>
{
public TimeWorkedMapping()
{
Id(x => x.Id);
References(x => x.Invoice);
}
}
public class ExpenseReportMapping : ClassMap<ExpenseReport>
{
public ExpenseReportMapping()
{
// Primary Key
Id(x => x.Id);
HasMany(x => x.Expenses).Cascade.AllDeleteOrphan();
References(x => x.Invoice);
}
}
public class InvoiceMapping : ClassMap<Invoice>
{
public InvoiceMapping()
{
Id(x => x.Id);
HasMany(x => x.ExpenseReports).Inverse();
HasMany(x => x.BilledTime).Inverse();
}
}
// Application Code
public class MyPage
{
// Do stuff...
Invoice invoice = new Invoice();
// Add the expense reports
List<ExpenseReport> erList = GetExpenseReports();
invoice.AddExpenseReport(erList);
// Add billable time
List<TimeWorked> twList = GetTimeWorked(); <<== Exception occurs in here
invoice.AddTimeWorked(twList);
// Save invoice
Save(invoice);
}
Soluzione
Fetch the lists before creating the new invoice, there is probably a problem with the default invoice ID.
List<TimeWorked> twList = GetTimeWorked();
List<ExpenseReport> erList = GetExpenseReports();
Invoice invoice = new Invoice();
// Add the expense reports
invoice.AddExpenseReport(erList);
// Add billable time
invoice.AddTimeWorked(twList);
// Save invoice
Save(invoice);
Altri suggerimenti
setting session.Flushmode == FlushMode.Commit would help. i guess GetExpenses()
and GetTimeWorked()
reference Invoices which results in a flush to get the right data