优化批量插入SQL Server CE数据库

时间:2011-11-17 15:02:26

标签: .net database optimization sql-server-ce

我正在尝试使用大量行(~100K)初始化SQL Server CE数据库,但此过程需要几分钟才能完成。使用LinqToSQL数据模型创建数据库,使用codeplex sql bulk copy中的修改代码。

我还能做些什么来加快速度?

分析显示大部分时间花费在行插入调用中:

Profiled Expensive Insertion

相关代码:

Public Sub CreateAndPopulateDatabase()
    'read data from comma-separated files
    ...

    'create and populate
    Using db = New TestLinqToSQLDataModelDataContext("Data Source=<path>.sdf")
        db.CreateDatabase()

        Dim c = DirectCast(db.Connection, SqlCeConnection)
        Using trans = c.BeginTransaction()
            c.LinqToSQLCeBulkInsert(someItems, trans)
            c.LinqToSQLCeBulkInsert(someOtherItemsOfADifferentType, trans)
            c.LinqToSQLCeBulkInsert(lotsMoreItemsOfAnotherType, trans)
            ...

            trans.Commit()
        End Using
    End Using
End Function

'''<summary>Inserts a collection of items into a SQLCe database using Linq-To-Sql metadata.</summary>
'''<typeparam name="T">The type of items to insert. The target database table and column-field mappings are determined based on the type's LingToSQL meta data.</typeparam>
<Extension()>
Private Sub LinqToSQLCeBulkInsert(Of T)(conn As SqlCeConnection, collection As IEnumerable(Of T), trans As SqlCeTransaction)
    Dim tableName = GetType(T).GetCustomAttributes(inherit:=False).
                    OfType(Of TableAttribute).
                    Select(Function(e) e.Name).
                    Where(Function(e) Not String.IsNullOrEmpty(e)).
                    Append(GetType(T).Name).
                    First()
    Dim specializedRecordUpdater = MakeSpecializedRecordUpdaterForTypeToCols(Of T, SqlCeUpdatableRecord)(IterTableColumns(conn, tableName))

    Using cmd As New SqlCeCommand(tableName, conn, trans) With {.CommandType = CommandType.TableDirect},
          rs = cmd.ExecuteResultSet(ResultSetOptions.Updatable)
        Dim rec = rs.CreateRecord()
        For Each sourceItem In collection
            specializedRecordUpdater(sourceItem, rec)
            rs.Insert(rec)
        Next
    End Using
End Sub

'''<summary>Compiles a specialized dynamic method that populates a row record using values of a specified type.</summary>
Private Function MakeSpecializedRecordUpdaterForTypeToCols(Of TValue, TRecord)(cols As IEnumerable(Of String)) As Action(Of TValue, TRecord)
    'Find value getters corresponding to column names
    Dim colToValueGetterMap = GetType(TValue).GetProperties().ToMap(
        keySelector:=Function(e) e.GetCustomAttributes(inherit:=False).
                                   OfType(Of ColumnAttribute)().
                                   Select(Function(attr) attr.Name).
                                   Where(Function(x) Not String.IsNullOrEmpty(x)).
                                   Append(e.Name).
                                   First().
                                   ToUpperInvariant(),
        valueSelector:=Function(e) e.GetGetMethod())

    'Find record setters corresponding to types
    Dim typeToUpdaterMap = (From potentialSpecializedSetter In GetType(TRecord).GetMethods()
                            Where potentialSpecializedSetter.GetParameters().Length = 2
                            Where potentialSpecializedSetter.ReturnType = GetType(Void)
                            Let parColId = potentialSpecializedSetter.GetParameters().First()
                            Let parValue = potentialSpecializedSetter.GetParameters().Last()
                            Where parColId.ParameterType = GetType(Integer)
                            Where parColId.Name = "ordinal"
                            Where parValue.Name = "value"
                            Where potentialSpecializedSetter.Name = "Set" + parValue.ParameterType.Name OrElse potentialSpecializedSetter.Name = "SetValue"
                            ).ToMap(Function(e) e.parValue.ParameterType,
                                    Function(e) e.potentialSpecializedSetter)

    'Dynamic method updates each column by passing result of getter to corresponding updater
    Dim targetParameter = Expression.Parameter(GetType(TRecord), "target")
    Dim valueParameter = Expression.Parameter(GetType(TValue), "value")
    Dim updateCalls = cols.Select(Function(e, i)
                                      Dim colId = Expression.Constant(i)
                                      Dim getter = colToValueGetterMap(e.ToUpperInvariant())
                                      Dim updater = typeToUpdaterMap(getter.ReturnType)
                                      Dim getterValue = Expression.Call(valueParameter, getter)
                                      Return Expression.Call(targetParameter, updater, {colId, getterValue})
                                  End Function).ToArray()
    Dim updateBlock = Expression.Block(updateCalls)
    Dim func = Expression.Lambda(updateBlock,
                                 "_MakeSpecializedRecordUpdaterForTypeToCols" + GetType(TValue).Name,
                                 {valueParameter, targetParameter})
    Return DirectCast(func.Compile(), Action(Of TValue, TRecord))
End Function

'''<summary>Enumerates the columns in a SQLCe database table.</summary>
Private Iterator Function IterTableColumns(conn As SqlCeConnection, tableName As String) As IEnumerable(Of String)
    Dim hasResults = False
    Using ordCmd As New SqlCeCommand("SELECT Column_Name FROM information_schema.columns WHERE TABLE_NAME = N'{0}' ORDER BY Ordinal_Position".Frmt(tableName), conn),
          reader = ordCmd.ExecuteReader()
        While reader.Read()
            Dim name = reader.GetString(0)
            If String.IsNullOrEmpty(name) Then Throw New IO.IOException("Unnamed column")
            Yield name
            hasResults = True
        End While
    End Using
    If Not hasResults Then Throw New IO.IOException("No columns")
End Function

0 个答案:

没有答案