Datei: NDODLL/SqlPersistenceHandling/SqlPersistenceHandler.cs

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