【问题标题】:How can use SQLBulkCopy on a table with a GUID primary key and default newsequentialid()?如何在具有 GUID 主键和默认 newsequentialid() 的表上使用 SQLBulkCopy?
【发布时间】:2008-09-26 08:30:18
【问题描述】:

在具有 GUID 主键和默认 newsequentialid() 的表上使用 SQLBulkCopy 时

例如

CREATE TABLE [dbo].[MyTable](
[MyPrimaryKey] [uniqueidentifier] NOT NULL CONSTRAINT [MyConstraint]  DEFAULT (newsequentialid()),
[Status] [int] NULL,
[Priority] [int] NULL,
 CONSTRAINT [PK_MyTable] PRIMARY KEY NONCLUSTERED 
(
[MyPrimaryKey] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

使用 C# 代码

        tran = connection.BeginTransaction();
        SqlBulkCopy sqlCopy = new SqlBulkCopy(connection,SqlBulkCopyOptions.Default, tran);            

        sqlCopy.DestinationTableName = "MyTable";            
        sqlCopy.WriteToServer(dataTable);

给你一个错误...

“MyPrimaryKey”列不允许 DBNull.Value

我试过摆弄 SqlBulkCopyOptions。唯一可行的是将 MyPrimaryKey 字段设置为允许空值并删除主键。

有人知道这个问题是否有解决方法吗? 或者您能否验证没有解决方法(除了更改表结构)?

【问题讨论】:

    标签: .net sql


    【解决方案1】:

    您需要设置列映射。第一次调用

    sqlCopy.ColumnMappings.Clear();
    

    然后调用

    sqlBulkCopy.ColumnMappings.Add("Status", "Status");
    sqlBulkCopy.ColumnMappings.Add("Priority", "Priority");
    

    这意味着批量复制将停止尝试插入 MyPrimaryKey 列,而只会插入状态和优先级列。

    【讨论】:

    • JRummell,GUID 列是否会自动为通过 SQLBULKCOPY 命令插入的每一行生成一个新值?提前致谢。
    【解决方案2】:

    您唯一的选择是从正在加载的数据中删除 MyPrimaryKey 字段或修改表结构。

    如果该字段没有值,您是在告诉 SQL 您想在该字段中强制输入一个空值,这显然是不允许的。

    【讨论】:

      【解决方案3】:

      在写入之前从列集中删除数据库生成的列是您需要做的。

      我们的大部分数据库操作都使用 LINQ-to-SQL,但使用另一种方法一次插入多条记录,因为 L2S 在这方面有点慢。

      我们有一个名为BulkInsertAll<> 的通用方法,我们可以在任何表上使用它,它在内部使用SqlBulkCopy。我们基于泛型类型的属性使用反射动态生成列。 ColumnAttribute 位于从我们的 .dbml 文件生成的 .cs 文件中,我们已将 guid 主键列指定为 IsDbGenerated="true"

      public void BulkInsertAll<T>( IEnumerable<T> entities ) {
          entities = entities.ToArray();
      
          string cs = Connection.ConnectionString;
          var conn = new SqlConnection( cs );
          conn.Open();
      
          Type t = typeof( T );
      
          var tableAttribute = (TableAttribute) t.GetCustomAttributes(
              typeof( TableAttribute ), false
          ).Single();
      
          var bulkCopy = new SqlBulkCopy( conn ) {
              DestinationTableName = tableAttribute.Name
          };
      
          var properties = t.GetProperties().Where( EventTypeFilter );
      
          // This will prevent the bulk insert from attempting to update DBGenerated columns
          // Without, inserts with a guid pk will fail to get the generated sequential id
          // If uninitialized guids are passed to the DB, it will throw duplicate key exceptions
          properties = properties.Where( 
              x => !x.GetCustomAttributes( typeof( ColumnAttribute ), false )
                  .Cast<ColumnAttribute>().Any( attr => attr.IsDbGenerated )
          );
      
          var table = new DataTable();
      
          foreach( var property in properties ) {
              Type propertyType = property.PropertyType;
              if( propertyType.IsGenericType &&
                  propertyType.GetGenericTypeDefinition() == typeof( Nullable<> ) ) {
                  propertyType = Nullable.GetUnderlyingType( propertyType );
              }
      
              table.Columns.Add( new DataColumn( property.Name, propertyType ) );
          }
      
          foreach( var entity in entities ) {
              table.Rows.Add( 
                  properties.Select( 
                      property => GetPropertyValue( property.GetValue( entity, null ) ) 
                  ).ToArray()
              );
          }
      
          //specify the mapping for SqlBulk Upload
          foreach( var col in properties ) {
              bulkCopy.ColumnMappings.Add( col.Name, col.Name );
          }
      
          bulkCopy.WriteToServer( table );
      
          conn.Close();
      }
      
      
      private bool EventTypeFilter( System.Reflection.PropertyInfo p ) {
          var attribute = Attribute.GetCustomAttribute( p,
              typeof( AssociationAttribute ) ) as AssociationAttribute;
      
          if( attribute == null ) return true;
          if( attribute.IsForeignKey == false ) return true;
      
          return false;
      }
      
      private object GetPropertyValue( object o ) {
          if( o == null )
              return DBNull.Value;
          return o;
      }
      

      这很好用。实体不会使用新分配的 Guid 进行更新,因此您必须进行另一个查询才能获取这些实体,但新行在数据库中具有属性生成的 guid。

      我们可以将 .Where 过滤器包装到 EventTypeFilter 方法中,但我不是编写大部分内容的人,我还没有通过它来调整所有内容。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-06-06
        • 2010-12-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-30
        • 1970-01-01
        相关资源
        最近更新 更多