/src/ServiceStack.OrmLite/OrmLiteWriteCommandExtensions.cs

https://github.com/ServiceStack/ServiceStack.OrmLite · C# · 1201 lines · 952 code · 231 blank · 18 comment · 148 complexity · f9fcc9e65ed5fb51cee8f5739a0f89b8 MD5 · raw file

  1. //
  2. // ServiceStack.OrmLite: Light-weight POCO ORM for .NET and Mono
  3. //
  4. // Authors:
  5. // Demis Bellot (demis.bellot@gmail.com)
  6. //
  7. // Copyright 2013 ServiceStack, Inc. All Rights Reserved.
  8. //
  9. // Licensed under the same terms of ServiceStack.
  10. //
  11. using System;
  12. using System.Collections;
  13. using System.Collections.Generic;
  14. using System.Data;
  15. using System.Linq;
  16. using ServiceStack.Data;
  17. using ServiceStack.DataAnnotations;
  18. using ServiceStack.Logging;
  19. using ServiceStack.OrmLite.Converters;
  20. using ServiceStack.Text;
  21. namespace ServiceStack.OrmLite
  22. {
  23. public static class OrmLiteWriteCommandExtensions
  24. {
  25. internal static ILog Log = LogManager.GetLogger(typeof(OrmLiteWriteCommandExtensions));
  26. internal static bool CreateSchema<T>(this IDbCommand dbCmd)
  27. {
  28. var schemaName = typeof(T).FirstAttribute<SchemaAttribute>()?.Name;
  29. if(schemaName == null) throw new InvalidOperationException($"Type {typeof(T).Name} does not have a schema attribute, just CreateSchema(string schemaName) instead");
  30. return CreateSchema(dbCmd, schemaName);
  31. }
  32. internal static bool CreateSchema(this IDbCommand dbCmd, string schemaName)
  33. {
  34. schemaName.ThrowIfNullOrEmpty(nameof(schemaName));
  35. var dialectProvider = dbCmd.GetDialectProvider();
  36. var schema = dialectProvider.NamingStrategy.GetSchemaName(schemaName);
  37. var schemaExists = dialectProvider.DoesSchemaExist(dbCmd, schema);
  38. if (schemaExists)
  39. {
  40. return true;
  41. }
  42. try
  43. {
  44. ExecuteSql(dbCmd, dialectProvider.ToCreateSchemaStatement(schema));
  45. return true;
  46. }
  47. catch (Exception ex)
  48. {
  49. if (IgnoreAlreadyExistsError(ex))
  50. {
  51. Log.DebugFormat("Ignoring existing schema '{0}': {1}", schema, ex.Message);
  52. return false;
  53. }
  54. throw;
  55. }
  56. }
  57. internal static void CreateTables(this IDbCommand dbCmd, bool overwrite, params Type[] tableTypes)
  58. {
  59. foreach (var tableType in tableTypes)
  60. {
  61. CreateTable(dbCmd, overwrite, tableType);
  62. }
  63. }
  64. internal static bool CreateTable<T>(this IDbCommand dbCmd, bool overwrite = false)
  65. {
  66. var tableType = typeof(T);
  67. return CreateTable(dbCmd, overwrite, tableType);
  68. }
  69. internal static bool CreateTable(this IDbCommand dbCmd, bool overwrite, Type modelType)
  70. {
  71. var modelDef = modelType.GetModelDefinition();
  72. var dialectProvider = dbCmd.GetDialectProvider();
  73. var tableName = dialectProvider.NamingStrategy.GetTableName(modelDef);
  74. var schema = dialectProvider.NamingStrategy.GetSchemaName(modelDef);
  75. var tableExists = dialectProvider.DoesTableExist(dbCmd, tableName, schema);
  76. if (overwrite && tableExists)
  77. {
  78. DropTable(dbCmd, modelDef);
  79. var postDropTableSql = dialectProvider.ToPostDropTableStatement(modelDef);
  80. if (postDropTableSql != null)
  81. {
  82. ExecuteSql(dbCmd, postDropTableSql);
  83. }
  84. tableExists = false;
  85. }
  86. try
  87. {
  88. if (!tableExists)
  89. {
  90. if (modelDef.PreCreateTableSql != null)
  91. {
  92. ExecuteSql(dbCmd, modelDef.PreCreateTableSql);
  93. }
  94. // sequences must be created before tables
  95. var sequenceList = dialectProvider.SequenceList(modelType);
  96. if (sequenceList.Count > 0)
  97. {
  98. foreach (var seq in sequenceList)
  99. {
  100. if (dialectProvider.DoesSequenceExist(dbCmd, seq) == false)
  101. {
  102. var seqSql = dialectProvider.ToCreateSequenceStatement(modelType, seq);
  103. dbCmd.ExecuteSql(seqSql);
  104. }
  105. }
  106. }
  107. else
  108. {
  109. var sequences = dialectProvider.ToCreateSequenceStatements(modelType);
  110. foreach (var seq in sequences)
  111. {
  112. try
  113. {
  114. dbCmd.ExecuteSql(seq);
  115. }
  116. catch (Exception ex)
  117. {
  118. if (IgnoreAlreadyExistsGeneratorError(ex))
  119. {
  120. Log.DebugFormat("Ignoring existing generator '{0}': {1}", seq, ex.Message);
  121. continue;
  122. }
  123. throw;
  124. }
  125. }
  126. }
  127. var createTableSql = dialectProvider.ToCreateTableStatement(modelType);
  128. ExecuteSql(dbCmd, createTableSql);
  129. var postCreateTableSql = dialectProvider.ToPostCreateTableStatement(modelDef);
  130. if (postCreateTableSql != null)
  131. {
  132. ExecuteSql(dbCmd, postCreateTableSql);
  133. }
  134. if (modelDef.PostCreateTableSql != null)
  135. {
  136. ExecuteSql(dbCmd, modelDef.PostCreateTableSql);
  137. }
  138. var sqlIndexes = dialectProvider.ToCreateIndexStatements(modelType);
  139. foreach (var sqlIndex in sqlIndexes)
  140. {
  141. try
  142. {
  143. dbCmd.ExecuteSql(sqlIndex);
  144. }
  145. catch (Exception exIndex)
  146. {
  147. if (IgnoreAlreadyExistsError(exIndex))
  148. {
  149. Log.DebugFormat("Ignoring existing index '{0}': {1}", sqlIndex, exIndex.Message);
  150. continue;
  151. }
  152. throw;
  153. }
  154. }
  155. return true;
  156. }
  157. }
  158. catch (Exception ex)
  159. {
  160. if (IgnoreAlreadyExistsError(ex))
  161. {
  162. Log.DebugFormat("Ignoring existing table '{0}': {1}", modelDef.ModelName, ex.Message);
  163. return false;
  164. }
  165. throw;
  166. }
  167. return false;
  168. }
  169. internal static void DropTable<T>(this IDbCommand dbCmd)
  170. {
  171. DropTable(dbCmd, ModelDefinition<T>.Definition);
  172. }
  173. internal static void DropTable(this IDbCommand dbCmd, Type modelType)
  174. {
  175. DropTable(dbCmd, modelType.GetModelDefinition());
  176. }
  177. internal static void DropTables(this IDbCommand dbCmd, params Type[] tableTypes)
  178. {
  179. foreach (var modelDef in tableTypes.Select(type => type.GetModelDefinition()))
  180. {
  181. DropTable(dbCmd, modelDef);
  182. }
  183. }
  184. private static void DropTable(IDbCommand dbCmd, ModelDefinition modelDef)
  185. {
  186. try
  187. {
  188. var dialectProvider = dbCmd.GetDialectProvider();
  189. var tableName = dialectProvider.NamingStrategy.GetTableName(modelDef);
  190. var schema = dialectProvider.NamingStrategy.GetSchemaName(modelDef);
  191. if (dialectProvider.DoesTableExist(dbCmd, tableName, schema))
  192. {
  193. if (modelDef.PreDropTableSql != null)
  194. {
  195. ExecuteSql(dbCmd, modelDef.PreDropTableSql);
  196. }
  197. var dropTableFks = dialectProvider.GetDropForeignKeyConstraints(modelDef);
  198. if (!string.IsNullOrEmpty(dropTableFks))
  199. {
  200. dbCmd.ExecuteSql(dropTableFks);
  201. }
  202. dbCmd.ExecuteSql("DROP TABLE " + dialectProvider.GetQuotedTableName(modelDef));
  203. if (modelDef.PostDropTableSql != null)
  204. {
  205. ExecuteSql(dbCmd, modelDef.PostDropTableSql);
  206. }
  207. }
  208. }
  209. catch (Exception ex)
  210. {
  211. Log.DebugFormat("Could not drop table '{0}': {1}", modelDef.ModelName, ex.Message);
  212. throw;
  213. }
  214. }
  215. internal static string LastSql(this IDbCommand dbCmd)
  216. {
  217. return dbCmd.CommandText;
  218. }
  219. internal static int ExecuteSql(this IDbCommand dbCmd, string sql, IEnumerable<IDbDataParameter> sqlParams = null, Action<IDbCommand> commandFilter = null)
  220. {
  221. dbCmd.CommandText = sql;
  222. dbCmd.SetParameters(sqlParams);
  223. commandFilter?.Invoke(dbCmd);
  224. if (Log.IsDebugEnabled)
  225. Log.DebugCommand(dbCmd);
  226. OrmLiteConfig.BeforeExecFilter?.Invoke(dbCmd);
  227. if (OrmLiteConfig.ResultsFilter != null)
  228. return OrmLiteConfig.ResultsFilter.ExecuteSql(dbCmd);
  229. return dbCmd.ExecuteNonQuery();
  230. }
  231. internal static int ExecuteSql(this IDbCommand dbCmd, string sql, object anonType, Action<IDbCommand> commandFilter = null)
  232. {
  233. if (anonType != null)
  234. dbCmd.SetParameters(anonType.ToObjectDictionary(), excludeDefaults: false, sql:ref sql);
  235. dbCmd.CommandText = sql;
  236. commandFilter?.Invoke(dbCmd);
  237. if (Log.IsDebugEnabled)
  238. Log.DebugCommand(dbCmd);
  239. OrmLiteConfig.BeforeExecFilter?.Invoke(dbCmd);
  240. if (OrmLiteConfig.ResultsFilter != null)
  241. return OrmLiteConfig.ResultsFilter.ExecuteSql(dbCmd);
  242. return dbCmd.ExecuteNonQuery();
  243. }
  244. private static bool IgnoreAlreadyExistsError(Exception ex)
  245. {
  246. //ignore Sqlite table already exists error
  247. const string sqliteAlreadyExistsError = "already exists";
  248. const string sqlServerAlreadyExistsError = "There is already an object named";
  249. return ex.Message.Contains(sqliteAlreadyExistsError)
  250. || ex.Message.Contains(sqlServerAlreadyExistsError);
  251. }
  252. private static bool IgnoreAlreadyExistsGeneratorError(Exception ex)
  253. {
  254. const string fbError = "attempt to store duplicate value";
  255. const string fbAlreadyExistsError = "already exists";
  256. return ex.Message.Contains(fbError) || ex.Message.Contains(fbAlreadyExistsError);
  257. }
  258. public static T PopulateWithSqlReader<T>(this T objWithProperties, IOrmLiteDialectProvider dialectProvider, IDataReader reader)
  259. {
  260. var indexCache = reader.GetIndexFieldsCache(ModelDefinition<T>.Definition, dialectProvider);
  261. var values = new object[reader.FieldCount];
  262. return PopulateWithSqlReader(objWithProperties, dialectProvider, reader, indexCache, values);
  263. }
  264. public static int GetColumnIndex(this IDataReader reader, IOrmLiteDialectProvider dialectProvider, string fieldName)
  265. {
  266. try
  267. {
  268. return reader.GetOrdinal(dialectProvider.NamingStrategy.GetColumnName(fieldName));
  269. }
  270. catch (IndexOutOfRangeException /*ignoreNotFoundExInSomeProviders*/)
  271. {
  272. return NotFound;
  273. }
  274. }
  275. private const int NotFound = -1;
  276. public static T PopulateWithSqlReader<T>(this T objWithProperties,
  277. IOrmLiteDialectProvider dialectProvider, IDataReader reader,
  278. Tuple<FieldDefinition, int, IOrmLiteConverter>[] indexCache, object[] values)
  279. {
  280. dialectProvider.PopulateObjectWithSqlReader<T>(objWithProperties, reader, indexCache, values);
  281. return objWithProperties;
  282. }
  283. public static void PopulateObjectWithSqlReader<T>(this IOrmLiteDialectProvider dialectProvider,
  284. object objWithProperties, IDataReader reader,
  285. Tuple<FieldDefinition, int, IOrmLiteConverter>[] indexCache, object[] values)
  286. {
  287. values = PopulateValues(reader, values, dialectProvider);
  288. var dbNullFilter = OrmLiteConfig.OnDbNullFilter;
  289. foreach (var fieldCache in indexCache)
  290. {
  291. try
  292. {
  293. var fieldDef = fieldCache.Item1;
  294. var index = fieldCache.Item2;
  295. var converter = fieldCache.Item3;
  296. if (values != null && values[index] == DBNull.Value)
  297. {
  298. var value = fieldDef.IsNullable ? null : fieldDef.FieldTypeDefaultValue;
  299. var useValue = dbNullFilter?.Invoke(fieldDef);
  300. if (useValue != null)
  301. value = useValue;
  302. fieldDef.SetValue(objWithProperties, value);
  303. }
  304. else
  305. {
  306. var value = converter.GetValue(reader, index, values);
  307. if (value == null)
  308. {
  309. if (!fieldDef.IsNullable)
  310. value = fieldDef.FieldTypeDefaultValue;
  311. var useValue = dbNullFilter?.Invoke(fieldDef);
  312. if (useValue != null)
  313. value = useValue;
  314. fieldDef.SetValue(objWithProperties, value);
  315. }
  316. else
  317. {
  318. var fieldValue = converter.FromDbValue(fieldDef.FieldType, value);
  319. fieldDef.SetValue(objWithProperties, fieldValue);
  320. }
  321. }
  322. }
  323. catch (Exception ex)
  324. {
  325. OrmLiteUtils.HandleException(ex);
  326. }
  327. }
  328. OrmLiteConfig.PopulatedObjectFilter?.Invoke(objWithProperties);
  329. }
  330. internal static object[] PopulateValues(this IDataReader reader, object[] values, IOrmLiteDialectProvider dialectProvider)
  331. {
  332. if (!OrmLiteConfig.DeoptimizeReader)
  333. {
  334. if (values == null)
  335. values = new object[reader.FieldCount];
  336. try
  337. {
  338. dialectProvider.GetValues(reader, values);
  339. }
  340. catch (Exception ex)
  341. {
  342. values = null;
  343. Log.Warn("Error trying to use GetValues() from DataReader. Falling back to individual field reads...", ex);
  344. }
  345. }
  346. else
  347. {
  348. //Calling GetValues() on System.Data.SQLite.Core ADO.NET Provider changes behavior of reader.GetGuid()
  349. //So allow providers to by-pass reader.GetValues() optimization.
  350. values = null;
  351. }
  352. return values;
  353. }
  354. internal static int Update<T>(this IDbCommand dbCmd, T obj, Action<IDbCommand> commandFilter = null)
  355. {
  356. return dbCmd.UpdateInternal<T>(obj, commandFilter);
  357. }
  358. internal static int Update<T>(this IDbCommand dbCmd, Dictionary<string,object> obj, Action<IDbCommand> commandFilter = null)
  359. {
  360. return dbCmd.UpdateInternal<T>(obj, commandFilter);
  361. }
  362. internal static int UpdateInternal<T>(this IDbCommand dbCmd, object obj, Action<IDbCommand> commandFilter = null)
  363. {
  364. OrmLiteUtils.AssertNotAnonType<T>();
  365. OrmLiteConfig.UpdateFilter?.Invoke(dbCmd, obj.ToFilterType<T>());
  366. var dialectProvider = dbCmd.GetDialectProvider();
  367. var hadRowVersion = dialectProvider.PrepareParameterizedUpdateStatement<T>(dbCmd);
  368. if (string.IsNullOrEmpty(dbCmd.CommandText))
  369. return 0;
  370. dialectProvider.SetParameterValues<T>(dbCmd, obj);
  371. return dbCmd.UpdateAndVerify<T>(commandFilter, hadRowVersion);
  372. }
  373. internal static int UpdateAndVerify<T>(this IDbCommand dbCmd, Action<IDbCommand> commandFilter, bool hadRowVersion)
  374. {
  375. commandFilter?.Invoke(dbCmd);
  376. var rowsUpdated = dbCmd.ExecNonQuery();
  377. if (hadRowVersion && rowsUpdated == 0)
  378. throw new OptimisticConcurrencyException();
  379. return rowsUpdated;
  380. }
  381. internal static int Update<T>(this IDbCommand dbCmd, T[] objs, Action<IDbCommand> commandFilter = null)
  382. {
  383. return dbCmd.UpdateAll(objs: objs, commandFilter: commandFilter);
  384. }
  385. internal static int UpdateAll<T>(this IDbCommand dbCmd, IEnumerable<T> objs, Action<IDbCommand> commandFilter = null)
  386. {
  387. OrmLiteUtils.AssertNotAnonType<T>();
  388. IDbTransaction dbTrans = null;
  389. int count = 0;
  390. try
  391. {
  392. if (dbCmd.Transaction == null)
  393. dbCmd.Transaction = dbTrans = dbCmd.Connection.BeginTransaction();
  394. var dialectProvider = dbCmd.GetDialectProvider();
  395. var hadRowVersion = dialectProvider.PrepareParameterizedUpdateStatement<T>(dbCmd);
  396. if (string.IsNullOrEmpty(dbCmd.CommandText))
  397. return 0;
  398. foreach (var obj in objs)
  399. {
  400. OrmLiteConfig.UpdateFilter?.Invoke(dbCmd, obj);
  401. dialectProvider.SetParameterValues<T>(dbCmd, obj);
  402. commandFilter?.Invoke(dbCmd); //filters can augment SQL & only should be invoked once
  403. commandFilter = null;
  404. var rowsUpdated = dbCmd.ExecNonQuery();
  405. if (hadRowVersion && rowsUpdated == 0)
  406. throw new OptimisticConcurrencyException();
  407. count += rowsUpdated;
  408. }
  409. dbTrans?.Commit();
  410. }
  411. finally
  412. {
  413. dbTrans?.Dispose();
  414. }
  415. return count;
  416. }
  417. private static int AssertRowsUpdated(IDbCommand dbCmd, bool hadRowVersion)
  418. {
  419. var rowsUpdated = dbCmd.ExecNonQuery();
  420. if (hadRowVersion && rowsUpdated == 0)
  421. throw new OptimisticConcurrencyException();
  422. return rowsUpdated;
  423. }
  424. internal static int Delete<T>(this IDbCommand dbCmd, T anonType, Action<IDbCommand> commandFilter = null)
  425. {
  426. return dbCmd.Delete<T>((object)anonType, commandFilter);
  427. }
  428. internal static int Delete<T>(this IDbCommand dbCmd, object anonType, Action<IDbCommand> commandFilter = null)
  429. {
  430. var dialectProvider = dbCmd.GetDialectProvider();
  431. var hadRowVersion = dialectProvider.PrepareParameterizedDeleteStatement<T>(
  432. dbCmd, anonType.AllFieldsMap<T>());
  433. dialectProvider.SetParameterValues<T>(dbCmd, anonType);
  434. commandFilter?.Invoke(dbCmd);
  435. return AssertRowsUpdated(dbCmd, hadRowVersion);
  436. }
  437. internal static int DeleteNonDefaults<T>(this IDbCommand dbCmd, T filter)
  438. {
  439. var dialectProvider = dbCmd.GetDialectProvider();
  440. var hadRowVersion = dialectProvider.PrepareParameterizedDeleteStatement<T>(
  441. dbCmd, filter.AllFieldsMap<T>().NonDefaultsOnly());
  442. dialectProvider.SetParameterValues<T>(dbCmd, filter);
  443. return AssertRowsUpdated(dbCmd, hadRowVersion);
  444. }
  445. internal static int Delete<T>(this IDbCommand dbCmd, T[] objs)
  446. {
  447. if (objs.Length == 0)
  448. return 0;
  449. return DeleteAll(dbCmd, objs);
  450. }
  451. internal static int DeleteNonDefaults<T>(this IDbCommand dbCmd, T[] filters)
  452. {
  453. if (filters.Length == 0) return 0;
  454. return DeleteAll(dbCmd, filters, o => o.AllFieldsMap<T>().NonDefaultsOnly());
  455. }
  456. private static int DeleteAll<T>(IDbCommand dbCmd, IEnumerable<T> objs,
  457. Func<object,Dictionary<string,object>> fieldValuesFn, Action<IDbCommand> commandFilter = null)
  458. {
  459. OrmLiteUtils.AssertNotAnonType<T>();
  460. IDbTransaction dbTrans = null;
  461. int count = 0;
  462. try
  463. {
  464. if (dbCmd.Transaction == null)
  465. dbCmd.Transaction = dbTrans = dbCmd.Connection.BeginTransaction();
  466. var dialectProvider = dbCmd.GetDialectProvider();
  467. foreach (var obj in objs)
  468. {
  469. var fieldValues = fieldValuesFn != null
  470. ? fieldValuesFn(obj)
  471. : obj.AllFieldsMap<T>();
  472. dialectProvider.PrepareParameterizedDeleteStatement<T>(dbCmd, fieldValues);
  473. dialectProvider.SetParameterValues<T>(dbCmd, obj);
  474. commandFilter?.Invoke(dbCmd); //filters can augment SQL & only should be invoked once
  475. commandFilter = null;
  476. count += dbCmd.ExecNonQuery();
  477. }
  478. dbTrans?.Commit();
  479. }
  480. finally
  481. {
  482. dbTrans?.Dispose();
  483. }
  484. return count;
  485. }
  486. internal static int DeleteById<T>(this IDbCommand dbCmd, object id, Action<IDbCommand> commandFilter = null)
  487. {
  488. var sql = DeleteByIdSql<T>(dbCmd, id);
  489. return dbCmd.ExecuteSql(sql, commandFilter: commandFilter);
  490. }
  491. internal static string DeleteByIdSql<T>(this IDbCommand dbCmd, object id)
  492. {
  493. var modelDef = ModelDefinition<T>.Definition;
  494. var dialectProvider = dbCmd.GetDialectProvider();
  495. var idParamString = dialectProvider.GetParam();
  496. var sql = $"DELETE FROM {dialectProvider.GetQuotedTableName(modelDef)} " +
  497. $"WHERE {dialectProvider.GetQuotedColumnName(modelDef.PrimaryKey.FieldName)} = {idParamString}";
  498. var idParam = dbCmd.CreateParameter();
  499. idParam.ParameterName = idParamString;
  500. idParam.Value = id;
  501. dbCmd.Parameters.Add(idParam);
  502. return sql;
  503. }
  504. internal static void DeleteById<T>(this IDbCommand dbCmd, object id, ulong rowVersion, Action<IDbCommand> commandFilter = null)
  505. {
  506. var sql = DeleteByIdSql<T>(dbCmd, id, rowVersion);
  507. var rowsAffected = dbCmd.ExecuteSql(sql, commandFilter: commandFilter);
  508. if (rowsAffected == 0)
  509. throw new OptimisticConcurrencyException("The row was modified or deleted since the last read");
  510. }
  511. internal static string DeleteByIdSql<T>(this IDbCommand dbCmd, object id, ulong rowVersion)
  512. {
  513. OrmLiteUtils.AssertNotAnonType<T>();
  514. var modelDef = ModelDefinition<T>.Definition;
  515. var dialectProvider = dbCmd.GetDialectProvider();
  516. dbCmd.Parameters.Clear();
  517. var idParam = dbCmd.CreateParameter();
  518. idParam.ParameterName = dialectProvider.GetParam();
  519. idParam.Value = id;
  520. dbCmd.Parameters.Add(idParam);
  521. var rowVersionField = modelDef.RowVersion;
  522. if (rowVersionField == null)
  523. throw new InvalidOperationException(
  524. "Cannot use DeleteById with rowVersion for model type without a row version column");
  525. var rowVersionParam = dbCmd.CreateParameter();
  526. rowVersionParam.ParameterName = dialectProvider.GetParam("rowVersion");
  527. var converter = dialectProvider.GetConverterBestMatch(typeof(RowVersionConverter));
  528. converter.InitDbParam(rowVersionParam, typeof(ulong));
  529. rowVersionParam.Value = converter.ToDbValue(typeof(ulong), rowVersion);
  530. dbCmd.Parameters.Add(rowVersionParam);
  531. var sql = $"DELETE FROM {dialectProvider.GetQuotedTableName(modelDef)} " +
  532. $"WHERE {dialectProvider.GetQuotedColumnName(modelDef.PrimaryKey.FieldName)} = {idParam.ParameterName} " +
  533. $"AND {dialectProvider.GetRowVersionColumn(rowVersionField)} = {rowVersionParam.ParameterName}";
  534. return sql;
  535. }
  536. internal static int DeleteByIds<T>(this IDbCommand dbCmd, IEnumerable idValues)
  537. {
  538. OrmLiteUtils.AssertNotAnonType<T>();
  539. var sqlIn = dbCmd.SetIdsInSqlParams(idValues);
  540. if (string.IsNullOrEmpty(sqlIn))
  541. return 0;
  542. var sql = GetDeleteByIdsSql<T>(sqlIn, dbCmd.GetDialectProvider());
  543. return dbCmd.ExecuteSql(sql);
  544. }
  545. internal static string GetDeleteByIdsSql<T>(string sqlIn, IOrmLiteDialectProvider dialectProvider)
  546. {
  547. var modelDef = ModelDefinition<T>.Definition;
  548. var sql = $"DELETE FROM {dialectProvider.GetQuotedTableName(modelDef)} " +
  549. $"WHERE {dialectProvider.GetQuotedColumnName(modelDef.PrimaryKey.FieldName)} IN ({sqlIn})";
  550. return sql;
  551. }
  552. internal static int DeleteAll<T>(this IDbCommand dbCmd)
  553. {
  554. OrmLiteUtils.AssertNotAnonType<T>();
  555. return DeleteAll(dbCmd, typeof(T));
  556. }
  557. internal static int DeleteAll<T>(this IDbCommand dbCmd, IEnumerable<T> rows)
  558. {
  559. var ids = rows.Map(x => x.GetId());
  560. return dbCmd.DeleteByIds<T>(ids);
  561. }
  562. internal static int DeleteAll(this IDbCommand dbCmd, Type tableType)
  563. {
  564. return dbCmd.ExecuteSql(dbCmd.GetDialectProvider().ToDeleteStatement(tableType, null));
  565. }
  566. internal static int Delete<T>(this IDbCommand dbCmd, string sql, object anonType = null)
  567. {
  568. OrmLiteUtils.AssertNotAnonType<T>();
  569. if (anonType != null) dbCmd.SetParameters<T>(anonType, excludeDefaults: false, sql: ref sql);
  570. return dbCmd.ExecuteSql(dbCmd.GetDialectProvider().ToDeleteStatement(typeof(T), sql));
  571. }
  572. internal static int Delete(this IDbCommand dbCmd, Type tableType, string sql, object anonType = null)
  573. {
  574. if (anonType != null) dbCmd.SetParameters(tableType, anonType, excludeDefaults: false, sql: ref sql);
  575. return dbCmd.ExecuteSql(dbCmd.GetDialectProvider().ToDeleteStatement(tableType, sql));
  576. }
  577. internal static long Insert<T>(this IDbCommand dbCmd, T obj, Action<IDbCommand> commandFilter, bool selectIdentity = false, bool enableIdentityInsert=false)
  578. {
  579. OrmLiteUtils.AssertNotAnonType<T>();
  580. OrmLiteConfig.InsertFilter?.Invoke(dbCmd, obj);
  581. var dialectProvider = dbCmd.GetDialectProvider();
  582. var pkField = ModelDefinition<T>.Definition.FieldDefinitions.FirstOrDefault(f => f.IsPrimaryKey);
  583. if (!enableIdentityInsert || pkField == null || !pkField.AutoIncrement)
  584. {
  585. dialectProvider.PrepareParameterizedInsertStatement<T>(dbCmd,
  586. insertFields: dialectProvider.GetNonDefaultValueInsertFields<T>(obj));
  587. return InsertInternal<T>(dialectProvider, dbCmd, obj, commandFilter, selectIdentity);
  588. }
  589. else
  590. {
  591. try
  592. {
  593. dialectProvider.EnableIdentityInsert<T>(dbCmd);
  594. dialectProvider.PrepareParameterizedInsertStatement<T>(dbCmd,
  595. insertFields: dialectProvider.GetNonDefaultValueInsertFields<T>(obj),
  596. shouldInclude: f => f == pkField);
  597. InsertInternal<T>(dialectProvider, dbCmd, obj, commandFilter, selectIdentity);
  598. if (selectIdentity)
  599. {
  600. var id = pkField.GetValue(obj);
  601. return Convert.ToInt64(id);
  602. }
  603. return default;
  604. }
  605. finally
  606. {
  607. dialectProvider.DisableIdentityInsert<T>(dbCmd);
  608. }
  609. }
  610. }
  611. internal static long Insert<T>(this IDbCommand dbCmd, Dictionary<string,object> obj, Action<IDbCommand> commandFilter, bool selectIdentity = false)
  612. {
  613. OrmLiteUtils.AssertNotAnonType<T>();
  614. OrmLiteConfig.InsertFilter?.Invoke(dbCmd, obj.ToFilterType<T>());
  615. var dialectProvider = dbCmd.GetDialectProvider();
  616. var pkField = ModelDefinition<T>.Definition.PrimaryKey;
  617. object id = null;
  618. var enableIdentityInsert = pkField?.AutoIncrement == true && obj.TryGetValue(pkField.Name, out id);
  619. try
  620. {
  621. if (enableIdentityInsert)
  622. dialectProvider.EnableIdentityInsert<T>(dbCmd);
  623. dialectProvider.PrepareParameterizedInsertStatement<T>(dbCmd,
  624. insertFields: dialectProvider.GetNonDefaultValueInsertFields<T>(obj),
  625. shouldInclude: f => obj.ContainsKey(f.Name));
  626. var ret = InsertInternal<T>(dialectProvider, dbCmd, obj, commandFilter, selectIdentity);
  627. if (enableIdentityInsert)
  628. return Convert.ToInt64(id);
  629. return ret;
  630. }
  631. finally
  632. {
  633. if (enableIdentityInsert)
  634. dialectProvider.DisableIdentityInsert<T>(dbCmd);
  635. }
  636. }
  637. internal static void RemovePrimaryKeyWithDefaultValue<T>(this Dictionary<string, object> obj)
  638. {
  639. var pkField = typeof(T).GetModelDefinition().PrimaryKey;
  640. if (pkField != null && (pkField.AutoIncrement || pkField.AutoId))
  641. {
  642. // Don't insert Primary Key with Default Value
  643. if (obj.TryGetValue(pkField.Name, out var idValue) &&
  644. idValue != null && !idValue.Equals(pkField.DefaultValue))
  645. {
  646. obj.Remove(pkField.Name);
  647. }
  648. }
  649. }
  650. private static long InsertInternal<T>(IOrmLiteDialectProvider dialectProvider, IDbCommand dbCmd, object obj, Action<IDbCommand> commandFilter, bool selectIdentity)
  651. {
  652. OrmLiteUtils.AssertNotAnonType<T>();
  653. dialectProvider.SetParameterValues<T>(dbCmd, obj);
  654. commandFilter?.Invoke(dbCmd); //dbCmd.OnConflictInsert() needs to be applied before last insert id
  655. if (dialectProvider.HasInsertReturnValues(ModelDefinition<T>.Definition))
  656. {
  657. using var reader = dbCmd.ExecReader(dbCmd.CommandText);
  658. return reader.PopulateReturnValues<T>(dialectProvider, obj);
  659. }
  660. if (selectIdentity)
  661. {
  662. dbCmd.CommandText += dialectProvider.GetLastInsertIdSqlSuffix<T>();
  663. return dbCmd.ExecLongScalar();
  664. }
  665. return dbCmd.ExecNonQuery();
  666. }
  667. internal static long PopulateReturnValues<T>(this IDataReader reader, IOrmLiteDialectProvider dialectProvider, object obj)
  668. {
  669. if (reader.Read())
  670. {
  671. var modelDef = ModelDefinition<T>.Definition;
  672. var values = new object[reader.FieldCount];
  673. var indexCache = reader.GetIndexFieldsCache(modelDef, dialectProvider);
  674. dialectProvider.PopulateObjectWithSqlReader<T>(obj, reader, indexCache, values);
  675. if ((modelDef.PrimaryKey != null) && (modelDef.PrimaryKey.AutoIncrement || modelDef.PrimaryKey.ReturnOnInsert))
  676. {
  677. var id = modelDef.GetPrimaryKey(obj);
  678. if (modelDef.PrimaryKey.AutoId)
  679. return 1;
  680. return Convert.ToInt64(id);
  681. }
  682. }
  683. return 0;
  684. }
  685. internal static void Insert<T>(this IDbCommand dbCmd, Action<IDbCommand> commandFilter, params T[] objs)
  686. {
  687. dbCmd.InsertAll(objs: objs, commandFilter: commandFilter);
  688. }
  689. internal static long InsertIntoSelect<T>(this IDbCommand dbCmd, ISqlExpression query, Action<IDbCommand> commandFilter) =>
  690. dbCmd.InsertIntoSelectInternal<T>(query, commandFilter).ExecNonQuery();
  691. internal static IDbCommand InsertIntoSelectInternal<T>(this IDbCommand dbCmd, ISqlExpression query, Action<IDbCommand> commandFilter)
  692. {
  693. var dialectProvider = dbCmd.GetDialectProvider();
  694. var sql = query.ToSelectStatement(QueryType.Select);
  695. var selectFields = query.GetUntypedSqlExpression()
  696. .SelectExpression
  697. .Substring("SELECT ".Length)
  698. .ParseCommands();
  699. var fieldsOrAliases = selectFields
  700. .Map(x => x.Original.ToString().AliasOrColumn());
  701. dialectProvider.PrepareParameterizedInsertStatement<T>(dbCmd, insertFields: fieldsOrAliases);
  702. dbCmd.SetParameters(query.Params);
  703. dbCmd.CommandText = dbCmd.CommandText.LeftPart(")") + ")\n" + sql;
  704. commandFilter?.Invoke(dbCmd); //dbCmd.OnConflictInsert() needs to be applied before last insert id
  705. return dbCmd;
  706. }
  707. internal static void InsertAll<T>(this IDbCommand dbCmd, IEnumerable<T> objs, Action<IDbCommand> commandFilter)
  708. {
  709. IDbTransaction dbTrans = null;
  710. try
  711. {
  712. if (dbCmd.Transaction == null)
  713. dbCmd.Transaction = dbTrans = dbCmd.Connection.BeginTransaction();
  714. var dialectProvider = dbCmd.GetDialectProvider();
  715. dialectProvider.PrepareParameterizedInsertStatement<T>(dbCmd);
  716. foreach (var obj in objs)
  717. {
  718. OrmLiteConfig.InsertFilter?.Invoke(dbCmd, obj);
  719. dialectProvider.SetParameterValues<T>(dbCmd, obj);
  720. commandFilter?.Invoke(dbCmd); //filters can augment SQL & only should be invoked once
  721. commandFilter = null;
  722. try
  723. {
  724. dbCmd.ExecNonQuery();
  725. }
  726. catch (Exception ex)
  727. {
  728. Log.Error($"SQL ERROR: {dbCmd.GetLastSqlAndParams()}", ex);
  729. throw;
  730. }
  731. }
  732. dbTrans?.Commit();
  733. }
  734. finally
  735. {
  736. dbTrans?.Dispose();
  737. }
  738. }
  739. internal static void InsertUsingDefaults<T>(this IDbCommand dbCmd, params T[] objs)
  740. {
  741. IDbTransaction dbTrans = null;
  742. try
  743. {
  744. if (dbCmd.Transaction == null)
  745. dbCmd.Transaction = dbTrans = dbCmd.Connection.BeginTransaction();
  746. var dialectProvider = dbCmd.GetDialectProvider();
  747. var modelDef = typeof(T).GetModelDefinition();
  748. var fieldsWithoutDefaults = modelDef.FieldDefinitionsArray
  749. .Where(x => x.DefaultValue == null)
  750. .Select(x => x.Name)
  751. .ToSet();
  752. dialectProvider.PrepareParameterizedInsertStatement<T>(dbCmd,
  753. insertFields: fieldsWithoutDefaults);
  754. foreach (var obj in objs)
  755. {
  756. OrmLiteConfig.InsertFilter?.Invoke(dbCmd, obj);
  757. dialectProvider.SetParameterValues<T>(dbCmd, obj);
  758. try
  759. {
  760. dbCmd.ExecNonQuery();
  761. }
  762. catch (Exception ex)
  763. {
  764. Log.Error($"SQL ERROR: {dbCmd.GetLastSqlAndParams()}", ex);
  765. throw;
  766. }
  767. }
  768. dbTrans?.Commit();
  769. }
  770. finally
  771. {
  772. dbTrans?.Dispose();
  773. }
  774. }
  775. internal static int Save<T>(this IDbCommand dbCmd, params T[] objs)
  776. {
  777. return SaveAll(dbCmd, objs);
  778. }
  779. internal static bool Save<T>(this IDbCommand dbCmd, T obj)
  780. {
  781. var modelDef = typeof(T).GetModelDefinition();
  782. var id = modelDef.GetPrimaryKey(obj);
  783. var existingRow = id != null ? dbCmd.SingleById<T>(id) : default(T);
  784. if (Equals(existingRow, default(T)))
  785. {
  786. if (modelDef.HasAutoIncrementId)
  787. {
  788. var dialectProvider = dbCmd.GetDialectProvider();
  789. var newId = dbCmd.Insert(obj, commandFilter:null, selectIdentity: true);
  790. var safeId = dialectProvider.FromDbValue(newId, modelDef.PrimaryKey.FieldType);
  791. modelDef.PrimaryKey.SetValue(obj, safeId);
  792. id = newId;
  793. }
  794. else
  795. {
  796. dbCmd.Insert(obj, commandFilter:null);
  797. }
  798. modelDef.RowVersion?.SetValue(obj, dbCmd.GetRowVersion(modelDef, id));
  799. return true;
  800. }
  801. var rowsUpdated = dbCmd.Update(obj);
  802. if (rowsUpdated == 0 && Env.StrictMode)
  803. throw new OptimisticConcurrencyException("No rows were inserted or updated");
  804. modelDef.RowVersion?.SetValue(obj, dbCmd.GetRowVersion(modelDef, id));
  805. return false;
  806. }
  807. internal static int SaveAll<T>(this IDbCommand dbCmd, IEnumerable<T> objs)
  808. {
  809. var saveRows = objs.ToList();
  810. var firstRow = saveRows.FirstOrDefault();
  811. if (Equals(firstRow, default(T))) return 0;
  812. var modelDef = typeof(T).GetModelDefinition();
  813. var firstRowId = modelDef.GetPrimaryKey(firstRow);
  814. var defaultIdValue = firstRowId?.GetType().GetDefaultValue();
  815. var idMap = defaultIdValue != null
  816. ? saveRows.Where(x => !defaultIdValue.Equals(modelDef.GetPrimaryKey(x))).ToSafeDictionary(x => modelDef.GetPrimaryKey(x))
  817. : saveRows.Where(x => modelDef.GetPrimaryKey(x) != null).ToSafeDictionary(x => modelDef.GetPrimaryKey(x));
  818. var existingRowsMap = dbCmd.SelectByIds<T>(idMap.Keys).ToDictionary(x => modelDef.GetPrimaryKey(x));
  819. var rowsAdded = 0;
  820. IDbTransaction dbTrans = null;
  821. if (dbCmd.Transaction == null)
  822. dbCmd.Transaction = dbTrans = dbCmd.Connection.BeginTransaction();
  823. try
  824. {
  825. var dialect = dbCmd.Dialect();
  826. foreach (var row in saveRows)
  827. {
  828. var id = modelDef.GetPrimaryKey(row);
  829. if (id != defaultIdValue && existingRowsMap.ContainsKey(id))
  830. {
  831. dbCmd.Update(row);
  832. }
  833. else
  834. {
  835. if (modelDef.HasAutoIncrementId)
  836. {
  837. var newId = dbCmd.Insert(row, commandFilter: null, selectIdentity: true);
  838. var safeId = dialect.FromDbValue(newId, modelDef.PrimaryKey.FieldType);
  839. modelDef.PrimaryKey.SetValue(row, safeId);
  840. id = newId;
  841. }
  842. else
  843. {
  844. dbCmd.Insert(row, commandFilter: null);
  845. }
  846. rowsAdded++;
  847. }
  848. modelDef.RowVersion?.SetValue(row, dbCmd.GetRowVersion(modelDef, id));
  849. }
  850. dbTrans?.Commit();
  851. }
  852. finally
  853. {
  854. dbTrans?.Dispose();
  855. if (dbCmd.Transaction == dbTrans)
  856. dbCmd.Transaction = null;
  857. }
  858. return rowsAdded;
  859. }
  860. internal static void SaveAllReferences<T>(this IDbCommand dbCmd, T instance)
  861. {
  862. var modelDef = ModelDefinition<T>.Definition;
  863. var pkValue = modelDef.PrimaryKey.GetValue(instance);
  864. var fieldDefs = modelDef.AllFieldDefinitionsArray.Where(x => x.IsReference);
  865. bool updateInstance = false;
  866. foreach (var fieldDef in fieldDefs)
  867. {
  868. var listInterface = fieldDef.FieldType.GetTypeWithGenericInterfaceOf(typeof(IList<>));
  869. if (listInterface != null)
  870. {
  871. var refType = listInterface.GetGenericArguments()[0];
  872. var refModelDef = refType.GetModelDefinition();
  873. var refField = modelDef.GetRefFieldDef(refModelDef, refType);
  874. var results = (IEnumerable)fieldDef.GetValue(instance);
  875. if (results != null)
  876. {
  877. foreach (var oRef in results)
  878. {
  879. refField.SetValue(oRef, pkValue);
  880. }
  881. dbCmd.CreateTypedApi(refType).SaveAll(results);
  882. }
  883. }
  884. else
  885. {
  886. var refType = fieldDef.FieldType;
  887. var refModelDef = refType.GetModelDefinition();
  888. var refSelf = modelDef.GetSelfRefFieldDefIfExists(refModelDef, fieldDef);
  889. var result = fieldDef.GetValue(instance);
  890. var refField = refSelf == null
  891. ? modelDef.GetRefFieldDef(refModelDef, refType)
  892. : modelDef.GetRefFieldDefIfExists(refModelDef);
  893. if (result != null)
  894. {
  895. if (refField != null && refSelf == null)
  896. refField.SetValue(result, pkValue);
  897. dbCmd.CreateTypedApi(refType).Save(result);
  898. //Save Self Table.RefTableId PK
  899. if (refSelf != null)
  900. {
  901. var refPkValue = refModelDef.PrimaryKey.GetValue(result);
  902. refSelf.SetValue(instance, refPkValue);
  903. updateInstance = true;
  904. }
  905. }
  906. }
  907. }
  908. if (updateInstance)
  909. {
  910. dbCmd.Update(instance);
  911. }
  912. }
  913. internal static void SaveReferences<T, TRef>(this IDbCommand dbCmd, T instance, params TRef[] refs)
  914. {
  915. var modelDef = ModelDefinition<T>.Definition;
  916. var pkValue = modelDef.PrimaryKey.GetValue(instance);
  917. var refType = typeof(TRef);
  918. var refModelDef = ModelDefinition<TRef>.Definition;
  919. var refSelf = modelDef.GetSelfRefFieldDefIfExists(refModelDef, null);
  920. foreach (var oRef in refs)
  921. {
  922. var refField = refSelf == null
  923. ? modelDef.GetRefFieldDef(refModelDef, refType)
  924. : modelDef.GetRefFieldDefIfExists(refModelDef);
  925. refField?.SetValue(oRef, pkValue);
  926. }
  927. dbCmd.SaveAll(refs);
  928. foreach (var oRef in refs)
  929. {
  930. //Save Self Table.RefTableId PK
  931. if (refSelf != null)
  932. {
  933. var refPkValue = refModelDef.PrimaryKey.GetValue(oRef);
  934. refSelf.SetValue(instance, refPkValue);
  935. dbCmd.Update(instance);
  936. }
  937. }
  938. }
  939. // Procedures
  940. internal static void ExecuteProcedure<T>(this IDbCommand dbCmd, T obj)
  941. {
  942. var dialectProvider = dbCmd.GetDialectProvider();
  943. dialectProvider.PrepareStoredProcedureStatement(dbCmd, obj);
  944. dbCmd.ExecuteNonQuery();
  945. }
  946. internal static object GetRowVersion(this IDbCommand dbCmd, ModelDefinition modelDef, object id)
  947. {
  948. var sql = RowVersionSql(dbCmd, modelDef, id);
  949. var to = dbCmd.GetDialectProvider().FromDbRowVersion(modelDef.RowVersion.FieldType, dbCmd.Scalar<object>(sql));
  950. if (to is ulong u && modelDef.RowVersion.ColumnType == typeof(byte[]))
  951. return BitConverter.GetBytes(u);
  952. return to ?? modelDef.RowVersion.ColumnType.GetDefaultValue();
  953. }
  954. internal static string RowVersionSql(this IDbCommand dbCmd, ModelDefinition modelDef, object id)
  955. {
  956. var dialectProvider = dbCmd.GetDialectProvider();
  957. var idParamString = dialectProvider.GetParam();
  958. var sql = $"SELECT {dialectProvider.GetRowVersionSelectColumn(modelDef.RowVersion)} " +
  959. $"FROM {dialectProvider.GetQuotedTableName(modelDef)} " +
  960. $"WHERE {dialectProvider.GetQuotedColumnName(modelDef.PrimaryKey.FieldName)} = {idParamString}";
  961. dbCmd.Parameters.Clear();
  962. var idParam = dbCmd.CreateParameter();
  963. idParam.Direction = ParameterDirection.Input;
  964. idParam.ParameterName = idParamString;
  965. dialectProvider.SetParamValue(idParam, id, modelDef.PrimaryKey.ColumnType, modelDef.PrimaryKey);
  966. dbCmd.Parameters.Add(idParam);
  967. return sql;
  968. }
  969. }
  970. }