33using System . Data ;
44using System . Data . SqlClient ;
55using System . Diagnostics ;
6+ using System . Linq ;
67using System . Reflection ;
78using System . Threading ;
89
@@ -45,18 +46,193 @@ public GenericSQLEntityHandler(SqlConnection sqlConnection)
4546
4647 #region Save Methods
4748
48- /// <summary>
49- /// Saves a single entity to the database.
50- /// </summary>
51- /// <param name="entity">The entity to save.</param>
52- /// <param name="table">The name of the table to save to.</param>
53- /// <param name="identityColumns">Only used for Update and InsertOrUpdate. Must contain the columns that identify the entity (Case sensitive).</param>
54- /// <param name="saveType">To save to or update the database, InsertOrUpdate handles a mixed list, but has a little overhead. FastInsert will not select the autogen column after an insert.</param>
55- /// <param name="autoGenIdColumn">If an autoincrement column is in the table, on insert it sets the new id to the inserted entitys corresponding property (Case sensitive).</param>
56- /// <returns>Returns true if the entity is saved/updated, else false.</returns>
57- public bool SaveEntity < T > ( T entity , string table , string [ ] identityColumns , SaveType saveType , string autoGenIdColumn ) where T : class
49+ #region Bulk Save
50+ public bool BulkInsert < T > ( ICollection < T > entityList , string tableName , string [ ] columnNames ) where T : class
51+ {
52+ return BulkInsert ( entityList , tableName , columnNames , null ) ;
53+ }
54+
55+ public bool BulkInsert < T > ( ICollection < T > entityList , string tableName , string [ ] columnNames , string identityColumn ) where T : class
56+ {
57+ return BulkSave ( entityList , tableName , columnNames , identityColumn , false ) ;
58+ }
59+
60+ public bool BulkUpdate < T > ( ICollection < T > entityList , string tableName , string [ ] columnNames , string identityColumn ) where T : class
61+ {
62+ return BulkSave ( entityList , tableName , columnNames , identityColumn , true ) ;
63+ }
64+ public bool BulkSave < T > ( ICollection < T > entities , string tableName , string [ ] columnNames , string identityColumn ,
65+ bool update , int bulkCopyTimeout = 600 ) where T : class
66+ {
67+ bool succes = false ;
68+
69+ try
70+ {
71+ string destinationTableName ;
72+ bool identity = ! string . IsNullOrEmpty ( identityColumn ) ;
73+ if ( identity )
74+ destinationTableName = "#tmp_" + tableName ;
75+ else
76+ destinationTableName = tableName ;
77+
78+ Type entityType = typeof ( T ) ;
79+ List < PropertyInfo > propertyInfos = new List < PropertyInfo > ( ) ;
80+ if ( columnNames != null && columnNames . Length > 0 )
81+ {
82+ foreach ( string columnName in columnNames )
83+ {
84+ propertyInfos . Add ( entityType . GetProperty ( columnName ) ) ;
85+ }
86+ }
87+ else
88+ {
89+ SqlCommand tempCommand = GetSqlCommand ( ) ;
90+ Dictionary < string , string > tableColumns = GetTableColumns ( tempCommand , tableName , entityType ) ;
91+ tempCommand . Dispose ( ) ;
92+ PropertyInfo [ ] allPropertyInfoss = entityType . GetProperties ( ) ;
93+ foreach ( PropertyInfo propertyInfo in allPropertyInfoss )
94+ {
95+ if ( tableColumns . ContainsKey ( propertyInfo . Name . ToLower ( ) ) )
96+ {
97+ propertyInfos . Add ( propertyInfo ) ;
98+ }
99+ }
100+ tempCommand . Dispose ( ) ;
101+ }
102+
103+
104+
105+ SqlCommand cmd = GetSqlCommand ( ) ;
106+ SqlBulkCopy copy = new SqlBulkCopy ( sqlConnection , update || identity ? SqlBulkCopyOptions . KeepIdentity : SqlBulkCopyOptions . Default , sqlTransaction ) ;
107+ copy . BulkCopyTimeout = bulkCopyTimeout ;
108+
109+ try
110+ {
111+
112+ DataTable table = new DataTable ( ) ;
113+ string columnNamesForQuery = "" ;
114+ foreach ( PropertyInfo property in propertyInfos )
115+ {
116+ if ( ! identity || property . Name . ToLower ( ) != identityColumn . ToLower ( ) )
117+ {
118+ if ( ! update )
119+ columnNamesForQuery += "[" + property . Name + "]," ;
120+ else
121+ columnNamesForQuery += "[" + tableName + "].[" + property . Name + "]=" + destinationTableName + ".[" + property . Name + "]," ;
122+ }
123+ if ( property . PropertyType == typeof ( int ? ) )
124+ {
125+ DataColumn col = new DataColumn ( property . Name , typeof ( int ) ) ;
126+ col . AllowDBNull = true ;
127+ table . Columns . Add ( col ) ;
128+ }
129+ else
130+ table . Columns . Add ( property . Name , property . PropertyType ) ;
131+ copy . ColumnMappings . Add ( property . Name , property . Name ) ;
132+ }
133+ columnNamesForQuery = columnNamesForQuery . TrimEnd ( ',' ) ;
134+ DataRow row ;
135+ int rowNumber = 1000 ;
136+ foreach ( T entity in entities )
137+ {
138+ row = table . NewRow ( ) ;
139+ int index = 0 ;
140+ foreach ( PropertyInfo property in propertyInfos )
141+ {
142+ if ( ! update && identity && property . Name . ToLower ( ) == identityColumn . ToLower ( ) )
143+ {
144+ property . SetValue ( entity , rowNumber , null ) ;
145+ }
146+ object val = property . GetValue ( entity , null ) ;
147+ if ( val == null )
148+ row [ index ] = DBNull . Value ;
149+ else
150+ row [ index ] = val ;
151+ index ++ ;
152+ }
153+ table . Rows . Add ( row ) ;
154+ rowNumber ++ ;
155+ }
156+
157+ copy . DestinationTableName = destinationTableName ;
158+ copy . BatchSize = entities . Count ;
159+
160+ if ( identity )
161+ {
162+ //Check if the temp table exists for some reason and then just reuse it if it does otherwise create it
163+ cmd . CommandText = "IF OBJECT_ID('tempdb.." + destinationTableName + "') IS NULL BEGIN SELECT 0 END ELSE SELECT 1" ;
164+ if ( ( int ) cmd . ExecuteScalar ( ) == 0 )
165+ CreateTableClone ( tableName , destinationTableName , entityType ) ;
166+ else
167+ cmd . CommandText = "TRUNCATE TABLE " + destinationTableName ;
168+
169+ copy . WriteToServer ( table ) ;
170+ if ( ! update )
171+ {
172+ cmd . CommandText = "DECLARE @tmpIdTable TABLE (id INT) " ;
173+ cmd . CommandText += "INSERT INTO [" + tableName + "] (" + columnNamesForQuery + ") " ;
174+ cmd . CommandText += "OUTPUT inserted.[" + identityColumn + "] INTO @tmpIdTable " ;
175+ cmd . CommandText += "SELECT " + columnNamesForQuery + " FROM " + destinationTableName ;
176+ cmd . CommandText += " ORDER BY [" + identityColumn + "] " ;
177+ cmd . CommandText += "SELECT [" + identityColumn + "] FROM @tmpIdTable ORDER BY [" + identityColumn + "]" ;
178+ using ( SqlDataReader reader = cmd . ExecuteReader ( ) )
179+ {
180+ IEnumerator < T > enumerator = entities . GetEnumerator ( ) ;
181+ while ( reader . Read ( ) )
182+ {
183+ enumerator . MoveNext ( ) ;
184+ entityType . GetProperty ( identityColumn ) . SetValue ( enumerator . Current , reader [ 0 ] , null ) ;
185+ }
186+ }
187+ }
188+ else
189+ {
190+ cmd . CommandText = "UPDATE [" + tableName + "] SET " + columnNamesForQuery + " FROM " + destinationTableName ;
191+ cmd . CommandText += " WHERE " + destinationTableName + ".[" + identityColumn + "]=[" + tableName + "].[" + identityColumn + "]" ;
192+ succes = cmd . ExecuteNonQuery ( ) == entities . Count ;
193+ }
194+ cmd . CommandText = "DROP TABLE " + destinationTableName ;
195+ cmd . ExecuteNonQuery ( ) ;
196+ cmd . Dispose ( ) ;
197+ }
198+ else
199+ copy . WriteToServer ( table ) ;
200+
201+ succes = true ;
202+ }
203+ finally
204+ {
205+ copy . Close ( ) ;
206+ }
207+ }
208+ catch ( Exception exception )
209+ {
210+ string errorMessage = GenerateErrorMessage < T > ( entities . Count > 0 ? entities . First ( ) . GetType ( ) : null , tableName , ( update ? SaveType . Update : SaveType . Insert ) , exception . Message ,
211+ exception . StackTrace ) ;
212+
213+
214+
215+ Debug . WriteLine ( errorMessage ) ;
216+ ErrorOccurred ? . Invoke ( exception ) ;
217+ }
218+
219+ return succes ;
220+ }
221+
222+ #endregion Bulk Save
223+
224+ /// <summary>
225+ /// Saves a single entity to the database.
226+ /// </summary>
227+ /// <param name="entity">The entity to save.</param>
228+ /// <param name="table">The name of the table to save to.</param>
229+ /// <param name="identityColumns">Only used for Update and InsertOrUpdate. Must contain the columns that identify the entity (Case sensitive).</param>
230+ /// <param name="saveType">To save to or update the database, InsertOrUpdate handles a mixed list, but has a little overhead. FastInsert will not select the autogen column after an insert.</param>
231+ /// <param name="autoGenIdColumn">If an autoincrement column is in the table, on insert it sets the new id to the inserted entitys corresponding property (Case sensitive).</param>
232+ /// <returns>Returns true if the entity is saved/updated, else false.</returns>
233+ public bool SaveEntity < T > ( T entity , string tableName , string [ ] identityColumns , SaveType saveType , string autoGenIdColumn ) where T : class
58234 {
59- return SaveEntityList ( new List < T > ( new [ ] { entity } ) , table , identityColumns , saveType , autoGenIdColumn , false , null ) ;
235+ return SaveEntityList ( new List < T > ( new [ ] { entity } ) , tableName , identityColumns , saveType , autoGenIdColumn , false , null ) ;
60236 }
61237
62238 /// <summary>
@@ -68,9 +244,9 @@ public bool SaveEntity<T>(T entity, string table, string[] identityColumns, Save
68244 /// <param name="saveType">To save or update the database, InsertOrUpdate handles a mixed list, but has a little overhead. FastInsert will not select the autogen column after an insert.</param>
69245 /// <param name="autoGenIdColumn">If an autoincrement column is in the table, on insert it sets the new id to the inserted entitys corresponding property (Case sensitive).</param>
70246 /// <returns>Returns true if all entities are saved/updated, else false.</returns>
71- public bool SaveEntities < T > ( List < T > entityList , string table , string [ ] identityColumns , SaveType saveType , string autoGenIdColumn ) where T : class
247+ public bool SaveEntities < T > ( List < T > entityList , string tableName , string [ ] identityColumns , SaveType saveType , string autoGenIdColumn ) where T : class
72248 {
73- return SaveEntityList ( entityList , table , identityColumns , saveType , autoGenIdColumn , false , null ) ;
249+ return SaveEntityList ( entityList , tableName , identityColumns , saveType , autoGenIdColumn , false , null ) ;
74250 }
75251
76252 /// <summary>
@@ -84,12 +260,12 @@ public bool SaveEntities<T>(List<T> entityList, string table, string[] identityC
84260 /// <param name="fetchAutoGenIdColumnOnUpdate">True if the auto gen id column should be fetched on update, on insert it already does</param>
85261 /// <param name="propertiesToIgnore">Specify an array of property names, that should not be inserted/updated in the DB</param>
86262 /// <returns>Returns true if all entities are saved/updated, else false.</returns>
87- public bool SaveEntities < T > ( List < T > entityList , string table , string [ ] identityColumns , SaveType saveType , string autoGenIdColumn , bool fetchAutoGenIdColumnOnUpdate , string [ ] propertiesToIgnore ) where T : class
263+ public bool SaveEntities < T > ( List < T > entityList , string tableName , string [ ] identityColumns , SaveType saveType , string autoGenIdColumn , bool fetchAutoGenIdColumnOnUpdate , string [ ] propertiesToIgnore ) where T : class
88264 {
89- return SaveEntityList ( entityList , table , identityColumns , saveType , autoGenIdColumn , fetchAutoGenIdColumnOnUpdate , propertiesToIgnore ) ;
265+ return SaveEntityList ( entityList , tableName , identityColumns , saveType , autoGenIdColumn , fetchAutoGenIdColumnOnUpdate , propertiesToIgnore ) ;
90266 }
91267
92- private bool SaveEntityList < T > ( List < T > entities , string table , string [ ] identityColumns , SaveType saveType ,
268+ private bool SaveEntityList < T > ( List < T > entities , string tableName , string [ ] identityColumns , SaveType saveType ,
93269 string autoGenIdColumn , bool fetchAutoGenIdColumnOnUpdate , string [ ] propertiesToIgnore ) where T : class
94270 {
95271 try
@@ -121,7 +297,7 @@ private bool SaveEntityList<T>(List<T> entities, string table, string[] identity
121297 if ( identityColumns . Length != 1 )
122298 throw new Exception ( "Only one identityColumn supported when using fetchAutoGenIdColumnOnUpdate" ) ;
123299 cmd . Parameters . Clear ( ) ;
124- cmd . CommandText = "SELECT " + identityColumns [ 0 ] + ", " + autoGenIdColumn + " FROM " + table ;
300+ cmd . CommandText = "SELECT " + identityColumns [ 0 ] + ", " + autoGenIdColumn + " FROM " + tableName ;
125301 using ( SqlDataReader reader = cmd . ExecuteReader ( ) )
126302 {
127303 while ( reader . Read ( ) )
@@ -133,7 +309,7 @@ private bool SaveEntityList<T>(List<T> entities, string table, string[] identity
133309 }
134310
135311 //fetch table columns
136- Dictionary < string , string > tableColumns = GetTableColumns ( cmd , table , entityType ) ;
312+ Dictionary < string , string > tableColumns = GetTableColumns ( cmd , tableName , entityType ) ;
137313
138314
139315 if ( saveType == SaveType . Insert )
@@ -150,9 +326,9 @@ private bool SaveEntityList<T>(List<T> entities, string table, string[] identity
150326 // - generate the SQL
151327 cmd . Parameters . Clear ( ) ;
152328 if ( ! string . IsNullOrEmpty ( autoGenIdColumn ) )
153- cmd . CommandText = "SELECT TOP 1 [" + autoGenIdColumn + "] FROM [" + table + "] WHERE " ;
329+ cmd . CommandText = "SELECT TOP 1 [" + autoGenIdColumn + "] FROM [" + tableName + "] WHERE " ;
154330 else
155- cmd . CommandText = "SELECT TOP 1 [" + identityColumns [ 0 ] + "] FROM [" + table + "] WHERE " ;
331+ cmd . CommandText = "SELECT TOP 1 [" + identityColumns [ 0 ] + "] FROM [" + tableName + "] WHERE " ;
156332 foreach ( string idColumn in identityColumns )
157333 {
158334 cmd . CommandText += idColumn + " = @" + idColumn + " AND " ;
@@ -211,7 +387,7 @@ private bool SaveEntityList<T>(List<T> entities, string table, string[] identity
211387 bool autoGenIdIsGuid = false ;
212388 List < PropertyInfo > propertiesToInsert = new List < PropertyInfo > ( ) ;
213389
214- string sqlInsertInto = "INSERT INTO [" + table + "](" ;
390+ string sqlInsertInto = "INSERT INTO [" + tableName + "](" ;
215391 string sqlParameters = "VALUES(" ;
216392 // find the columns to insert into
217393 PropertyInfo [ ] propertyInfos = entityType . GetProperties ( ) ;
@@ -295,7 +471,7 @@ private bool SaveEntityList<T>(List<T> entities, string table, string[] identity
295471 if ( entitiesToUpdate . Count > 0 )
296472 {
297473 cmd . Parameters . Clear ( ) ;
298- string sqlUpdate = "UPDATE [" + table + "] SET " ;
474+ string sqlUpdate = "UPDATE [" + tableName + "] SET " ;
299475 string sqlWhere = " WHERE " ;
300476 List < PropertyInfo > whereProperties = new List < PropertyInfo > ( ) ;
301477 List < PropertyInfo > setProperties = new List < PropertyInfo > ( ) ;
@@ -360,7 +536,7 @@ private bool SaveEntityList<T>(List<T> entities, string table, string[] identity
360536 }
361537 catch ( Exception exception )
362538 {
363- string errorMessage = GenerateErrorMessage < T > ( entities . Count > 0 ? entities [ 0 ] . GetType ( ) : null , table , saveType , exception . Message ,
539+ string errorMessage = GenerateErrorMessage < T > ( entities . Count > 0 ? entities [ 0 ] . GetType ( ) : null , tableName , saveType , exception . Message ,
364540 exception . StackTrace ) ;
365541
366542 Debug . WriteLine ( errorMessage ) ;
@@ -462,6 +638,19 @@ private void AddParameterToCmd(object fieldObj, SqlCommand cmd, string idColumn)
462638 cmd . Parameters . AddWithValue ( "@" + idColumn , fieldObj ?? DBNull . Value ) ;
463639 }
464640
641+ public bool CreateTableClone ( string sourceTableName , string newName , Type entityType )
642+ {
643+ SQLTableCreator creator = new SQLTableCreator ( sqlConnection , sqlTransaction ) ;
644+ SqlCommand cmd = GetSqlCommand ( ) ;
645+
646+ cmd . CommandText = "SELECT TOP 1 * FROM [" + sourceTableName + "]" ;
647+ SqlDataReader reader = cmd . ExecuteReader ( ) ;
648+ DataTable dataTable = reader . GetSchemaTable ( ) ;
649+ reader . Close ( ) ;
650+ cmd . Dispose ( ) ;
651+ creator . Create ( dataTable , newName , entityType ) ;
652+ return true ;
653+ }
465654 #endregion Helper Methods
466655 }
467656}
0 commit comments