2008-09-26 12 views
6

Al utilizar SqlBulkCopy en una tabla con una clave principal GUID y newsequentialid predeterminado()¿Cómo se puede usar SQLBulkCopy en una tabla con una clave principal GUID y por defecto newsequentialid()?

por ejemplo

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] 

con el código C#

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

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

Te da un error ...

columna 'MyPrimaryKey' no permite DBNull.Value

He intentado manipular las SqlBulkCopyOptions. Lo único que funciona es establecer el campo MyPrimaryKey para permitir nulos y eliminar la clave principal.

Alguien sabe si hay una solución para este problema? ¿O puede verificar que no haya una solución (aparte de cambiar la estructura de la tabla)?

Respuesta

0

Eres únicas opciones son para quitar el campo MyPrimaryKey partir de los datos que se está cargando o para modificar la estructura de la tabla.

Con el campo de estar allí sin valores que le están diciendo SQL que desea forzar un nulo en el campo, lo que, obviamente, no está permitido.

11

Debe configurar las asignaciones de columna. Primera llamada

sqlCopy.ColumnMappings.Clear(); 

Entonces llame

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

Esto significa que la copia masiva dejará de tratar de insertar en la columna MyPrimaryKey y sólo se insertará en las columnas de estado y prioridad.

+0

Excelente, ¡¡¡esto me ayudó !!!!! – HaBo

+0

JRummell, ¿la columna GUID generará automáticamente un nuevo valor para cada fila insertada mediante el comando SQLBULKCOPY? Gracias por adelantado. –

0

Extracción de la base de datos generada a partir de las columnas del conjunto de columnas antes de escribir es lo que hay que hacer.

Utilizamos LINQ a SQL para la mayoría de nuestras operaciones de bases de datos, pero el uso de otro método para insertar muchos registros a la vez, como L2S es un poco lenta para esto.

Tenemos un método genérico llamado BulkInsertAll<> que podemos usar en cualquier tabla, que utiliza SqlBulkCopy internamente. Generamos dinámicamente las columnas usando la reflexión basada en las propiedades del tipo genérico. El ColumnAttribute se encuentra en el archivo .cs generado a partir de nuestro archivo .dbml, donde hemos especificado la columna clave principal de GUID como 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; 
} 

Y esto funciona bien. Las entidades no se actualizarán con el Guid recién asignado, por lo que tendrá que hacer otra consulta para obtenerlas, pero las nuevas filas tienen guiones generados por la propiedad en la base de datos.

Podríamos envolver ese filtro .Where en el método EventTypeFilter, pero no soy el que escribió la mayor parte de esto, y no lo he revisado para sintonizar todo.

Cuestiones relacionadas