I am trying to understand why Linq to Sql is generating an insert statement for a child entity rather than inserting the parent. I am working with two tables: WorkOrders and Boats. A WorkOrder has a boat amongst other things. On my form to add work orders, I allow the user to add a new boat for a selected customer. If they choose to add a new boat rather than select an existing one, they are presented with another form to add the boat/vessel. Every time I attempt to save the new Boat it is attempting to insert a work order instead. Below, I have included a subset of my Linq to Sql mapping for both classes, my sql server script to create the foreign key between WorkOrders and Boats, my C# save logic, the linq-to-sql statement that is generated, and the call stack. Can someone give me any idea of what might be going on here?
I have checked my form that adds a new vessel and it does not have a reference to a work order so I know that I am not setting a work order onto the vessel before saving. Additionally, I can use the form to add a new vessel without navigating through the work order screen without this issue.
Markup
<Table Name="dbo.Boats" Member="Boats">
<Type Name="Boat">
<Column Name="recno" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
<Column Name="EditedDateTime" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
<Column Name="AddDateTime" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
<Association Name="Boat_WorkOrder" Member="WorkOrders" ThisKey="recno" OtherKey="Vessel" Type="WorkOrder" />
</Type>
</Table>
SQL
ALTER TABLE [dbo].[WorkOrders] WITH CHECK ADD CONSTRAINT [FK_WorkOrders_Vessel] FOREIGN KEY([Vessel])
REFERENCES [dbo].[Boats] ([recno])
INSERT INTO [dbo].[WorkOrders]([CustomerFullName], [ClassListID], [ClassFullName], [ARAccountListId], [ARAccountFullName], [TemplateListID], [TemplateFullName], [InvoiceDate], [BillingAddress1], [BillingAddress2], [BillingAddress3], [BillingAddress4], [BillingAddress5], [BillingCity], [BillingState], [BillingPostalCode], [BillingCountry], [BillingNote], [ShippingAddress4], [ShippingAddress5], [TermsListID], [TermsFullName], [SalesRepListID], [SalesRepFullName], [Memo], [CustomerMessageListID], [CustomerMessageFullName], [OrderType], [OrderStatus], [Technician], [StartDate], [EstCompletionDate], [CompletionDate], [Vessel], [ShippingAddress1], [ShippingAddress2], [ShippingAddress3], [ShippingCity], [ShippingState], [ShippingPostalCode], [ShippingCountry], [PONumber], [ShippingNote], [Symptoms], [Notes], [AddDateTime], [AddBy], [EditedDateTime], [EditedBy], [TransactionStatus], [SyncStatus], [CustomerListId], [CustomerId], [Location])
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16, @p17, @p18, @p19, @p20, @p21, @p22, @p23, @p24, @p25, @p26, @p27, @p28, @p29, @p30, @p31, @p32, @p33, @p34, @p35, @p36, @p37, @p38, @p39, @p40, @p41, @p42, @p43, @p44, @p45, @p46, @p47, @p48, @p49, @p50, @p51, @p52, @p53)
C#
private void saveBoat()
{
try
{
if (operationType == GlobalCollections.dbOperationType.Update)
{
currentBoat.AddBy = "user";
currentBoat.AddDateTime = DateTime.Now;
currentBoat.EditedBy = "user";
currentBoat.EditedDateTime = DateTime.Now;
dao.UpdateEntity(currentBoat, false);
}
else if (operationType == GlobalCollections.dbOperationType.Insert)
{
currentBoat.AddBy = "user";
currentBoat.AddDateTime = DateTime.Now;
currentBoat.EditedBy = "user";
currentBoat.EditedDateTime = DateTime.Now;
dao.AddEntity(currentBoat);
}
}
catch (Exception ex)
{
GlobalCollections.showAndLogErrors(logger, ex);
}
}
Stacktrace
09/06/2012 09:36:15 Error BEN-LAPTOP MarineService.GlobalCollections.showAndLogErrors Cannot insert the value NULL into column 'AddDateTime', table 'ScribbleSoft.dbo.WorkOrders'; column does not allow nulls. INSERT fails.
The statement has been terminated.
09/06/2012 09:36:15 Error BEN-LAPTOP MarineService.GlobalCollections.showAndLogErrors at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
at System.Data.SqlClient.SqlDataReader.ConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteReader()
at System.Data.Linq.SqlClient.SqlProvider.Execute(Expression query, QueryInfo queryInfo, IObjectReaderFactory factory, Object[] parentArgs, Object[] userArgs, ICompiledSubQuery[] subQueries, Object lastResult)
at System.Data.Linq.SqlClient.SqlProvider.ExecuteAll(Expression query, QueryInfo[] queryInfos, IObjectReaderFactory factory, Object[] userArguments, ICompiledSubQuery[] subQueries)
at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
at System.Data.Linq.ChangeDirector.StandardChangeDirector.DynamicInsert(TrackedObject item)
at System.Data.Linq.ChangeDirector.StandardChangeDirector.Insert(TrackedObject item)
at System.Data.Linq.ChangeProcessor.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges()
at Scribble.Database.Utilities.Domain.Persistence.Repository`1.SaveAll() in C:\Aaron\Dev\HIGH PRIORITY\ServiceModule\MarineService\PureService.DatabaseAccess\Scribble.Database.Utilities\Domain\Persistence\Repository.cs:line 69
at Scribble.Database.Utilities.Domain.Persistence.Repository`1.UpdateEntity(T entity, Boolean attach) in C:\Aaron\Dev\HIGH PRIORITY\ServiceModule\MarineService\PureService.DatabaseAccess\Scribble.Database.Utilities\Domain\Persistence\Repository.cs:line 43
at MarineService.Tests.AddVesselForm.saveBoat() in C:\Aaron\Dev\HIGH PRIORITY\ServiceModule\MarineService\ServiceModule\AddVesselForm.cs:line 207
After two days, I finally figured out my problem. In short, I have a form that is used to add work orders. The form has a class variable for the WorkOrder that is used to populate the form. A WorkOrder can have a Vessel and a Customer. My problem is that a Vessel has a relationship to a customer which has a relationship to a WorkOrder. As soon as the user selected the Customer, the Customer was set onto the WorkOrder. And, then when the user attempted to add or update a boat, Linq detected that the Boat was attached to a Customer which was attached to a WorkOrder. This explains why the framework was generating an insert for the work order before attempting to insert or update the new boat.
Now, I have modified the form so that the only time I modify or access the WorkOrder that backs the form is on loading and saving. Otherwise, I use the text or tag attribute on my controls.