Datei: NDODLL/SqlPersistenceHandling/SqlPersistenceHandler.cs

Last Commit (831b962)
1 //
2 // Copyright (c) 2002-2016 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Text;
25 using System.Text.RegularExpressions;
26 using System.Diagnostics;
27 using System.IO;
28 using System.Reflection;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Linq;
32 using System.Data;
33 using System.Data.Common;
34 using NDO;
35 using NDO.Mapping;
36 using NDO.Logging;
37 using NDOInterfaces;
38 using NDO.Query;
39 using System.Globalization;
40 using NDO.Configuration;
41
42 namespace NDO.SqlPersistenceHandling
43 {
44 ····/// <summary>
45 ····/// Parameter type for the IProvider function RegisterRowUpdateHandler
46 ····/// </summary>
47 ····public delegate void RowUpdateHandler(DataRow row);
48
49 ····/// <summary>
50 ····/// Summary description for NDOPersistenceHandler.
51 ····/// </summary>
52 ····///
53 ····public class SqlPersistenceHandler : IPersistenceHandler
54 ····{
55 ········/// <summary>
56 ········/// This event will be triggered, if a concurrency error occurs
57 ········/// </summary>
58 ········public event ConcurrencyErrorHandler ConcurrencyError;
59
60 ········private IDbCommand selectCommand;
61 ········private IDbCommand insertCommand;
62 ········private IDbCommand updateCommand;
63 ········private IDbCommand deleteCommand;
64 ········private IDbConnection conn;
65 ········private IDbTransaction transaction;
66 ········private DbDataAdapter dataAdapter;
67 ········private Class classMapping;
68 ········private string selectFieldList;
69 ········private string selectFieldListWithAlias;
70 ········private string tableName;
71 ········private string qualifiedTableName;
72 ········private bool verboseMode;
73 ········private ILogAdapter logAdapter;
74 ········private Dictionary<string, IMappingTableHandler> mappingTableHandlers = new Dictionary<string, IMappingTableHandler>();
75 ········private IProvider provider;
76 ········private NDOMapping ndoMapping;
77 ········private string timeStampColumn = null;
78 ········private Column typeNameColumn = null;
79 ········private bool hasAutoincrementedColumn;
80 ········private OidColumn autoIncrementColumn;
81 ········private Dictionary<string,MemberInfo> persistentFields;
82 ········private List<RelationFieldInfo> relationInfos;
83 ········private Type type;
84 ········private int guidlength;
85 ········private string hollowFields;
86 ········private string hollowFieldsWithAlias;
87 ········private string fieldList;
88 ········private string namedParamList;
89 ········private bool hasGuidOid;
90 ········private readonly INDOContainer configContainer;
91 ········private Action<Type,IPersistenceHandler> disposeCallback;
92
93 ········/// <summary>
94 ········/// Constructs a SqlPersistenceHandler object
95 ········/// </summary>
96 ········/// <param name="configContainer"></param>
97 ········public SqlPersistenceHandler(INDOContainer configContainer)
98 ········{
99 ············this.configContainer = configContainer;
100 ········}
101
102 ········private void GenerateSelectCommand()
103 ········{
104 ············this.selectCommand.CommandText = string.Empty;
105 ········}
106
107 ········private int ParameterLength(Mapping.Field fieldMapping, Type memberType)
108 ········{
109 ············if (0 == fieldMapping.Column.Size)
110 ················return provider.GetDefaultLength(memberType);
111 ············else
112 ················return fieldMapping.Column.Size;
113 ········}
114
115 ········private void GenerateInsertCommand()
116 ········{
117 ············// Generate Parameters
118 ············foreach (OidColumn oidColumn in this.classMapping.Oid.OidColumns)
119 ············{
120 ················if (!oidColumn.AutoIncremented && oidColumn.FieldName == null && oidColumn.RelationName == null)
121 ················{
122 ····················provider.AddParameter( insertCommand, provider.GetNamedParameter( oidColumn.Name ), provider.GetDbType( oidColumn.SystemType ), provider.GetDefaultLength( oidColumn.SystemType ), oidColumn.Name );
123 ················}
124 ············}
125
126 ············foreach (var e in this.persistentFields)
127 ············{
128 ················Type memberType;
129 ················if (e.Value is FieldInfo)
130 ····················memberType = ((FieldInfo)e.Value).FieldType;
131 ················else
132 ····················memberType = ((PropertyInfo)e.Value).PropertyType;
133
134 ················var fieldMapping = classMapping.FindField( (string)e.Key );
135
136 ················// Ignore fields without mapping.
137 ················if (null == fieldMapping)
138 ····················continue;
139
140 ················if (null == fieldMapping.Column.DbType)
141 ················{
142 ····················fieldMapping.ColumnDbType = (int)provider.GetDbType( memberType );
143 ················}
144 ················else
145 ················{
146 ····················fieldMapping.ColumnDbType = (int)provider.GetDbType( fieldMapping.Column.DbType );
147 ················}
148
149 ················provider.AddParameter( insertCommand, provider.GetNamedParameter( fieldMapping.Column.Name ), fieldMapping.ColumnDbType, ParameterLength( fieldMapping, memberType ), fieldMapping.Column.Name );
150 ············}
151
152 ············foreach (RelationFieldInfo ri in relationInfos)
153 ············{
154 ················Relation r = ri.Rel;
155 ················foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
156 ················{
157 ····················provider.AddParameter( insertCommand, provider.GetNamedParameter( fkColumn.Name ), provider.GetDbType( fkColumn.SystemType ), provider.GetDefaultLength( fkColumn.SystemType ), fkColumn.Name );
158 ················}
159 ················if (r.ForeignKeyTypeColumnName != null)
160 ················{
161 ····················provider.AddParameter( insertCommand, provider.GetNamedParameter( r.ForeignKeyTypeColumnName ), provider.GetDbType( typeof( int ) ), provider.GetDefaultLength( typeof( int ) ), r.ForeignKeyTypeColumnName );
162 ················}
163
164 ············}
165
166 ············if (this.timeStampColumn != null)
167 ············{
168 ················provider.AddParameter( insertCommand, provider.GetNamedParameter( timeStampColumn ), provider.GetDbType( typeof( Guid ) ), guidlength, this.timeStampColumn );
169 ············}
170
171 ············if (this.typeNameColumn != null)
172 ············{
173 ················Type tncType = Type.GetType( this.typeNameColumn.NetType );
174 ················provider.AddParameter( insertCommand, provider.GetNamedParameter( typeNameColumn.Name ), provider.GetDbType( tncType ), provider.GetDefaultLength( tncType ), this.typeNameColumn.Name );
175 ············}
176
177 ············string sql;
178 ············//{0} = TableName: Mitarbeiter············
179 ············//{1} = FieldList: vorname, nachname
180 ············//{2} = NamedParamList mit @: @vorname, @nachname
181 ············//{3} = FieldList mit Id: id, vorname, nachname
182 ············//{4} = Name der Id-Spalte
183 ············if (hasAutoincrementedColumn && provider.SupportsLastInsertedId && provider.SupportsInsertBatch)
184 ············{
185 ················sql = "INSERT INTO {0} ({1}) VALUES ({2}); SELECT {3} FROM {0} WHERE ({4} = " + provider.GetLastInsertedId(this.tableName, this.autoIncrementColumn.Name) + ")";
186 ················sql = string.Format(sql, qualifiedTableName, this.fieldList, this.namedParamList, selectFieldList, this.autoIncrementColumn.Name);
187 ················this.insertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
188 ············}
189 ············else
190 ············{
191 ················sql = "INSERT INTO {0} ({1}) VALUES ({2})";
192 ················sql = string.Format(sql, qualifiedTableName, this.fieldList, this.namedParamList);
193 ············}
194 ············if (hasAutoincrementedColumn && !provider.SupportsInsertBatch)
195 ············{
196 ················if (provider.SupportsLastInsertedId)
197 ····················provider.RegisterRowUpdateHandler(this);
198 ················else
199 ····················throw new NDOException(32, "The provider of type " + provider.GetType().FullName + " doesn't support Autonumbered Ids. Use Self generated Ids instead.");
200 ············}
201 ············this.insertCommand.CommandText = sql;
202 ············this.insertCommand.Connection = this.conn;
203 ········}
204
205 ········/// <summary>
206 ········/// Row update handler for providers that require Row Update Handling
207 ········/// </summary>
208 ········/// <param name="row"></param>
209 ········public void OnRowUpdate(DataRow row)
210 ········{
211 ············if (row.RowState == DataRowState.Deleted)
212 ················return;
213
214 ············if (!hasAutoincrementedColumn)
215 ················return;
216 ············
217 ············string oidColumnName = this.autoIncrementColumn.Name;
218 ············Type t = row[oidColumnName].GetType();
219 ············if (t != typeof(int))
220 ················return;
221 ············
222 ············// Ist schon eine ID vergeben?
223 ············if (((int)row[oidColumnName]) > 0)
224 ················return;
225 ············bool unchanged = (row.RowState == DataRowState.Unchanged);
226 ············IDbCommand cmd = provider.NewSqlCommand(this.conn);
227
228 ············cmd.CommandText = provider.GetLastInsertedId(this.tableName, this.autoIncrementColumn.Name);
229 ············DumpBatch(cmd.CommandText);
230
231 ············using (IDataReader reader = cmd.ExecuteReader())
232 ············{
233 ················if (reader.Read())
234 ················{
235 ····················object oidValue = reader.GetValue(0);
236 ····················if ( this.verboseMode )
237 ····················{
238 ························if ( oidValue == DBNull.Value )
239 ····························LogAdapter.Info( oidColumnName + " = DbNull" );
240 ························else
241 ····························LogAdapter.Info( oidColumnName + " = " + oidValue );
242 ····················}
243 ····················row[oidColumnName] = oidValue;
244 ····················if (unchanged)
245 ························row.AcceptChanges();
246 ················}
247 ················else
248 ····················throw new NDOException(33, "Can't read autonumbered id from the database.");
249 ············}
250 ········}
251
252 ········private void GenerateUpdateCommand()
253 ········{
254 ············string sql;
255
256 ············NDO.Mapping.Field fieldMapping;
257
258 ············sql = @"UPDATE {0} SET {1} WHERE ({2})";
259
260 ········
261 ············//{0} = Tabellenname: Mitarbeiter
262 ············//{1} = Zuweisungsliste: vorname = @vorname, nachname = @nachname
263 ············//{2} = Where-Bedingung: id = @Original_id [ AND TimeStamp = @Original_timestamp ]
264 ············AssignmentGenerator assignmentGenerator = new AssignmentGenerator(this.classMapping);
265 ············string zuwListe = assignmentGenerator.Result;
266
267 ············foreach (var e in this.persistentFields)
268 ············{
269 ················Type memberType;
270 ················if (e.Value is FieldInfo)
271 ····················memberType = ((FieldInfo)e.Value).FieldType;
272 ················else
273 ····················memberType = ((PropertyInfo)e.Value).PropertyType;
274
275 ················fieldMapping = classMapping.FindField( (string)e.Key );
276 ················if (fieldMapping != null)
277 ················{
278 ····················provider.AddParameter( updateCommand, provider.GetNamedParameter( "U_" + fieldMapping.Column.Name ), fieldMapping.ColumnDbType, ParameterLength( fieldMapping, memberType ), fieldMapping.Column.Name );
279 ················}
280 ············}
281
282 ············foreach (RelationFieldInfo ri in relationInfos)
283 ············{
284 ················Relation r = ri.Rel;
285 ················if (r.Multiplicity == RelationMultiplicity.Element && r.MappingTable == null
286 ····················|| r.Multiplicity == RelationMultiplicity.List && r.MappingTable == null && r.Parent.FullName != classMapping.FullName)
287 ················{
288 ····················foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
289 ····················{
290 ························Type systemType = fkColumn.SystemType;
291 ························provider.AddParameter( updateCommand, provider.GetNamedParameter( "U_" + fkColumn.Name ), provider.GetDbType( systemType ), provider.GetDefaultLength( systemType ), fkColumn.Name );
292 ····················}
293 ····················if (r.ForeignKeyTypeColumnName != null)
294 ····················{
295 ························provider.AddParameter( updateCommand, provider.GetNamedParameter( "U_" + r.ForeignKeyTypeColumnName ), provider.GetDbType( typeof( int ) ), provider.GetDefaultLength( typeof( int ) ), r.ForeignKeyTypeColumnName );
296 ····················}
297 ················}
298 ············}
299
300 ············string where = string.Empty;
301
302 ············if (this.timeStampColumn != null)
303 ············{
304 ················if (provider.UseNamedParams)
305 ····················where += provider.GetQuotedName(timeStampColumn) + " = " + provider.GetNamedParameter("U_Original_" + timeStampColumn) + " AND ";
306 ················else
307 ····················where += provider.GetQuotedName(timeStampColumn) + " = ? AND ";
308 ················// The new timestamp value as parameter
309 ················provider.AddParameter(updateCommand, provider.GetNamedParameter("U_" + timeStampColumn), provider.GetDbType(typeof(Guid)), guidlength, timeStampColumn);
310 ················provider.AddParameter(updateCommand, provider.GetNamedParameter("U_Original_" + timeStampColumn), provider.GetDbType(typeof(Guid)), guidlength, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), timeStampColumn, System.Data.DataRowVersion.Original, null);
311 ············}
312
313 ············int oidCount = classMapping.Oid.OidColumns.Count;
314 ············for (int i = 0; i < oidCount; i++)
315 ············{
316 ················OidColumn oidColumn = (OidColumn)classMapping.Oid.OidColumns[i];
317 ················// Oid as parameter
318 ················provider.AddParameter(updateCommand, provider.GetNamedParameter("U_Original_" + oidColumn.Name), provider.GetDbType(oidColumn.SystemType), oidColumn.TypeLength, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), oidColumn.Name, System.Data.DataRowVersion.Original, null);
319 ················if (provider.UseNamedParams)
320 ····················where += provider.GetQuotedName(oidColumn.Name) + " = " + provider.GetNamedParameter("U_Original_" + oidColumn.Name);
321 ················else
322 ····················where += provider.GetQuotedName(oidColumn.Name) + " = ?";
323
324 ················Relation r = oidColumn.Relation;
325 ················if (!this.hasGuidOid && r != null && r.ForeignKeyTypeColumnName != null)
326 ················{
327 ····················where += " AND " +
328 ························provider.GetQuotedName(r.ForeignKeyTypeColumnName) + " = " + provider.GetNamedParameter("U_Original_" + r.ForeignKeyTypeColumnName);
329 ····················provider.AddParameter(updateCommand, provider.GetNamedParameter("U_Original_" + r.ForeignKeyTypeColumnName), provider.GetDbType(typeof(int)), provider.GetDefaultLength(typeof(int)), System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), r.ForeignKeyTypeColumnName, System.Data.DataRowVersion.Original, null);
330 ················}
331
332 ················if (i < oidCount - 1)
333 ····················where += " AND ";
334 ············}
335 ············//else
336 ············//{
337 ············//····// Dual oids are defined using two relations.
338 ············//····MultiKeyHandler dkh = new MultiKeyHandler(this.classMapping);
339 ················
340 ············//····for (int i = 0; i < 2; i++)
341 ············//····{
342 ············//········where += provider.GetQuotedName(dkh.ForeignKeyColumnName(i)) + " = " + provider.GetNamedParameter("U_Original_" + dkh.ForeignKeyColumnName(i));
343 ············//········provider.AddParameter(updateCommand, provider.GetNamedParameter("U_Original_" + dkh.ForeignKeyColumnName(i)), provider.GetDbType(dkh.GetClass(i).Oid.FieldType), dkh.GetClass(i).Oid.TypeLength, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), dkh.ForeignKeyColumnName(i), System.Data.DataRowVersion.Original, null);
344 ············//········if (dkh.ForeignKeyTypeColumnName(i) != null && dkh.GetClass(i).Oid.FieldType != typeof(Guid))
345 ············//········{
346 ············//············where += " AND " +
347 ············//················provider.GetQuotedName(dkh.ForeignKeyTypeColumnName(i)) + " = " + provider.GetNamedParameter("U_Original_" + dkh.ForeignKeyTypeColumnName(i));
348 ············//············provider.AddParameter(updateCommand, provider.GetNamedParameter("U_Original_" + dkh.ForeignKeyTypeColumnName(i)), provider.GetDbType(typeof(int)), provider.GetDefaultLength(typeof(int)), System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), dkh.ForeignKeyTypeColumnName(i), System.Data.DataRowVersion.Original, null);
349 ············//········}
350 ············//········if (i == 0)
351 ············//············where += " AND ";
352 ············//····}
353 ············//}
354
355 ············sql = string.Format(sql, qualifiedTableName, zuwListe, where);
356 ············//Console.WriteLine(sql);
357 ············this.updateCommand.CommandText = sql;
358 ········}
359
360
361 ········private void GenerateDeleteCommand()
362 ········{
363 ············string sql = "DELETE FROM {0} WHERE ({1})";
364 ············//{0} = Tabellenname: Mitarbeiter
365 ············//{1} = Where-Bedingung: id = @Original_id
366
367 ············string where = string.Empty;
368
369 ············int oidCount = this.classMapping.Oid.OidColumns.Count;
370 ············for(int i = 0; i < oidCount; i++)
371 ············{
372 ················OidColumn oidColumn = (OidColumn)this.classMapping.Oid.OidColumns[i];
373 ················if (provider.UseNamedParams)
374 ····················where += provider.GetQuotedName(oidColumn.Name) + " = " + provider.GetNamedParameter("D_Original_" + oidColumn.Name);
375 ················else
376 ····················where += provider.GetQuotedName(oidColumn.Name) + " = ?";
377 ················provider.AddParameter(deleteCommand, provider.GetNamedParameter("D_Original_" + oidColumn.Name), provider.GetDbType(oidColumn.SystemType), oidColumn.TypeLength, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), oidColumn.Name, System.Data.DataRowVersion.Original, null);
378
379 ················Relation r = oidColumn.Relation;
380 ················if (!this.hasGuidOid && r != null && r.ForeignKeyTypeColumnName != null)
381 ················{
382 ····················where += " AND " +
383 ························provider.GetQuotedName(r.ForeignKeyTypeColumnName) + " = " + provider.GetNamedParameter("D_Original_" + r.ForeignKeyTypeColumnName);
384 ····················provider.AddParameter(updateCommand, provider.GetNamedParameter("D_Original_" + r.ForeignKeyTypeColumnName), provider.GetDbType(typeof(int)), provider.GetDefaultLength(typeof(int)), System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), r.ForeignKeyTypeColumnName, System.Data.DataRowVersion.Original, null);
385 ················}
386
387 ················if (i < oidCount - 1)
388 ····················where += " AND ";
389 ············}
390
391 ············string whereTS = string.Empty;
392 ············if (this.timeStampColumn != null)
393 ············{
394 ················if (provider.UseNamedParams)
395 ····················whereTS = " AND " + provider.GetQuotedName(timeStampColumn) + " = " + provider.GetNamedParameter("D_Original_" + timeStampColumn);
396 ················else
397 ····················whereTS = " AND " + provider.GetQuotedName(timeStampColumn) + " = ?";
398 ················provider.AddParameter(deleteCommand, provider.GetNamedParameter("D_Original_" + timeStampColumn), provider.GetDbType(typeof(Guid)), guidlength, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), timeStampColumn, System.Data.DataRowVersion.Original, null);
399 ············}
400
401 ············sql = string.Format(sql, qualifiedTableName, where + whereTS);
402 ············this.deleteCommand.CommandText = sql;
403 ········}
404
405 ········private void CollectFields()
406 ········{
407 ············FieldMap fm = new FieldMap(this.classMapping);
408 ············this.persistentFields = fm.PersistentFields;
409 ········}
410
411
412 ········/// <summary>
413 ········/// Gets or sets a value which determines, if database operations will be logged in a logging file.
414 ········/// </summary>
415 ········public bool VerboseMode
416 ········{
417 ············get { return this.verboseMode; }
418 ············set
419 ············{
420 ················this.verboseMode = value;
421 ················foreach(var mth in this.mappingTableHandlers.Values)
422 ················{
423 ····················mth.VerboseMode = value;
424 ················}
425 ············}
426 ········}
427 ········/// <summary>
428 ········/// Gets or sets the log adapter to determine the sink where log entries are written to.
429 ········/// </summary>
430 ········public ILogAdapter LogAdapter
431 ········{
432 ············get
433 ············{
434 ················return this.logAdapter;
435 ············}
436 ············set
437 ············{
438 ················this.logAdapter = value;
439 ················foreach(var mth in this.mappingTableHandlers.Values)
440 ················{
441 ····················mth.LogAdapter = value;
442 ················}
443 ············}
444 ········}
445
446 ········SqlColumnListGenerator CreateColumnListGenerator( Class cls )
447 ········{
448 ············var key = $"{nameof(SqlColumnListGenerator)}-{cls.FullName}";
449 ············return configContainer.ResolveOrRegisterType<SqlColumnListGenerator>( new ContainerControlledLifetimeManager(), key, new ParameterOverride( "cls", cls ) );
450 ········}
451
452 ········/// <summary>
453 ········/// Initializes the PersistenceHandler
454 ········/// </summary>
455 ········/// <param name="ndoMapping">Mapping information.</param>
456 ········/// <param name="t">Type for which the Handler is constructed.</param>
457 ········/// <param name="disposeCallback">Method to be called at the end of the usage. The method can be used to push back the object to the PersistenceHandlerPool.</param>
458 ········public void Initialize(NDOMapping ndoMapping, Type t, Action<Type,IPersistenceHandler> disposeCallback)
459 ········{
460 ············this.ndoMapping = ndoMapping;
461 ············this.classMapping = ndoMapping.FindClass(t);
462 ············this.timeStampColumn = classMapping.TimeStampColumn;
463 ············this.typeNameColumn = classMapping.TypeNameColumn;
464 ············this.hasAutoincrementedColumn = false;
465 ············foreach (OidColumn oidColumn in this.classMapping.Oid.OidColumns)
466 ············{
467 ················if (oidColumn.AutoIncremented)
468 ················{
469 ····················this.hasAutoincrementedColumn = true;
470 ····················this.autoIncrementColumn = oidColumn;
471 ····················break;
472 ················}
473 ············}
474 ············this.hasGuidOid = this.classMapping.HasGuidOid;
475 ············this.tableName = classMapping.TableName;
476 ············Connection connInfo = ndoMapping.FindConnection(classMapping);
477 ············this.provider = ndoMapping.GetProvider(connInfo);
478 ············this.qualifiedTableName = provider.GetQualifiedTableName( tableName );
479 ············// The connection object will be initialized by the pm, to
480 ············// enable connection string housekeeping.
481 ············// CheckTransaction is the place, where this happens.
482 ············this.conn = null;
483
484 ············var columnListGenerator = CreateColumnListGenerator( classMapping );····
485 ············this.hollowFields = columnListGenerator.HollowFields;
486 ············this.hollowFieldsWithAlias = columnListGenerator.HollowFieldsWithAlias;
487 ············this.namedParamList = columnListGenerator.ParamList;
488 ············this.fieldList = columnListGenerator.BaseList;
489 ············this.selectFieldList = columnListGenerator.SelectList;
490 ············this.selectFieldListWithAlias = columnListGenerator.SelectListWithAlias;
491 ············this.guidlength = provider.GetDefaultLength(typeof(Guid));
492 ············if (this.guidlength == 0)
493 ················this.guidlength = provider.SupportsNativeGuidType ? 16 : 36;
494 ············this.disposeCallback = disposeCallback;
495
496
497 ············this.selectCommand = provider.NewSqlCommand(conn);
498 ············this.insertCommand = provider.NewSqlCommand(conn);
499 ············this.updateCommand = provider.NewSqlCommand(conn);
500 ············this.deleteCommand = provider.NewSqlCommand(conn);
501 ············this.dataAdapter = provider.NewDataAdapter(selectCommand, updateCommand, insertCommand, deleteCommand);
502 ············this.type = t;
503
504 ············CollectFields();····// Alle Feldinformationen landen in persistentField
505 ············// determine the relations, which have a foreign key
506 ············// column in the table of the given class
507 ············relationInfos = new RelationCollector(this.classMapping)
508 ················.CollectRelations().ToList();
509
510
511 ············GenerateSelectCommand();
512 ············GenerateInsertCommand();
513 ············GenerateUpdateCommand();
514 ············GenerateDeleteCommand();
515 ········}
516 ····
517 ········#region Implementation of IPersistenceHandler
518
519 ········/// <summary>
520 ········/// Saves Changes to a DataTable
521 ········/// </summary>
522 ········/// <param name="dt"></param>
523 ········public void Update(DataTable dt)
524 ········{
525 ············DataRow[] rows = null;
526 ············try
527 ············{
528 ················rows = Select(dt, DataViewRowState.Added | DataViewRowState.ModifiedCurrent);
529 ················if (rows.Length == 0)
530 ····················return;
531 ················Dump(rows);
532 ················if (this.timeStampColumn != null)
533 ················{
534 ····················Guid newTs = Guid.NewGuid();
535 ····················foreach(DataRow r in rows)
536 ························r[timeStampColumn] = newTs;
537 ················}
538 ················dataAdapter.Update(rows);
539 ············}
540 catch ( System. Data. DBConcurrencyException dbex)
541 ············{
542 ················if (this.ConcurrencyError != null)
543 ················{
544 ····················// This is a Firebird Hack because Fb doesn't set the row
545 ····················if (dbex.Row == null)
546 ····················{
547 ························foreach(DataRow r in rows)
548 ························{
549 ····························if (r.RowState == DataRowState.Added ||
550 ································r.RowState == DataRowState.Modified)
551 ····························{
552 ································dbex.Row = r;
553 ································break;
554 ····························}
555 ························}
556 ····················}
557 ····················ConcurrencyError(dbex);
558 ················}
559 ················else
560 throw dbex;
561 ············}
562 catch ( System. Exception ex)
563 ············{
564 ················string text = "Exception of type " + ex.GetType().Name + " while updating or inserting data rows: " + ex.Message + "\n";
565 ················if ((ex.Message.IndexOf("Die Variable") > -1 && ex.Message.IndexOf("muss deklariert") > -1) || (ex.Message.IndexOf("Variable") > -1 && ex.Message.IndexOf("declared") > -1))
566 ····················text += "Check the field names in the mapping file.\n";
567 ················text += "Sql Update statement: " + updateCommand.CommandText + "\n";
568 ················text += "Sql Insert statement: " + insertCommand.CommandText;
569 ················throw new NDOException(37, text);
570 ············}
571 ········}
572
573
574 ········private void DumpBatch(string sql)
575 ········{
576 ············if (!this.verboseMode)
577 ················return;
578 ············this.logAdapter.Info("Batch: \r\n" + sql);
579 ········}
580
581 ········private void Dump(DataRow[] rows)
582 ········{
583 ············if (!this.verboseMode)
584 ················return;
585 ············new SqlDumper(this.logAdapter, this.provider, insertCommand, selectCommand, updateCommand, deleteCommand).Dump(rows);
586 ········}
587
588 ········DataRow[] Select(DataTable dt, DataViewRowState rowState)
589 ········{
590 ············// Mono Hack: Some rows in Mono are null after Select.
591 ············DataRow[] rows = dt.Select(null, null, rowState).Where(dr=>dr != null).ToArray();
592 ············return rows;
593 ········}
594
595 ········/// <summary>
596 ········/// Delets all rows of a DataTable marked as deleted
597 ········/// </summary>
598 ········/// <param name="dt"></param>
599 ········public void UpdateDeletedObjects(DataTable dt)
600 ········{
601 ············DataRow[] rows = Select(dt, DataViewRowState.Deleted);
602 ············if (rows.Length == 0) return;
603 ············Dump(rows);
604 ············try
605 ············{
606 ················dataAdapter.Update(rows);
607 ············}
608 catch ( System. Data. DBConcurrencyException dbex)
609 ············{
610 ················if (this.ConcurrencyError != null)
611 ····················ConcurrencyError(dbex);
612 ················else
613 throw dbex;
614 ············}
615 catch ( System. Exception ex)
616 ············{
617 ················string text = "Exception of type " + ex.GetType().Name + " while deleting data rows: " + ex.Message + "\n";
618 ················text += "Sql statement: " + deleteCommand.CommandText + "\n";
619 ················throw new NDOException(38, text);
620 ············}
621 ········}
622
623 ········private DataTable GetTemplateTable(DataSet templateDataset, string name)
624 ········{
625 ············// The instance of ds is actually static,
626 ············// since the SqlPersistenceHandler lives as
627 ············// a static instance in the PersistenceHandlerCache.
628 ············DataTable dt = templateDataset.Tables[name];
629 ············if (dt == null)
630 ················throw new NDOException(39, "Can't find table '" + name + "' in the schema. Check your mapping file.");
631 ············return dt;
632 ········}
633
634 ········/// <summary>
635 ········/// Executes a batch of sql statements.
636 ········/// </summary>
637 ········/// <param name="statements">Each element in the array is a sql statement.</param>
638 ········/// <param name="parameters">A list of parameters (see remarks).</param>
639 ········/// <returns>An List of Hashtables, containing the Name/Value pairs of the results.</returns>
640 ········/// <remarks>
641 ········/// For emty resultsets an empty Hashtable will be returned.
642 ········/// If parameters is a NDOParameterCollection, the parameters in the collection are valid for
643 ········/// all subqueries. If parameters is an ordinary IList, NDO expects to find a NDOParameterCollection
644 ········/// for each subquery. If an element is null, no parameters are submitted for the given query.
645 ········/// </remarks>
646 ········public IList<Dictionary<string, object>> ExecuteBatch( string[] statements, IList parameters )
647 ········{
648 ············List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();
649 ············bool closeIt = false;
650 ············IDataReader dr = null;
651 ············int i;
652 ············try
653 ············{
654 ················if (this.conn.State != ConnectionState.Open)
655 ················{
656 ····················closeIt = true;
657 ····················this.conn.Open();
658 ················}
659 ················string sql = string.Empty;
660
661 ················if (this.provider.SupportsBulkCommands)
662 ················{
663 ····················IDbCommand cmd = this.provider.NewSqlCommand( conn );
664 ····················sql = this.provider.GenerateBulkCommand( statements );
665 ····················cmd.CommandText = sql;
666 ····················if (parameters != null && parameters.Count > 0)
667 ····················{
668 ························// Only the first command gets parameters
669 ························for (i = 0; i < statements.Length; i++)
670 ························{
671 ····························if (i == 0)
672 ································CreateQueryParameters( cmd, parameters );
673 ····························else
674 ································CreateQueryParameters( null, null );
675 ························}
676 ····················}
677
678 ····················// cmd.CommandText can be changed in CreateQueryParameters
679 ····················DumpBatch( cmd.CommandText );
680 ····················if (this.transaction != null)
681 ························cmd.Transaction = this.transaction;
682
683 ····················dr = cmd.ExecuteReader();
684
685 ····················for (; ; )
686 ····················{
687 ························var dict = new Dictionary<string, object>();
688 ························while (dr.Read())
689 ························{
690 ····························for (i = 0; i < dr.FieldCount; i++)
691 ····························{
692 ································dict.Add( dr.GetName( i ), dr.GetValue( i ) );
693 ····························}
694 ························}
695 ························result.Add( dict );
696 ························if (!dr.NextResult())
697 ····························break;
698 ····················}
699
700 ····················dr.Close();
701 ················}
702 ················else
703 ················{
704 ····················for (i = 0; i < statements.Length; i++)
705 ····················{
706 ························string s = statements[i];
707 ························sql += s + ";\n"; // For DumpBatch only
708 ························var dict = new Dictionary<string, object>();
709 ························IDbCommand cmd = this.provider.NewSqlCommand( conn );
710
711 ························cmd.CommandText = s;
712 ························if (parameters != null && parameters.Count > 0)
713 ························{
714 ····························CreateQueryParameters( cmd, parameters );
715 ························}
716
717 ························if (this.transaction != null)
718 ····························cmd.Transaction = this.transaction;
719
720 ························dr = cmd.ExecuteReader();
721
722 ························while (dr.Read())
723 ························{
724 ····························for (int j = 0; j < dr.FieldCount; j++)
725 ····························{
726 ································dict.Add( dr.GetName( j ), dr.GetValue( j ) );
727 ····························}
728 ························}
729
730 ························dr.Close();
731 ························result.Add( dict );
732 ····················}
733
734 ····················DumpBatch( sql );
735 ················}
736 ············}
737 ············finally
738 ············{
739 ················if (dr != null && !dr.IsClosed)
740 ····················dr.Close();
741 ················if (closeIt)
742 ····················this.conn.Close();
743 ············}
744
745 ············return result;
746 ········}
747
748 ········private void CreateQueryParameters(IDbCommand command, IList parameters)
749 ········{
750 ············if (parameters == null || parameters.Count == 0)
751 ················return;
752
753 ············string sql = command.CommandText;
754
755 ············Regex regex = new Regex( @"\{(\d+)\}" );
756
757 ············MatchCollection matches = regex.Matches( sql );
758 ············Dictionary<string, object> tcValues = new Dictionary<string, object>();
759 ············int endIndex = parameters.Count - 1;
760 ············foreach (Match match in matches)
761 ············{
762 ················int nr = int.Parse( match.Groups[1].Value );
763 ················if (nr > endIndex)
764 ····················throw new QueryException( 10009, "Parameter-Reference " + match.Value + " has no matching parameter." );
765
766 ················sql = sql.Replace( match.Value,
767 ····················this.provider.GetNamedParameter( "p" + nr.ToString() ) );
768 ············}
769
770 ············command.CommandText = sql;
771
772 ············for (int i = 0; i < parameters.Count; i++)
773 ············{
774 ················object p = parameters[i];
775 ················if (p == null)
776 ····················p = DBNull.Value;
777 ················Type type = p.GetType();
778 ················if (type.FullName.StartsWith("System.Nullable`1"))
779 ····················type = type.GetGenericArguments()[0];
780 ················if (type == typeof( Guid ) && Guid.Empty.Equals( p ) || type == typeof( DateTime ) && DateTime.MinValue.Equals( p ))
781 ················{
782 ····················p = DBNull.Value;
783 ················}
784 ················if (type.IsEnum)
785 ················{
786 ····················type = Enum.GetUnderlyingType(type);
787 ····················p = ((IConvertible)p ).ToType(type, CultureInfo.CurrentCulture);
788 ················}
789 ················else if (type == typeof(Guid) && !provider.SupportsNativeGuidType)
790 ················{
791 ····················type = typeof(string);
792 ····················if (p != DBNull.Value)
793 ························p = p.ToString();
794 ················}
795 ················string name = "p" + i.ToString();
796 ················int length = this.provider.GetDefaultLength(type);
797 ················if (type == typeof(string))
798 ················{
799 ····················length = ((string)p).Length;
800 ····················if (provider.GetType().Name.IndexOf("Oracle") > -1)
801 ····················{
802 ························if (length == 0)
803 ····························throw new QueryException(10001, "Empty string parameters are not allowed in Oracle. Use IS NULL instead.");
804 ····················}
805 ················}
806 ················else if (type == typeof(byte[]))
807 ················{
808 ····················length = ((byte[])p).Length;
809 ················}
810 ················IDataParameter par = provider.AddParameter(
811 ····················command,
812 ····················this.provider.GetNamedParameter(name),
813 ····················this.provider.GetDbType( type == typeof( DBNull ) ? typeof( string ) : type ),
814 ····················length,
815 ····················this.provider.GetQuotedName(name));
816 ················par.Value = p;
817 ················par.Direction = ParameterDirection.Input;····················
818 ············}
819 ········}
820
821 ········/// <summary>
822 ········/// Performs a query and returns a DataTable
823 ········/// </summary>
824 ········/// <param name="sql"></param>
825 ········/// <param name="parameters"></param>
826 ········/// <param name="templateDataSet"></param>
827 ········/// <returns></returns>
828 ········public DataTable PerformQuery( string sql, IList parameters, DataSet templateDataSet )
829 ········{
830 ············if (sql.Trim().StartsWith( "EXEC", StringComparison.InvariantCultureIgnoreCase ))
831 ················this.selectCommand.CommandType = CommandType.StoredProcedure;
832 ············else
833 ················this.selectCommand.CommandType = CommandType.Text;
834
835 ············DataTable table = GetTemplateTable(templateDataSet, this.tableName).Clone();
836
837 ············this.selectCommand.CommandText = sql;
838
839 ············CreateQueryParameters(this.selectCommand, parameters);
840
841 ············Dump(null); // Dumps the Select Command
842
843 ············try
844 ············{
845 ················dataAdapter.Fill(table);
846 ············}
847 ············catch (System.Exception ex)
848 ············{
849 ················string text = "Exception of type " + ex.GetType().Name + " while executing a Query: " + ex.Message + "\n";
850 ················text += "Sql Statement: " + sql + "\n";
851 ················throw new NDOException(40, text);
852 ············}
853
854 ············return table;········
855 ········}
856
857 ········/// <summary>
858 ········/// Gets a Handler which can store data in relation tables
859 ········/// </summary>
860 ········/// <param name="r">Relation information</param>
861 ········/// <returns>The handler</returns>
862 ········public IMappingTableHandler GetMappingTableHandler(Relation r)
863 ········{
864 ············IMappingTableHandler handler;
865 ············if (!mappingTableHandlers.TryGetValue( r.FieldName, out handler ))
866 ············{
867 ················handler = new NDOMappingTableHandler();
868 ················handler.Initialize(ndoMapping, r);
869 ················handler.VerboseMode = this.verboseMode;
870 ················handler.LogAdapter = this.logAdapter;
871 ················mappingTableHandlers[r.FieldName] = handler;
872 ············}
873 ············return handler;
874 ········}
875
876 ········/// <summary>
877 ········/// Disposes a SqlPersistenceHandler
878 ········/// </summary>
879 ········public void Dispose()
880 ········{
881 ············this.disposeCallback( this.type, this );
882 ········}
883
884 ········/// <summary>
885 ········/// Gets or sets the connection to be used for the handler
886 ········/// </summary>
887 ········public IDbConnection Connection
888 ········{
889 ············get { return this.conn; }
890 ············set
891 ············{
892 ················this.conn = value;
893 ················this.selectCommand.Connection = value;
894 ················this.deleteCommand.Connection = value;
895 ················this.updateCommand.Connection = value;
896 ················this.insertCommand.Connection = value;
897 ············}
898 ········}
899
900 ········/// <summary>
901 ········/// Gets or sets the connection to be used for the handler
902 ········/// </summary>
903 ········public IDbTransaction Transaction
904 ········{
905 ············get { return this.transaction; }
906 ············set
907 ············{
908 ················this.transaction = value;
909 ················this.selectCommand.Transaction = value;
910 ················this.deleteCommand.Transaction = value;
911 ················this.updateCommand.Transaction = value;
912 ················this.insertCommand.Transaction = value;
913 ············}
914 ········}
915
916
917 ········/// <summary>
918 ········/// Gets the current DataAdapter.
919 ········/// </summary>
920 ········/// <remarks>
921 ········/// This is needed by RegisterRowUpdateHandler.
922 ········/// See the comment in SqlCeProvider.
923 ········/// </remarks>
924 ········public DbDataAdapter DataAdapter => this.dataAdapter;
925
926 ········#endregion
927 ····}
928 }
929
New Commit (add8490)
1 //
2 // Copyright (c) 2002-2016 Mirko Matytschak
3 // (www.netdataobjects.de)
4 //
5 // Author: Mirko Matytschak
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
10 // Software, and to permit persons to whom the Software is furnished to do so, subject to the following
11 // conditions:
12
13 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
14 // of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21
22
23 using System;
24 using System.Text;
25 using System.Text.RegularExpressions;
26 using System.Diagnostics;
27 using System.IO;
28 using System.Reflection;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Linq;
32 using System.Data;
33 using System.Data.Common;
34 using NDO;
35 using NDO.Mapping;
36 using NDO.Logging;
37 using NDOInterfaces;
38 using NDO.Query;
39 using System.Globalization;
40 using NDO.Configuration;
41
42 namespace NDO.SqlPersistenceHandling
43 {
44 ····/// <summary>
45 ····/// Parameter type for the IProvider function RegisterRowUpdateHandler
46 ····/// </summary>
47 ····public delegate void RowUpdateHandler(DataRow row);
48
49 ····/// <summary>
50 ····/// Summary description for NDOPersistenceHandler.
51 ····/// </summary>
52 ····///
53 ····public class SqlPersistenceHandler : IPersistenceHandler
54 ····{
55 ········/// <summary>
56 ········/// This event will be triggered, if a concurrency error occurs
57 ········/// </summary>
58 ········public event ConcurrencyErrorHandler ConcurrencyError;
59
60 ········private IDbCommand selectCommand;
61 ········private IDbCommand insertCommand;
62 ········private IDbCommand updateCommand;
63 ········private IDbCommand deleteCommand;
64 ········private IDbConnection conn;
65 ········private IDbTransaction transaction;
66 ········private DbDataAdapter dataAdapter;
67 ········private Class classMapping;
68 ········private string selectFieldList;
69 ········private string selectFieldListWithAlias;
70 ········private string tableName;
71 ········private string qualifiedTableName;
72 ········private bool verboseMode;
73 ········private ILogAdapter logAdapter;
74 ········private Dictionary<string, IMappingTableHandler> mappingTableHandlers = new Dictionary<string, IMappingTableHandler>();
75 ········private IProvider provider;
76 ········private NDOMapping ndoMapping;
77 ········private string timeStampColumn = null;
78 ········private Column typeNameColumn = null;
79 ········private bool hasAutoincrementedColumn;
80 ········private OidColumn autoIncrementColumn;
81 ········private Dictionary<string,MemberInfo> persistentFields;
82 ········private List<RelationFieldInfo> relationInfos;
83 ········private Type type;
84 ········private int guidlength;
85 ········private string hollowFields;
86 ········private string hollowFieldsWithAlias;
87 ········private string fieldList;
88 ········private string namedParamList;
89 ········private bool hasGuidOid;
90 ········private readonly INDOContainer configContainer;
91 ········private Action<Type,IPersistenceHandler> disposeCallback;
92
93 ········/// <summary>
94 ········/// Constructs a SqlPersistenceHandler object
95 ········/// </summary>
96 ········/// <param name="configContainer"></param>
97 ········public SqlPersistenceHandler(INDOContainer configContainer)
98 ········{
99 ············this.configContainer = configContainer;
100 ········}
101
102 ········private void GenerateSelectCommand()
103 ········{
104 ············this.selectCommand.CommandText = string.Empty;
105 ········}
106
107 ········private int ParameterLength(Mapping.Field fieldMapping, Type memberType)
108 ········{
109 ············if (0 == fieldMapping.Column.Size)
110 ················return provider.GetDefaultLength(memberType);
111 ············else
112 ················return fieldMapping.Column.Size;
113 ········}
114
115 ········private void GenerateInsertCommand()
116 ········{
117 ············// Generate Parameters
118 ············foreach (OidColumn oidColumn in this.classMapping.Oid.OidColumns)
119 ············{
120 ················if (!oidColumn.AutoIncremented && oidColumn.FieldName == null && oidColumn.RelationName == null)
121 ················{
122 ····················provider.AddParameter( insertCommand, provider.GetNamedParameter( oidColumn.Name ), provider.GetDbType( oidColumn.SystemType ), provider.GetDefaultLength( oidColumn.SystemType ), oidColumn.Name );
123 ················}
124 ············}
125
126 ············foreach (var e in this.persistentFields)
127 ············{
128 ················Type memberType;
129 ················if (e.Value is FieldInfo)
130 ····················memberType = ((FieldInfo)e.Value).FieldType;
131 ················else
132 ····················memberType = ((PropertyInfo)e.Value).PropertyType;
133
134 ················var fieldMapping = classMapping.FindField( (string)e.Key );
135
136 ················// Ignore fields without mapping.
137 ················if (null == fieldMapping)
138 ····················continue;
139
140 ················if (null == fieldMapping.Column.DbType)
141 ················{
142 ····················fieldMapping.ColumnDbType = (int)provider.GetDbType( memberType );
143 ················}
144 ················else
145 ················{
146 ····················fieldMapping.ColumnDbType = (int)provider.GetDbType( fieldMapping.Column.DbType );
147 ················}
148
149 ················provider.AddParameter( insertCommand, provider.GetNamedParameter( fieldMapping.Column.Name ), fieldMapping.ColumnDbType, ParameterLength( fieldMapping, memberType ), fieldMapping.Column.Name );
150 ············}
151
152 ············foreach (RelationFieldInfo ri in relationInfos)
153 ············{
154 ················Relation r = ri.Rel;
155 ················foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
156 ················{
157 ····················provider.AddParameter( insertCommand, provider.GetNamedParameter( fkColumn.Name ), provider.GetDbType( fkColumn.SystemType ), provider.GetDefaultLength( fkColumn.SystemType ), fkColumn.Name );
158 ················}
159 ················if (r.ForeignKeyTypeColumnName != null)
160 ················{
161 ····················provider.AddParameter( insertCommand, provider.GetNamedParameter( r.ForeignKeyTypeColumnName ), provider.GetDbType( typeof( int ) ), provider.GetDefaultLength( typeof( int ) ), r.ForeignKeyTypeColumnName );
162 ················}
163
164 ············}
165
166 ············if (this.timeStampColumn != null)
167 ············{
168 ················provider.AddParameter( insertCommand, provider.GetNamedParameter( timeStampColumn ), provider.GetDbType( typeof( Guid ) ), guidlength, this.timeStampColumn );
169 ············}
170
171 ············if (this.typeNameColumn != null)
172 ············{
173 ················Type tncType = Type.GetType( this.typeNameColumn.NetType );
174 ················provider.AddParameter( insertCommand, provider.GetNamedParameter( typeNameColumn.Name ), provider.GetDbType( tncType ), provider.GetDefaultLength( tncType ), this.typeNameColumn.Name );
175 ············}
176
177 ············string sql;
178 ············//{0} = TableName: Mitarbeiter············
179 ············//{1} = FieldList: vorname, nachname
180 ············//{2} = NamedParamList mit @: @vorname, @nachname
181 ············//{3} = FieldList mit Id: id, vorname, nachname
182 ············//{4} = Name der Id-Spalte
183 ············if (hasAutoincrementedColumn && provider.SupportsLastInsertedId && provider.SupportsInsertBatch)
184 ············{
185 ················sql = "INSERT INTO {0} ({1}) VALUES ({2}); SELECT {3} FROM {0} WHERE ({4} = " + provider.GetLastInsertedId(this.tableName, this.autoIncrementColumn.Name) + ")";
186 ················sql = string.Format(sql, qualifiedTableName, this.fieldList, this.namedParamList, selectFieldList, this.autoIncrementColumn.Name);
187 ················this.insertCommand.UpdatedRowSource = UpdateRowSource.FirstReturnedRecord;
188 ············}
189 ············else
190 ············{
191 ················sql = "INSERT INTO {0} ({1}) VALUES ({2})";
192 ················sql = string.Format(sql, qualifiedTableName, this.fieldList, this.namedParamList);
193 ············}
194 ············if (hasAutoincrementedColumn && !provider.SupportsInsertBatch)
195 ············{
196 ················if (provider.SupportsLastInsertedId)
197 ····················provider.RegisterRowUpdateHandler(this);
198 ················else
199 ····················throw new NDOException(32, "The provider of type " + provider.GetType().FullName + " doesn't support Autonumbered Ids. Use Self generated Ids instead.");
200 ············}
201 ············this.insertCommand.CommandText = sql;
202 ············this.insertCommand.Connection = this.conn;
203 ········}
204
205 ········/// <summary>
206 ········/// Row update handler for providers that require Row Update Handling
207 ········/// </summary>
208 ········/// <param name="row"></param>
209 ········public void OnRowUpdate(DataRow row)
210 ········{
211 ············if (row.RowState == DataRowState.Deleted)
212 ················return;
213
214 ············if (!hasAutoincrementedColumn)
215 ················return;
216 ············
217 ············string oidColumnName = this.autoIncrementColumn.Name;
218 ············Type t = row[oidColumnName].GetType();
219 ············if (t != typeof(int))
220 ················return;
221 ············
222 ············// Ist schon eine ID vergeben?
223 ············if (((int)row[oidColumnName]) > 0)
224 ················return;
225 ············bool unchanged = (row.RowState == DataRowState.Unchanged);
226 ············IDbCommand cmd = provider.NewSqlCommand(this.conn);
227
228 ············cmd.CommandText = provider.GetLastInsertedId(this.tableName, this.autoIncrementColumn.Name);
229 ············DumpBatch(cmd.CommandText);
230
231 ············using (IDataReader reader = cmd.ExecuteReader())
232 ············{
233 ················if (reader.Read())
234 ················{
235 ····················object oidValue = reader.GetValue(0);
236 ····················if ( this.verboseMode )
237 ····················{
238 ························if ( oidValue == DBNull.Value )
239 ····························LogAdapter.Info( oidColumnName + " = DbNull" );
240 ························else
241 ····························LogAdapter.Info( oidColumnName + " = " + oidValue );
242 ····················}
243 ····················row[oidColumnName] = oidValue;
244 ····················if (unchanged)
245 ························row.AcceptChanges();
246 ················}
247 ················else
248 ····················throw new NDOException(33, "Can't read autonumbered id from the database.");
249 ············}
250 ········}
251
252 ········private void GenerateUpdateCommand()
253 ········{
254 ············string sql;
255
256 ············NDO.Mapping.Field fieldMapping;
257
258 ············sql = @"UPDATE {0} SET {1} WHERE ({2})";
259
260 ········
261 ············//{0} = Tabellenname: Mitarbeiter
262 ············//{1} = Zuweisungsliste: vorname = @vorname, nachname = @nachname
263 ············//{2} = Where-Bedingung: id = @Original_id [ AND TimeStamp = @Original_timestamp ]
264 ············AssignmentGenerator assignmentGenerator = new AssignmentGenerator(this.classMapping);
265 ············string zuwListe = assignmentGenerator.Result;
266
267 ············foreach (var e in this.persistentFields)
268 ············{
269 ················Type memberType;
270 ················if (e.Value is FieldInfo)
271 ····················memberType = ((FieldInfo)e.Value).FieldType;
272 ················else
273 ····················memberType = ((PropertyInfo)e.Value).PropertyType;
274
275 ················fieldMapping = classMapping.FindField( (string)e.Key );
276 ················if (fieldMapping != null)
277 ················{
278 ····················provider.AddParameter( updateCommand, provider.GetNamedParameter( "U_" + fieldMapping.Column.Name ), fieldMapping.ColumnDbType, ParameterLength( fieldMapping, memberType ), fieldMapping.Column.Name );
279 ················}
280 ············}
281
282 ············foreach (RelationFieldInfo ri in relationInfos)
283 ············{
284 ················Relation r = ri.Rel;
285 ················if (r.Multiplicity == RelationMultiplicity.Element && r.MappingTable == null
286 ····················|| r.Multiplicity == RelationMultiplicity.List && r.MappingTable == null && r.Parent.FullName != classMapping.FullName)
287 ················{
288 ····················foreach (ForeignKeyColumn fkColumn in r.ForeignKeyColumns)
289 ····················{
290 ························Type systemType = fkColumn.SystemType;
291 ························provider.AddParameter( updateCommand, provider.GetNamedParameter( "U_" + fkColumn.Name ), provider.GetDbType( systemType ), provider.GetDefaultLength( systemType ), fkColumn.Name );
292 ····················}
293 ····················if (r.ForeignKeyTypeColumnName != null)
294 ····················{
295 ························provider.AddParameter( updateCommand, provider.GetNamedParameter( "U_" + r.ForeignKeyTypeColumnName ), provider.GetDbType( typeof( int ) ), provider.GetDefaultLength( typeof( int ) ), r.ForeignKeyTypeColumnName );
296 ····················}
297 ················}
298 ············}
299
300 ············string where = string.Empty;
301
302 ············if (this.timeStampColumn != null)
303 ············{
304 ················if (provider.UseNamedParams)
305 ····················where += provider.GetQuotedName(timeStampColumn) + " = " + provider.GetNamedParameter("U_Original_" + timeStampColumn) + " AND ";
306 ················else
307 ····················where += provider.GetQuotedName(timeStampColumn) + " = ? AND ";
308 ················// The new timestamp value as parameter
309 ················provider.AddParameter(updateCommand, provider.GetNamedParameter("U_" + timeStampColumn), provider.GetDbType(typeof(Guid)), guidlength, timeStampColumn);
310 ················provider.AddParameter(updateCommand, provider.GetNamedParameter("U_Original_" + timeStampColumn), provider.GetDbType(typeof(Guid)), guidlength, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), timeStampColumn, System.Data.DataRowVersion.Original, null);
311 ············}
312
313 ············int oidCount = classMapping.Oid.OidColumns.Count;
314 ············for (int i = 0; i < oidCount; i++)
315 ············{
316 ················OidColumn oidColumn = (OidColumn)classMapping.Oid.OidColumns[i];
317 ················// Oid as parameter
318 ················provider.AddParameter(updateCommand, provider.GetNamedParameter("U_Original_" + oidColumn.Name), provider.GetDbType(oidColumn.SystemType), oidColumn.TypeLength, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), oidColumn.Name, System.Data.DataRowVersion.Original, null);
319 ················if (provider.UseNamedParams)
320 ····················where += provider.GetQuotedName(oidColumn.Name) + " = " + provider.GetNamedParameter("U_Original_" + oidColumn.Name);
321 ················else
322 ····················where += provider.GetQuotedName(oidColumn.Name) + " = ?";
323
324 ················Relation r = oidColumn.Relation;
325 ················if (!this.hasGuidOid && r != null && r.ForeignKeyTypeColumnName != null)
326 ················{
327 ····················where += " AND " +
328 ························provider.GetQuotedName(r.ForeignKeyTypeColumnName) + " = " + provider.GetNamedParameter("U_Original_" + r.ForeignKeyTypeColumnName);
329 ····················provider.AddParameter(updateCommand, provider.GetNamedParameter("U_Original_" + r.ForeignKeyTypeColumnName), provider.GetDbType(typeof(int)), provider.GetDefaultLength(typeof(int)), System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), r.ForeignKeyTypeColumnName, System.Data.DataRowVersion.Original, null);
330 ················}
331
332 ················if (i < oidCount - 1)
333 ····················where += " AND ";
334 ············}
335 ············//else
336 ············//{
337 ············//····// Dual oids are defined using two relations.
338 ············//····MultiKeyHandler dkh = new MultiKeyHandler(this.classMapping);
339 ················
340 ············//····for (int i = 0; i < 2; i++)
341 ············//····{
342 ············//········where += provider.GetQuotedName(dkh.ForeignKeyColumnName(i)) + " = " + provider.GetNamedParameter("U_Original_" + dkh.ForeignKeyColumnName(i));
343 ············//········provider.AddParameter(updateCommand, provider.GetNamedParameter("U_Original_" + dkh.ForeignKeyColumnName(i)), provider.GetDbType(dkh.GetClass(i).Oid.FieldType), dkh.GetClass(i).Oid.TypeLength, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), dkh.ForeignKeyColumnName(i), System.Data.DataRowVersion.Original, null);
344 ············//········if (dkh.ForeignKeyTypeColumnName(i) != null && dkh.GetClass(i).Oid.FieldType != typeof(Guid))
345 ············//········{
346 ············//············where += " AND " +
347 ············//················provider.GetQuotedName(dkh.ForeignKeyTypeColumnName(i)) + " = " + provider.GetNamedParameter("U_Original_" + dkh.ForeignKeyTypeColumnName(i));
348 ············//············provider.AddParameter(updateCommand, provider.GetNamedParameter("U_Original_" + dkh.ForeignKeyTypeColumnName(i)), provider.GetDbType(typeof(int)), provider.GetDefaultLength(typeof(int)), System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), dkh.ForeignKeyTypeColumnName(i), System.Data.DataRowVersion.Original, null);
349 ············//········}
350 ············//········if (i == 0)
351 ············//············where += " AND ";
352 ············//····}
353 ············//}
354
355 ············sql = string.Format(sql, qualifiedTableName, zuwListe, where);
356 ············//Console.WriteLine(sql);
357 ············this.updateCommand.CommandText = sql;
358 ········}
359
360
361 ········private void GenerateDeleteCommand()
362 ········{
363 ············string sql = "DELETE FROM {0} WHERE ({1})";
364 ············//{0} = Tabellenname: Mitarbeiter
365 ············//{1} = Where-Bedingung: id = @Original_id
366
367 ············string where = string.Empty;
368
369 ············int oidCount = this.classMapping.Oid.OidColumns.Count;
370 ············for(int i = 0; i < oidCount; i++)
371 ············{
372 ················OidColumn oidColumn = (OidColumn)this.classMapping.Oid.OidColumns[i];
373 ················if (provider.UseNamedParams)
374 ····················where += provider.GetQuotedName(oidColumn.Name) + " = " + provider.GetNamedParameter("D_Original_" + oidColumn.Name);
375 ················else
376 ····················where += provider.GetQuotedName(oidColumn.Name) + " = ?";
377 ················provider.AddParameter(deleteCommand, provider.GetNamedParameter("D_Original_" + oidColumn.Name), provider.GetDbType(oidColumn.SystemType), oidColumn.TypeLength, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), oidColumn.Name, System.Data.DataRowVersion.Original, null);
378
379 ················Relation r = oidColumn.Relation;
380 ················if (!this.hasGuidOid && r != null && r.ForeignKeyTypeColumnName != null)
381 ················{
382 ····················where += " AND " +
383 ························provider.GetQuotedName(r.ForeignKeyTypeColumnName) + " = " + provider.GetNamedParameter("D_Original_" + r.ForeignKeyTypeColumnName);
384 ····················provider.AddParameter(updateCommand, provider.GetNamedParameter("D_Original_" + r.ForeignKeyTypeColumnName), provider.GetDbType(typeof(int)), provider.GetDefaultLength(typeof(int)), System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), r.ForeignKeyTypeColumnName, System.Data.DataRowVersion.Original, null);
385 ················}
386
387 ················if (i < oidCount - 1)
388 ····················where += " AND ";
389 ············}
390
391 ············string whereTS = string.Empty;
392 ············if (this.timeStampColumn != null)
393 ············{
394 ················if (provider.UseNamedParams)
395 ····················whereTS = " AND " + provider.GetQuotedName(timeStampColumn) + " = " + provider.GetNamedParameter("D_Original_" + timeStampColumn);
396 ················else
397 ····················whereTS = " AND " + provider.GetQuotedName(timeStampColumn) + " = ?";
398 ················provider.AddParameter(deleteCommand, provider.GetNamedParameter("D_Original_" + timeStampColumn), provider.GetDbType(typeof(Guid)), guidlength, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), timeStampColumn, System.Data.DataRowVersion.Original, null);
399 ············}
400
401 ············sql = string.Format(sql, qualifiedTableName, where + whereTS);
402 ············this.deleteCommand.CommandText = sql;
403 ········}
404
405 ········private void CollectFields()
406 ········{
407 ············FieldMap fm = new FieldMap(this.classMapping);
408 ············this.persistentFields = fm.PersistentFields;
409 ········}
410
411
412 ········/// <summary>
413 ········/// Gets or sets a value which determines, if database operations will be logged in a logging file.
414 ········/// </summary>
415 ········public bool VerboseMode
416 ········{
417 ············get { return this.verboseMode; }
418 ············set
419 ············{
420 ················this.verboseMode = value;
421 ················foreach(var mth in this.mappingTableHandlers.Values)
422 ················{
423 ····················mth.VerboseMode = value;
424 ················}
425 ············}
426 ········}
427 ········/// <summary>
428 ········/// Gets or sets the log adapter to determine the sink where log entries are written to.
429 ········/// </summary>
430 ········public ILogAdapter LogAdapter
431 ········{
432 ············get
433 ············{
434 ················return this.logAdapter;
435 ············}
436 ············set
437 ············{
438 ················this.logAdapter = value;
439 ················foreach(var mth in this.mappingTableHandlers.Values)
440 ················{
441 ····················mth.LogAdapter = value;
442 ················}
443 ············}
444 ········}
445
446 ········SqlColumnListGenerator CreateColumnListGenerator( Class cls )
447 ········{
448 ············var key = $"{nameof(SqlColumnListGenerator)}-{cls.FullName}";
449 ············return configContainer.ResolveOrRegisterType<SqlColumnListGenerator>( new ContainerControlledLifetimeManager(), key, new ParameterOverride( "cls", cls ) );
450 ········}
451
452 ········/// <summary>
453 ········/// Initializes the PersistenceHandler
454 ········/// </summary>
455 ········/// <param name="ndoMapping">Mapping information.</param>
456 ········/// <param name="t">Type for which the Handler is constructed.</param>
457 ········/// <param name="disposeCallback">Method to be called at the end of the usage. The method can be used to push back the object to the PersistenceHandlerPool.</param>
458 ········public void Initialize(NDOMapping ndoMapping, Type t, Action<Type,IPersistenceHandler> disposeCallback)
459 ········{
460 ············this.ndoMapping = ndoMapping;
461 ············this.classMapping = ndoMapping.FindClass(t);
462 ············this.timeStampColumn = classMapping.TimeStampColumn;
463 ············this.typeNameColumn = classMapping.TypeNameColumn;
464 ············this.hasAutoincrementedColumn = false;
465 ············foreach (OidColumn oidColumn in this.classMapping.Oid.OidColumns)
466 ············{
467 ················if (oidColumn.AutoIncremented)
468 ················{
469 ····················this.hasAutoincrementedColumn = true;
470 ····················this.autoIncrementColumn = oidColumn;
471 ····················break;
472 ················}
473 ············}
474 ············this.hasGuidOid = this.classMapping.HasGuidOid;
475 ············this.tableName = classMapping.TableName;
476 ············Connection connInfo = ndoMapping.FindConnection(classMapping);
477 ············this.provider = ndoMapping.GetProvider(connInfo);
478 ············this.qualifiedTableName = provider.GetQualifiedTableName( tableName );
479 ············// The connection object will be initialized by the pm, to
480 ············// enable connection string housekeeping.
481 ············// CheckTransaction is the place, where this happens.
482 ············this.conn = null;
483
484 ············var columnListGenerator = CreateColumnListGenerator( classMapping );····
485 ············this.hollowFields = columnListGenerator.HollowFields;
486 ············this.hollowFieldsWithAlias = columnListGenerator.HollowFieldsWithAlias;
487 ············this.namedParamList = columnListGenerator.ParamList;
488 ············this.fieldList = columnListGenerator.BaseList;
489 ············this.selectFieldList = columnListGenerator.SelectList;
490 ············this.selectFieldListWithAlias = columnListGenerator.SelectListWithAlias;
491 ············this.guidlength = provider.GetDefaultLength(typeof(Guid));
492 ············if (this.guidlength == 0)
493 ················this.guidlength = provider.SupportsNativeGuidType ? 16 : 36;
494 ············this.disposeCallback = disposeCallback;
495
496
497 ············this.selectCommand = provider.NewSqlCommand(conn);
498 ············this.insertCommand = provider.NewSqlCommand(conn);
499 ············this.updateCommand = provider.NewSqlCommand(conn);
500 ············this.deleteCommand = provider.NewSqlCommand(conn);
501 ············this.dataAdapter = provider.NewDataAdapter(selectCommand, updateCommand, insertCommand, deleteCommand);
502 ············this.type = t;
503
504 ············CollectFields();····// Alle Feldinformationen landen in persistentField
505 ············// determine the relations, which have a foreign key
506 ············// column in the table of the given class
507 ············relationInfos = new RelationCollector(this.classMapping)
508 ················.CollectRelations().ToList();
509
510
511 ············GenerateSelectCommand();
512 ············GenerateInsertCommand();
513 ············GenerateUpdateCommand();
514 ············GenerateDeleteCommand();
515 ········}
516 ····
517 ········#region Implementation of IPersistenceHandler
518
519 ········/// <summary>
520 ········/// Saves Changes to a DataTable
521 ········/// </summary>
522 ········/// <param name="dt"></param>
523 ········public void Update(DataTable dt)
524 ········{
525 ············DataRow[] rows = null;
526 ············try
527 ············{
528 ················rows = Select(dt, DataViewRowState.Added | DataViewRowState.ModifiedCurrent);
529 ················if (rows.Length == 0)
530 ····················return;
531 ················Dump(rows);
532 ················if (this.timeStampColumn != null)
533 ················{
534 ····················Guid newTs = Guid.NewGuid();
535 ····················foreach(DataRow r in rows)
536 ························r[timeStampColumn] = newTs;
537 ················}
538 ················dataAdapter.Update(rows);
539 ············}
540 catch ( DBConcurrencyException dbex)
541 ············{
542 ················if (this.ConcurrencyError != null)
543 ················{
544 ····················// This is a Firebird Hack because Fb doesn't set the row
545 ····················if (dbex.Row == null)
546 ····················{
547 ························foreach(DataRow r in rows)
548 ························{
549 ····························if (r.RowState == DataRowState.Added ||
550 ································r.RowState == DataRowState.Modified)
551 ····························{
552 ································dbex.Row = r;
553 ································break;
554 ····························}
555 ························}
556 ····················}
557 ····················ConcurrencyError(dbex);
558 ················}
559 ················else
560 throw;
561 ············}
562 catch ( Exception ex)
563 ············{
564 ················string text = "Exception of type " + ex.GetType().Name + " while updating or inserting data rows: " + ex.Message + "\n";
565 ················if ((ex.Message.IndexOf("Die Variable") > -1 && ex.Message.IndexOf("muss deklariert") > -1) || (ex.Message.IndexOf("Variable") > -1 && ex.Message.IndexOf("declared") > -1))
566 ····················text += "Check the field names in the mapping file.\n";
567 ················text += "Sql Update statement: " + updateCommand.CommandText + "\n";
568 ················text += "Sql Insert statement: " + insertCommand.CommandText;
569 ················throw new NDOException(37, text);
570 ············}
571 ········}
572
573
574 ········private void DumpBatch(string sql)
575 ········{
576 ············if (!this.verboseMode)
577 ················return;
578 ············this.logAdapter.Info("Batch: \r\n" + sql);
579 ········}
580
581 ········private void Dump(DataRow[] rows)
582 ········{
583 ············if (!this.verboseMode)
584 ················return;
585 ············new SqlDumper(this.logAdapter, this.provider, insertCommand, selectCommand, updateCommand, deleteCommand).Dump(rows);
586 ········}
587
588 ········DataRow[] Select(DataTable dt, DataViewRowState rowState)
589 ········{
590 ············// Mono Hack: Some rows in Mono are null after Select.
591 ············DataRow[] rows = dt.Select(null, null, rowState).Where(dr=>dr != null).ToArray();
592 ············return rows;
593 ········}
594
595 ········/// <summary>
596 ········/// Delets all rows of a DataTable marked as deleted
597 ········/// </summary>
598 ········/// <param name="dt"></param>
599 ········public void UpdateDeletedObjects(DataTable dt)
600 ········{
601 ············DataRow[] rows = Select(dt, DataViewRowState.Deleted);
602 ············if (rows.Length == 0) return;
603 ············Dump(rows);
604 ············try
605 ············{
606 ················dataAdapter.Update(rows);
607 ············}
608 catch ( DBConcurrencyException dbex)
609 ············{
610 ················if (this.ConcurrencyError != null)
611 ····················ConcurrencyError(dbex);
612 ················else
613 throw;
614 ············}
615 catch ( Exception ex)
616 ············{
617 ················string text = "Exception of type " + ex.GetType().Name + " while deleting data rows: " + ex.Message + "\n";
618 ················text += "Sql statement: " + deleteCommand.CommandText + "\n";
619 ················throw new NDOException(38, text);
620 ············}
621 ········}
622
623 ········private DataTable GetTemplateTable(DataSet templateDataset, string name)
624 ········{
625 ············// The instance of ds is actually static,
626 ············// since the SqlPersistenceHandler lives as
627 ············// a static instance in the PersistenceHandlerCache.
628 ············DataTable dt = templateDataset.Tables[name];
629 ············if (dt == null)
630 ················throw new NDOException(39, "Can't find table '" + name + "' in the schema. Check your mapping file.");
631 ············return dt;
632 ········}
633
634 ········/// <summary>
635 ········/// Executes a batch of sql statements.
636 ········/// </summary>
637 ········/// <param name="statements">Each element in the array is a sql statement.</param>
638 ········/// <param name="parameters">A list of parameters (see remarks).</param>
639 ········/// <returns>An List of Hashtables, containing the Name/Value pairs of the results.</returns>
640 ········/// <remarks>
641 ········/// For emty resultsets an empty Hashtable will be returned.
642 ········/// If parameters is a NDOParameterCollection, the parameters in the collection are valid for
643 ········/// all subqueries. If parameters is an ordinary IList, NDO expects to find a NDOParameterCollection
644 ········/// for each subquery. If an element is null, no parameters are submitted for the given query.
645 ········/// </remarks>
646 ········public IList<Dictionary<string, object>> ExecuteBatch( string[] statements, IList parameters )
647 ········{
648 ············List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();
649 ············bool closeIt = false;
650 ············IDataReader dr = null;
651 ············int i;
652 ············try
653 ············{
654 ················if (this.conn.State != ConnectionState.Open)
655 ················{
656 ····················closeIt = true;
657 ····················this.conn.Open();
658 ················}
659 ················string sql = string.Empty;
660
661 ················if (this.provider.SupportsBulkCommands)
662 ················{
663 ····················IDbCommand cmd = this.provider.NewSqlCommand( conn );
664 ····················sql = this.provider.GenerateBulkCommand( statements );
665 ····················cmd.CommandText = sql;
666 ····················if (parameters != null && parameters.Count > 0)
667 ····················{
668 ························// Only the first command gets parameters
669 ························for (i = 0; i < statements.Length; i++)
670 ························{
671 ····························if (i == 0)
672 ································CreateQueryParameters( cmd, parameters );
673 ····························else
674 ································CreateQueryParameters( null, null );
675 ························}
676 ····················}
677
678 ····················// cmd.CommandText can be changed in CreateQueryParameters
679 ····················DumpBatch( cmd.CommandText );
680 ····················if (this.transaction != null)
681 ························cmd.Transaction = this.transaction;
682
683 ····················dr = cmd.ExecuteReader();
684
685 ····················for (; ; )
686 ····················{
687 ························var dict = new Dictionary<string, object>();
688 ························while (dr.Read())
689 ························{
690 ····························for (i = 0; i < dr.FieldCount; i++)
691 ····························{
692 ································dict.Add( dr.GetName( i ), dr.GetValue( i ) );
693 ····························}
694 ························}
695 ························result.Add( dict );
696 ························if (!dr.NextResult())
697 ····························break;
698 ····················}
699
700 ····················dr.Close();
701 ················}
702 ················else
703 ················{
704 ····················for (i = 0; i < statements.Length; i++)
705 ····················{
706 ························string s = statements[i];
707 ························sql += s + ";\n"; // For DumpBatch only
708 ························var dict = new Dictionary<string, object>();
709 ························IDbCommand cmd = this.provider.NewSqlCommand( conn );
710
711 ························cmd.CommandText = s;
712 ························if (parameters != null && parameters.Count > 0)
713 ························{
714 ····························CreateQueryParameters( cmd, parameters );
715 ························}
716
717 ························if (this.transaction != null)
718 ····························cmd.Transaction = this.transaction;
719
720 ························dr = cmd.ExecuteReader();
721
722 ························while (dr.Read())
723 ························{
724 ····························for (int j = 0; j < dr.FieldCount; j++)
725 ····························{
726 ································dict.Add( dr.GetName( j ), dr.GetValue( j ) );
727 ····························}
728 ························}
729
730 ························dr.Close();
731 ························result.Add( dict );
732 ····················}
733
734 ····················DumpBatch( sql );
735 ················}
736 ············}
737 ············finally
738 ············{
739 ················if (dr != null && !dr.IsClosed)
740 ····················dr.Close();
741 ················if (closeIt)
742 ····················this.conn.Close();
743 ············}
744
745 ············return result;
746 ········}
747
748 ········private void CreateQueryParameters(IDbCommand command, IList parameters)
749 ········{
750 ············if (parameters == null || parameters.Count == 0)
751 ················return;
752
753 ············string sql = command.CommandText;
754
755 ············Regex regex = new Regex( @"\{(\d+)\}" );
756
757 ············MatchCollection matches = regex.Matches( sql );
758 ············Dictionary<string, object> tcValues = new Dictionary<string, object>();
759 ············int endIndex = parameters.Count - 1;
760 ············foreach (Match match in matches)
761 ············{
762 ················int nr = int.Parse( match.Groups[1].Value );
763 ················if (nr > endIndex)
764 ····················throw new QueryException( 10009, "Parameter-Reference " + match.Value + " has no matching parameter." );
765
766 ················sql = sql.Replace( match.Value,
767 ····················this.provider.GetNamedParameter( "p" + nr.ToString() ) );
768 ············}
769
770 ············command.CommandText = sql;
771
772 ············for (int i = 0; i < parameters.Count; i++)
773 ············{
774 ················object p = parameters[i];
775 ················if (p == null)
776 ····················p = DBNull.Value;
777 ················Type type = p.GetType();
778 ················if (type.FullName.StartsWith("System.Nullable`1"))
779 ····················type = type.GetGenericArguments()[0];
780 ················if (type == typeof( Guid ) && Guid.Empty.Equals( p ) || type == typeof( DateTime ) && DateTime.MinValue.Equals( p ))
781 ················{
782 ····················p = DBNull.Value;
783 ················}
784 ················if (type.IsEnum)
785 ················{
786 ····················type = Enum.GetUnderlyingType(type);
787 ····················p = ((IConvertible)p ).ToType(type, CultureInfo.CurrentCulture);
788 ················}
789 ················else if (type == typeof(Guid) && !provider.SupportsNativeGuidType)
790 ················{
791 ····················type = typeof(string);
792 ····················if (p != DBNull.Value)
793 ························p = p.ToString();
794 ················}
795 ················string name = "p" + i.ToString();
796 ················int length = this.provider.GetDefaultLength(type);
797 ················if (type == typeof(string))
798 ················{
799 ····················length = ((string)p).Length;
800 ····················if (provider.GetType().Name.IndexOf("Oracle") > -1)
801 ····················{
802 ························if (length == 0)
803 ····························throw new QueryException(10001, "Empty string parameters are not allowed in Oracle. Use IS NULL instead.");
804 ····················}
805 ················}
806 ················else if (type == typeof(byte[]))
807 ················{
808 ····················length = ((byte[])p).Length;
809 ················}
810 ················IDataParameter par = provider.AddParameter(
811 ····················command,
812 ····················this.provider.GetNamedParameter(name),
813 ····················this.provider.GetDbType( type == typeof( DBNull ) ? typeof( string ) : type ),
814 ····················length,
815 ····················this.provider.GetQuotedName(name));
816 ················par.Value = p;
817 ················par.Direction = ParameterDirection.Input;····················
818 ············}
819 ········}
820
821 ········/// <summary>
822 ········/// Performs a query and returns a DataTable
823 ········/// </summary>
824 ········/// <param name="sql"></param>
825 ········/// <param name="parameters"></param>
826 ········/// <param name="templateDataSet"></param>
827 ········/// <returns></returns>
828 ········public DataTable PerformQuery( string sql, IList parameters, DataSet templateDataSet )
829 ········{
830 ············if (sql.Trim().StartsWith( "EXEC", StringComparison.InvariantCultureIgnoreCase ))
831 ················this.selectCommand.CommandType = CommandType.StoredProcedure;
832 ············else
833 ················this.selectCommand.CommandType = CommandType.Text;
834
835 ············DataTable table = GetTemplateTable(templateDataSet, this.tableName).Clone();
836
837 ············this.selectCommand.CommandText = sql;
838
839 ············CreateQueryParameters(this.selectCommand, parameters);
840
841 ············Dump(null); // Dumps the Select Command
842
843 ············try
844 ············{
845 ················dataAdapter.Fill(table);
846 ············}
847 ············catch (System.Exception ex)
848 ············{
849 ················string text = "Exception of type " + ex.GetType().Name + " while executing a Query: " + ex.Message + "\n";
850 ················text += "Sql Statement: " + sql + "\n";
851 ················throw new NDOException(40, text);
852 ············}
853
854 ············return table;········
855 ········}
856
857 ········/// <summary>
858 ········/// Gets a Handler which can store data in relation tables
859 ········/// </summary>
860 ········/// <param name="r">Relation information</param>
861 ········/// <returns>The handler</returns>
862 ········public IMappingTableHandler GetMappingTableHandler(Relation r)
863 ········{
864 ············IMappingTableHandler handler;
865 ············if (!mappingTableHandlers.TryGetValue( r.FieldName, out handler ))
866 ············{
867 ················handler = new NDOMappingTableHandler();
868 ················handler.Initialize(ndoMapping, r);
869 ················handler.VerboseMode = this.verboseMode;
870 ················handler.LogAdapter = this.logAdapter;
871 ················mappingTableHandlers[r.FieldName] = handler;
872 ············}
873 ············return handler;
874 ········}
875
876 ········/// <summary>
877 ········/// Disposes a SqlPersistenceHandler
878 ········/// </summary>
879 ········public void Dispose()
880 ········{
881 ············this.disposeCallback( this.type, this );
882 ········}
883
884 ········/// <summary>
885 ········/// Gets or sets the connection to be used for the handler
886 ········/// </summary>
887 ········public IDbConnection Connection
888 ········{
889 ············get { return this.conn; }
890 ············set
891 ············{
892 ················this.conn = value;
893 ················this.selectCommand.Connection = value;
894 ················this.deleteCommand.Connection = value;
895 ················this.updateCommand.Connection = value;
896 ················this.insertCommand.Connection = value;
897 ············}
898 ········}
899
900 ········/// <summary>
901 ········/// Gets or sets the connection to be used for the handler
902 ········/// </summary>
903 ········public IDbTransaction Transaction
904 ········{
905 ············get { return this.transaction; }
906 ············set
907 ············{
908 ················this.transaction = value;
909 ················this.selectCommand.Transaction = value;
910 ················this.deleteCommand.Transaction = value;
911 ················this.updateCommand.Transaction = value;
912 ················this.insertCommand.Transaction = value;
913 ············}
914 ········}
915
916
917 ········/// <summary>
918 ········/// Gets the current DataAdapter.
919 ········/// </summary>
920 ········/// <remarks>
921 ········/// This is needed by RegisterRowUpdateHandler.
922 ········/// See the comment in SqlCeProvider.
923 ········/// </remarks>
924 ········public DbDataAdapter DataAdapter => this.dataAdapter;
925
926 ········#endregion
927 ····}
928 }
929