PageRenderTime 93ms CodeModel.GetById 60ms RepoModel.GetById 1ms app.codeStats 0ms

/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs

https://github.com/ngbrown/nhibernate-core
C# | 356 lines | 275 code | 43 blank | 38 comment | 37 complexity | f80346e98f382bf59f39509982301fc6 MD5 | raw file
  1. using System;
  2. using System.Collections;
  3. using System.Data.Common;
  4. using NHibernate.AdoNet;
  5. using NHibernate.Cache;
  6. using NHibernate.Collection;
  7. using NHibernate.Engine;
  8. using NHibernate.Exceptions;
  9. using NHibernate.Impl;
  10. using NHibernate.Loader.Collection;
  11. using NHibernate.Persister.Entity;
  12. using NHibernate.SqlCommand;
  13. using NHibernate.Type;
  14. using NHibernate.Util;
  15. using System.Collections.Generic;
  16. using NHibernate.SqlTypes;
  17. namespace NHibernate.Persister.Collection
  18. {
  19. /// <summary>
  20. /// Collection persister for collections of values and many-to-many associations.
  21. /// </summary>
  22. public partial class BasicCollectionPersister : AbstractCollectionPersister
  23. {
  24. public BasicCollectionPersister(Mapping.Collection collection, ICacheConcurrencyStrategy cache, ISessionFactoryImplementor factory)
  25. : base(collection, cache, factory) { }
  26. public override bool CascadeDeleteEnabled
  27. {
  28. get { return false; }
  29. }
  30. public override bool IsOneToMany
  31. {
  32. get { return false; }
  33. }
  34. public override bool IsManyToMany
  35. {
  36. get { return ElementType.IsEntityType; }
  37. }
  38. /// <summary>
  39. /// Generate the SQL DELETE that deletes all rows
  40. /// </summary>
  41. /// <returns></returns>
  42. protected override SqlCommandInfo GenerateDeleteString()
  43. {
  44. SqlDeleteBuilder delete = new SqlDeleteBuilder(Factory.Dialect, Factory)
  45. .SetTableName(qualifiedTableName)
  46. .SetIdentityColumn(KeyColumnNames, KeyType);
  47. if (HasWhere)
  48. delete.AddWhereFragment(sqlWhereString);
  49. if (Factory.Settings.IsCommentsEnabled)
  50. delete.SetComment("delete collection " + Role);
  51. return delete.ToSqlCommandInfo();
  52. }
  53. /// <summary>
  54. /// Generate the SQL INSERT that creates a new row
  55. /// </summary>
  56. /// <returns></returns>
  57. protected override SqlCommandInfo GenerateInsertRowString()
  58. {
  59. SqlInsertBuilder insert = new SqlInsertBuilder(Factory)
  60. .SetTableName(qualifiedTableName)
  61. .AddColumns(KeyColumnNames, null, KeyType);
  62. if (hasIdentifier)
  63. insert.AddColumns(new string[] {IdentifierColumnName}, null, IdentifierType);
  64. if (HasIndex)
  65. insert.AddColumns(IndexColumnNames, indexColumnIsSettable, IndexType);
  66. if (Factory.Settings.IsCommentsEnabled)
  67. insert.SetComment("insert collection row " + Role);
  68. insert.AddColumns(ElementColumnNames, elementColumnIsSettable, ElementType);
  69. return insert.ToSqlCommandInfo();
  70. }
  71. /// <summary>
  72. /// Generate the SQL UPDATE that updates a row
  73. /// </summary>
  74. /// <returns></returns>
  75. protected override SqlCommandInfo GenerateUpdateRowString()
  76. {
  77. SqlUpdateBuilder update = new SqlUpdateBuilder(Factory.Dialect, Factory)
  78. .SetTableName(qualifiedTableName)
  79. .AddColumns(ElementColumnNames, elementColumnIsSettable, ElementType);
  80. if (hasIdentifier)
  81. {
  82. update.AddWhereFragment(new string[] { IdentifierColumnName }, IdentifierType, " = ");
  83. }
  84. else if (HasIndex && !indexContainsFormula)
  85. {
  86. update.AddWhereFragment(KeyColumnNames, KeyType, " = ")
  87. .AddWhereFragment(IndexColumnNames, IndexType, " = ");
  88. }
  89. else
  90. {
  91. string[] cnames = ArrayHelper.Join(KeyColumnNames, ElementColumnNames, elementColumnIsInPrimaryKey);
  92. SqlType[] ctypes = ArrayHelper.Join(KeyType.SqlTypes(Factory), ElementType.SqlTypes(Factory), elementColumnIsInPrimaryKey);
  93. update.AddWhereFragment(cnames, ctypes, " = ");
  94. }
  95. if (Factory.Settings.IsCommentsEnabled)
  96. update.SetComment("update collection row " + Role);
  97. return update.ToSqlCommandInfo();
  98. }
  99. /// <inheritdoc />
  100. protected override SqlCommandInfo GenerateDeleteRowString(bool[] columnNullness)
  101. {
  102. var delete = new SqlDeleteBuilder(Factory.Dialect, Factory);
  103. delete.SetTableName(qualifiedTableName);
  104. if (hasIdentifier)
  105. {
  106. delete.AddWhereFragment(new[] { IdentifierColumnName }, IdentifierType, " = ");
  107. }
  108. else
  109. {
  110. var useIndex = HasIndex && !indexContainsFormula;
  111. var additionalFilterType = useIndex ? IndexType : ElementType;
  112. var additionalFilterColumns = useIndex ? IndexColumnNames : ElementColumnNames;
  113. var includes = useIndex ? null : Combine(elementColumnIsInPrimaryKey, columnNullness);
  114. var cnames = includes == null
  115. ? ArrayHelper.Join(KeyColumnNames, additionalFilterColumns)
  116. : ArrayHelper.Join(KeyColumnNames, additionalFilterColumns, includes);
  117. var ctypes = includes == null
  118. ? ArrayHelper.Join(KeyType.SqlTypes(Factory), additionalFilterType.SqlTypes(Factory))
  119. : ArrayHelper.Join(KeyType.SqlTypes(Factory), additionalFilterType.SqlTypes(Factory), includes);
  120. delete.AddWhereFragment(cnames, ctypes, " = ");
  121. if (columnNullness != null)
  122. {
  123. for (var i = 0; i < columnNullness.Length; i++)
  124. {
  125. if (columnNullness[i])
  126. continue;
  127. delete.AddWhereFragment($"{additionalFilterColumns[i]} is null");
  128. }
  129. }
  130. }
  131. if (Factory.Settings.IsCommentsEnabled)
  132. delete.SetComment("delete collection row " + Role);
  133. return delete.ToSqlCommandInfo();
  134. }
  135. public override bool ConsumesEntityAlias()
  136. {
  137. return false;
  138. }
  139. public override bool ConsumesCollectionAlias()
  140. {
  141. return true;
  142. }
  143. protected override int DoUpdateRows(object id, IPersistentCollection collection, ISessionImplementor session)
  144. {
  145. if (ArrayHelper.IsAllFalse(elementColumnIsSettable)) return 0;
  146. try
  147. {
  148. DbCommand st = null;
  149. IExpectation expectation = Expectations.AppropriateExpectation(UpdateCheckStyle);
  150. //bool callable = UpdateCallable;
  151. bool useBatch = expectation.CanBeBatched;
  152. IEnumerable entries = collection.Entries(this);
  153. int i = 0;
  154. int count = 0;
  155. foreach (object entry in entries)
  156. {
  157. if (collection.NeedsUpdating(entry, i, ElementType))
  158. {
  159. int offset = 0;
  160. if (useBatch)
  161. {
  162. if (st == null)
  163. {
  164. st =
  165. session.Batcher.PrepareBatchCommand(SqlUpdateRowString.CommandType, SqlUpdateRowString.Text,
  166. SqlUpdateRowString.ParameterTypes);
  167. }
  168. }
  169. else
  170. {
  171. st =
  172. session.Batcher.PrepareCommand(SqlUpdateRowString.CommandType, SqlUpdateRowString.Text,
  173. SqlUpdateRowString.ParameterTypes);
  174. }
  175. try
  176. {
  177. //offset += expectation.Prepare(st, Factory.ConnectionProvider.Driver);
  178. int loc = WriteElement(st, collection.GetElement(entry), offset, session);
  179. if (hasIdentifier)
  180. {
  181. WriteIdentifier(st, collection.GetIdentifier(entry, i), loc, session);
  182. }
  183. else
  184. {
  185. loc = WriteKey(st, id, loc, session);
  186. if (HasIndex && !indexContainsFormula)
  187. {
  188. WriteIndexToWhere(st, collection.GetIndex(entry, i, this), loc, session);
  189. }
  190. else
  191. {
  192. // No nullness handled on update: updates does not occurs with sets or bags, and
  193. // indexed collections allowing formula (maps) force their element columns to
  194. // not-nullable.
  195. WriteElementToWhere(st, collection.GetSnapshotElement(entry, i), null, loc, session);
  196. }
  197. }
  198. if (useBatch)
  199. {
  200. session.Batcher.AddToBatch(expectation);
  201. }
  202. else
  203. {
  204. expectation.VerifyOutcomeNonBatched(session.Batcher.ExecuteNonQuery(st), st);
  205. }
  206. }
  207. catch (Exception e)
  208. {
  209. if (useBatch)
  210. {
  211. session.Batcher.AbortBatch(e);
  212. }
  213. throw;
  214. }
  215. finally
  216. {
  217. if (!useBatch)
  218. {
  219. session.Batcher.CloseCommand(st, null);
  220. }
  221. }
  222. count++;
  223. }
  224. i++;
  225. }
  226. return count;
  227. }
  228. catch (DbException sqle)
  229. {
  230. throw ADOExceptionHelper.Convert(SQLExceptionConverter, sqle,
  231. "could not update collection rows: " + MessageHelper.CollectionInfoString(this, collection, id, session),
  232. SqlUpdateRowString.Text);
  233. }
  234. }
  235. public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo)
  236. {
  237. // we need to determine the best way to know that two joinables
  238. // represent a single many-to-many...
  239. if (rhs != null && IsManyToMany && !rhs.IsCollection)
  240. {
  241. IAssociationType elementType = (IAssociationType) ElementType;
  242. if (rhs.Equals(elementType.GetAssociatedJoinable(Factory)))
  243. {
  244. return ManyToManySelectFragment(rhs, rhsAlias, lhsAlias, collectionSuffix, elementType);
  245. }
  246. }
  247. return includeCollectionColumns ? SelectFragment(lhsAlias, collectionSuffix) : string.Empty;
  248. }
  249. private string ManyToManySelectFragment(
  250. IJoinable rhs,
  251. string rhsAlias,
  252. string lhsAlias,
  253. string collectionSuffix,
  254. IAssociationType elementType)
  255. {
  256. SelectFragment frag = GenerateSelectFragment(lhsAlias, collectionSuffix);
  257. // We need to select in the associated entity table instead of taking the collection actual element,
  258. // because filters can be applied to the entity table outer join. In such case, we need to return null
  259. // for filtered-out elements. (It is tempting to switch to an inner join and just use
  260. // SelectFragment(lhsAlias, collectionSuffix) for many-to-many too, but this would hinder the proper
  261. // handling of the not-found feature.)
  262. var elementColumnNames = string.IsNullOrEmpty(elementType.RHSUniqueKeyPropertyName)
  263. ? rhs.KeyColumnNames
  264. // rhs is the entity persister, it does not handle being referenced through an unique key by a
  265. // collection and always yield its identifier columns as KeyColumnNames. We need to resolve the
  266. // key columns instead.
  267. // 6.0 TODO: consider breaking again that IJoinable.SelectFragment interface for transmitting
  268. // the OuterJoinableAssociation instead of its Joinable property. This would allow to get the
  269. // adequate columns directly instead of re-computing them.
  270. : ((IPropertyMapping) rhs).ToColumns(elementType.RHSUniqueKeyPropertyName);
  271. frag.AddColumns(rhsAlias, elementColumnNames, elementColumnAliases);
  272. AppendIndexColumns(frag, lhsAlias);
  273. AppendIdentifierColumns(frag, lhsAlias);
  274. return frag.ToSqlStringFragment(false);
  275. }
  276. /// <summary>
  277. /// Create the <see cref="CollectionLoader" />
  278. /// </summary>
  279. protected override ICollectionInitializer CreateCollectionInitializer(IDictionary<string, IFilter> enabledFilters)
  280. {
  281. return BatchingCollectionInitializer.CreateBatchingCollectionInitializer(this, batchSize, Factory, enabledFilters);
  282. }
  283. public override SqlString FromJoinFragment(string alias, bool innerJoin, bool includeSubclasses)
  284. {
  285. return SqlString.Empty;
  286. }
  287. public override SqlString WhereJoinFragment(string alias, bool innerJoin, bool includeSubclasses)
  288. {
  289. return SqlString.Empty;
  290. }
  291. protected override ICollectionInitializer CreateSubselectInitializer(SubselectFetch subselect, ISessionImplementor session)
  292. {
  293. return
  294. new SubselectCollectionLoader(this, subselect.ToSubselectString(CollectionType.LHSPropertyName), subselect.Result,
  295. subselect.QueryParameters, session.Factory,
  296. session.EnabledFilters);
  297. }
  298. #region NH Specific
  299. protected override SqlCommandInfo GenerateIdentityInsertRowString()
  300. {
  301. // NH specific to manage identity for id-bag (NH-364)
  302. SqlInsertBuilder insert = identityDelegate.PrepareIdentifierGeneratingInsert();
  303. insert.SetTableName(qualifiedTableName).AddColumns(KeyColumnNames, null, KeyType);
  304. if (HasIndex)
  305. insert.AddColumns(IndexColumnNames, null, IndexType);
  306. insert.AddColumns(ElementColumnNames, elementColumnIsSettable, ElementType);
  307. if (Factory.Settings.IsCommentsEnabled)
  308. insert.SetComment("insert collection row " + Role);
  309. return insert.ToSqlCommandInfo();
  310. }
  311. #endregion
  312. }
  313. }