Linq-To-Sql 尝试插入父实体而不是子实体

Posted

技术标签:

【中文标题】Linq-To-Sql 尝试插入父实体而不是子实体【英文标题】:Linq-To-Sql Attempting to Insert Parent Rather Than Child Entity 【发布时间】:2012-09-06 14:02:04 【问题描述】:

我试图理解为什么 Linq to Sql 会为子实体生成插入语句,而不是插入父实体。我正在使用两个表:WorkOrders 和 Boats。工作订单除其他外还有一条船。在我添加工作订单的表单上,我允许用户为选定的客户添加新船。如果他们选择添加新船而不是选择现有船,则会向他们提供另一种表格以添加船/船。每次我尝试保存新船时,它都会尝试插入工作订单。下面,我包含了两个类的 Linq 到 Sql 映射的子集、用于在 WorkOrders 和 Boats 之间创建外键的 sql 服务器脚本、我的 C# 保存逻辑、生成的 linq-to-sql 语句以及调用堆。有人可以告诉我这里可能发生的事情吗?

我检查了添加新船只的表格,但它没有参考工作订单,所以我知道在保存之前我没有在船只上设置工作订单。此外,我可以使用该表格添加新船只,而无需浏览工作订单屏幕,而不会出现此问题。

标记

 <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);
        
    

堆栈跟踪

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

【问题讨论】:

我绝对认为这与我不理解的 Linq-to-Sql 映射有关。我尝试将对客户对象和工作订单集合的引用设置为 null,但它没有抱怨。不知何故,Linq 正在初始化一个工作订单对象并尝试在它执行我在船表中的插入或更新语句之前将其插入。 【参考方案1】:

两天后,我终于弄清楚了我的问题。简而言之,我有一个用于添加工单的表单。表单具有用于填充表单的 WorkOrder 的类变量。一个 WorkOrder 可以有一个 Vessel 和一个 Customer。我的问题是船只与客户有关系,而客户又与 WorkOrder 有关系。一旦用户选择了客户,客户就会被设置到工作订单中。然后,当用户尝试添加或更新一艘船时,Linq 检测到该船已附加到一个附加到 WorkOrder 的客户。这解释了为什么框架在尝试插入或更新新船之前为工作订单生成插入。

现在,我已经修改了表单,以便我修改或访问支持表单的 WorkOrder 的唯一时间是加载和保存。否则,我会在我的控件上使用 text 或 tag 属性。

【讨论】:

【参考方案2】:

您或许可以使用Attach 解决此问题。

你会做类似的事情

context.WorkOrders.Attach(currentBoat.WorkOrder);

这告诉上下文 WorkOrder 是一个现有对象,应该与数据库相关联,但不能像“新”项目那样插入。

【讨论】:

谢谢,我试过了,但在这种情况下,它对我不起作用。问题是,只要我将现有客户(即已经在数据库中)设置为 Linq 关联属性,WorkOrder 就会附加到上下文中。然后,当我尝试添加引用客户的 Boat 时,Linq 尝试在插入或更新与 WorkOrder 关联的 Vessel 之前插入 WorkOrder。我这里可能有设计问题。目前,我决定只访问用于填充表单并在构造函数和保存方法期间持久保存到数据库的 WorkOrder as soon as I set an existing Customer to the Linq association property - 不知道你的意思是什么(“Linq 关联属性”?)。当我遇到这些问题时,我建议在针对上下文的每个操作之后插入一个SubmitChanges 调用。放置断点并查询数据库,或使用Log 属性转储生成的 SQL 以查看发生了什么。由于可以设置关联属性/导航属性的方式多种多样,因此很难给出明确的答案。

以上是关于Linq-To-Sql 尝试插入父实体而不是子实体的主要内容,如果未能解决你的问题,请参考以下文章

JPA - 如果已获取父项,则 JpaRepository 子记录具有父 ID 而不是实体记录 [重复]

如何在 Spring Boot 中更新子实体和父实体?

如何阻止实体框架尝试保存/插入子对象?

基于(包括)子实体返回父实体

在 BLL 或 UI 中使用 Linq-to-SQL 实体?

从单个实体迁移到具有子实体的抽象父实体,未调用 NSEntityMigrationPolicy