我正在尝试使用大量行(~100K)初始化SQL Server CE数据库,但此过程需要几分钟才能完成。使用LinqToSQL数据模型创建数据库,使用codeplex sql bulk copy中的修改代码。
我还能做些什么来加快速度?
分析显示大部分时间花费在行插入调用中:
相关代码:
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