public object PerformInsert(SqlCommandInfo insertSQL, ISessionImplementor session, IBinder binder)
		{
			try
			{
				// prepare and execute the insert
				IDbCommand insert = session.Batcher.PrepareCommand(insertSQL.CommandType, insertSQL.Text, insertSQL.ParameterTypes);
				try
				{
					binder.BindValues(insert);
					session.Batcher.ExecuteNonQuery(insert);
				}
				finally
				{
					session.Batcher.CloseCommand(insert, null);
				}
			}
			catch (DbException sqle)
			{
				throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, sqle,
				                                 "could not insert: " + persister.GetInfoString(), insertSQL.Text);
			}

			SqlString selectSQL = SelectSQL;
			using (new SessionIdLoggingContext(session.SessionId)) 
			try
			{
				//fetch the generated id in a separate query
				IDbCommand idSelect = session.Batcher.PrepareCommand(CommandType.Text, selectSQL, ParametersTypes);
				try
				{
					BindParameters(session, idSelect, binder.Entity);
					IDataReader rs = session.Batcher.ExecuteReader(idSelect);
					try
					{
						return GetResult(session, rs, binder.Entity);
					}
					finally
					{
						session.Batcher.CloseReader(rs);
					}
				}
				finally
				{
					session.Batcher.CloseCommand(idSelect, null);
				}
			}
			catch (DbException sqle)
			{
				throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, sqle,
				                                 "could not retrieve generated id after insert: " + persister.GetInfoString(),
				                                 insertSQL.Text);
			}
		}
		protected internal override IDbCommand Prepare(SqlCommandInfo insertSQL, ISessionImplementor session)
		{
			IDbCommand command = session.Batcher.PrepareCommand(CommandType.Text, insertSQL.Text, insertSQL.ParameterTypes);
			//Add the output parameter
			IDbDataParameter idParameter = factory.ConnectionProvider.Driver.GenerateParameter(command, ReturnParameterName,
			                                                                                         paramType);
			driveGeneratedParamName = idParameter.ParameterName;
			idParameter.Direction = ParameterDirection.ReturnValue;

			command.Parameters.Add(idParameter);
			return command;
		}
		protected internal override IDbCommand Prepare(SqlCommandInfo insertSQL, ISessionImplementor session)
		{
			IDbCommand command = session.Batcher.PrepareCommand(CommandType.Text, insertSQL.Text, insertSQL.ParameterTypes);
			//Add the output parameter
			IDbDataParameter idParameter = factory.ConnectionProvider.Driver.GenerateParameter(command, ReturnParameterName,
			                                                                                         paramType);
			driveGeneratedParamName = idParameter.ParameterName;

            if (factory.Dialect.InsertGeneratedIdentifierRetrievalMethod == InsertGeneratedIdentifierRetrievalMethod.OutputParameter)
                idParameter.Direction = ParameterDirection.Output;
            else if (factory.Dialect.InsertGeneratedIdentifierRetrievalMethod == InsertGeneratedIdentifierRetrievalMethod.ReturnValueParameter)
                idParameter.Direction = ParameterDirection.ReturnValue;
            else
                throw new System.NotImplementedException("Unsupported InsertGeneratedIdentifierRetrievalMethod: " + factory.Dialect.InsertGeneratedIdentifierRetrievalMethod);

			command.Parameters.Add(idParameter);
			return command;
		}
 public object PerformInsert(SqlCommandInfo insertSQL, ISessionImplementor session, IBinder binder)
 {
     try
     {
         // prepare and execute the insert
         IDbCommand insert = Prepare(insertSQL, session);
         try
         {
             binder.BindValues(insert);
             return ExecuteAndExtract(insert, session);
         }
         finally
         {
             ReleaseStatement(insert, session);
         }
     }
     catch (DbException sqle)
     {
         throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, sqle,
                                          "could not insert: " + MessageHelper.InfoString(persister), insertSQL.Text);
     }
 }
 protected internal abstract IDbCommand Prepare(SqlCommandInfo insertSQL, ISessionImplementor session);
		protected SqlCommandInfo[] GenerateSQLDeleteStrings(object[] loadedState)
		{
			int span = TableSpan;
			SqlCommandInfo[] deleteStrings = new SqlCommandInfo[span];

			for (int j = span - 1; j >= 0; j--)
			{
				SqlDeleteBuilder delete = new SqlDeleteBuilder(Factory.Dialect, Factory)
					.SetTableName(GetTableName(j))
					.SetIdentityColumn(GetKeyColumns(j), IdentifierType);

				if (Factory.Settings.IsCommentsEnabled)
				{
					delete.SetComment("delete " + EntityName + " [" + j + "]");
				}

				bool[] versionability = PropertyVersionability;
				IType[] types = PropertyTypes;
				for (int i = 0; i < entityMetamodel.PropertySpan; i++)
				{
					bool include = versionability[i] &&
												 IsPropertyOfTable(i, j);

					if (include)
					{
						// this property belongs to the table and it is not specifically
						// excluded from optimistic locking by optimistic-lock="false"
						string[] _propertyColumnNames = GetPropertyColumnNames(i);
						bool[] propertyNullness = types[i].ToColumnNullness(loadedState[i], Factory);
						SqlType[] sqlt = types[i].SqlTypes(Factory);
						for (int k = 0; k < propertyNullness.Length; k++)
						{
							if (propertyNullness[k])
							{
								delete.AddWhereFragment(_propertyColumnNames[k], sqlt[k], " = ");
							}
							else
							{
								delete.AddWhereFragment(_propertyColumnNames[k] + " is null");
							}
						}
					}
				}
				deleteStrings[j] = delete.ToSqlCommandInfo();
			}

			return deleteStrings;
		}
		/// <summary>
		/// Perform an SQL DELETE
		/// </summary>
		public void Delete(object id, object version, int j, object obj, SqlCommandInfo sql, ISessionImplementor session,
											 object[] loadedState)
		{
			if (IsInverseTable(j))
			{
				return;
			}

			// NH : Only use version if lock mode is Version
			bool useVersion = j == 0 && IsVersioned && Versioning.OptimisticLock.Version == entityMetamodel.OptimisticLockMode;

			//bool callable = IsDeleteCallable(j);
			IExpectation expectation = Expectations.AppropriateExpectation(deleteResultCheckStyles[j]);
			bool useBatch = j == 0 && expectation.CanBeBatched && IsBatchable;

			if (log.IsDebugEnabled)
			{
				log.Debug("Deleting entity: " + MessageHelper.InfoString(this, id, Factory));
				if (useVersion)
				{
					log.Debug("Version: " + version);
				}
			}

			if (IsTableCascadeDeleteEnabled(j))
			{
				if (log.IsDebugEnabled)
				{
					log.Debug("delete handled by foreign key constraint: " + GetTableName(j));
				}
				return; //EARLY EXIT!
			}

			try
			{
				int index = 0;
				IDbCommand statement;
				if (useBatch)
				{
					statement = session.Batcher.PrepareBatchCommand(sql.CommandType, sql.Text, sql.ParameterTypes);
				}
				else
				{
					statement = session.Batcher.PrepareCommand(sql.CommandType, sql.Text, sql.ParameterTypes);
				}

				try
				{
					//index += expectation.Prepare(statement, factory.ConnectionProvider.Driver);

					// Do the key. The key is immutable so we can use the _current_ object state - not necessarily
					// the state at the time the delete was issued
					IdentifierType.NullSafeSet(statement, id, index, session);
					index += IdentifierColumnSpan;

					// We should use the _current_ object state (ie. after any updates that occurred during flush)
					if (useVersion)
					{
						VersionType.NullSafeSet(statement, version, index, session);
					}
					else if (entityMetamodel.OptimisticLockMode > Versioning.OptimisticLock.Version && loadedState != null)
					{
						bool[] versionability = PropertyVersionability;
						IType[] types = PropertyTypes;
						for (int i = 0; i < entityMetamodel.PropertySpan; i++)
						{
							if (IsPropertyOfTable(i, j) && versionability[i])
							{
								// this property belongs to the table and it is not specifically
								// excluded from optimistic locking by optimistic-lock="false"
								bool[] settable = types[i].ToColumnNullness(loadedState[i], Factory);

								types[i].NullSafeSet(statement, loadedState[i], index, settable, session);
								index += ArrayHelper.CountTrue(settable);
							}
						}
					}

					if (useBatch)
					{
						session.Batcher.AddToBatch(expectation);
					}
					else
					{
						Check(session.Batcher.ExecuteNonQuery(statement), id, j, expectation, statement);
					}
				}
				catch (Exception e)
				{
					if (useBatch)
					{
						session.Batcher.AbortBatch(e);
					}
					throw;
				}
				finally
				{
					if (!useBatch)
					{
						session.Batcher.CloseCommand(statement, null);
					}
				}
			}
			catch (DbException sqle)
			{
				var exceptionContext = new AdoExceptionContextInfo
				                       	{
				                       		SqlException = sqle,
				                       		Message = "could not delete: " + MessageHelper.InfoString(this, id, Factory),
				                       		Sql = sql.Text.ToString(),
				                       		EntityName = EntityName,
				                       		EntityId = id
				                       	};
				throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, exceptionContext);
			}
		}
		/// <summary> Perform an SQL UPDATE or SQL INSERT</summary>
		protected internal virtual void UpdateOrInsert(object id, object[] fields, object[] oldFields, object rowId,
			bool[] includeProperty, int j, object oldVersion, object obj, SqlCommandInfo sql, ISessionImplementor session)
		{
			if (!IsInverseTable(j))
			{
				bool isRowToUpdate;
				if (IsNullableTable(j) && oldFields != null && IsAllNull(oldFields, j))
				{
					//don't bother trying to update, we know there is no row there yet
					isRowToUpdate = false;
				}
				else if (IsNullableTable(j) && IsAllNull(fields, j))
				{
					//if all fields are null, we might need to delete existing row
					isRowToUpdate = true;
					Delete(id, oldVersion, j, obj, SqlDeleteStrings[j], session, null);
				}
				else
				{
					//there is probably a row there, so try to update
					//if no rows were updated, we will find out
					isRowToUpdate = Update(id, fields, oldFields, rowId, includeProperty, j, oldVersion, obj, sql, session);
				}

				if (!isRowToUpdate && !IsAllNull(fields, j))
				{
					// assume that the row was not there since it previously had only null
					// values, so do an INSERT instead
					//TODO: does not respect dynamic-insert
					Insert(id, fields, PropertyInsertability, j, SqlInsertStrings[j], obj, session);
				}
			}
		}
		/// <summary>
		/// Perform an SQL INSERT, and then retrieve a generated identifier.
		/// </summary>
		/// <remarks>
		/// This form is used for PostInsertIdentifierGenerator-style ids (IDENTITY, select, etc).
		/// </remarks>
		protected object Insert(object[] fields, bool[] notNull, SqlCommandInfo sql, object obj, ISessionImplementor session)
		{
			if (log.IsDebugEnabled)
			{
				log.Debug("Inserting entity: " + EntityName + " (native id)");
				if (IsVersioned)
				{
					log.Debug("Version: " + Versioning.GetVersion(fields, this));
				}
			}
			IBinder binder = new GeneratedIdentifierBinder(fields, notNull, session, obj, this);
			return identityDelegate.PerformInsert(sql, session, binder);
		}
		/// <summary>
		/// Persist an object, using a natively generated identifier
		/// </summary>
		protected object Insert(object[] fields, bool[] notNull, SqlCommandInfo sql, object obj, ISessionImplementor session)
		{
			if (log.IsDebugEnabled)
			{
				log.Debug("Inserting entity: " + ClassName + " (native id)");
				if (IsVersioned)
				{
					log.Debug("Version: " + Versioning.GetVersion(fields, this));
				}
			}

			try
			{
				SqlString insertSelectSQL = null;

				if (sql.CommandType == CommandType.Text)
				{
					insertSelectSQL = Dialect.AddIdentitySelectToInsert(sql.Text, GetKeyColumns(0)[0], GetTableName(0));
				}

				if (insertSelectSQL != null)
				{
					// Use one statement to insert the row and get the generated id
					IDbCommand insertSelect = session.Batcher.PrepareCommand(CommandType.Text, insertSelectSQL, sql.ParameterTypes);
					IDataReader rs = null;
					try
					{
						// Well, it's always the first table to dehydrate, so pass 0 as the position
						Dehydrate(null, fields, notNull, propertyColumnInsertable, 0, insertSelect, session, 0);
						rs = session.Batcher.ExecuteReader(insertSelect);
						return GetGeneratedIdentity(obj, session, rs);
					}
					finally
					{
						session.Batcher.CloseCommand(insertSelect, rs);
					}
				}
				else
				{
					// Do the insert
					IDbCommand statement = session.Batcher.PrepareCommand(sql.CommandType, sql.Text, sql.ParameterTypes);
					try
					{
						// Well, it's always the first table to dehydrate, so pass 0 as the position
						Dehydrate(null, fields, notNull, propertyColumnInsertable, 0, statement, session, 0);
						session.Batcher.ExecuteNonQuery(statement);

						// Fetch the generated id in a separate query. This is done inside the first try/finally block
						// to keep the insert command open, so that the batcher does not close the connection.
						//
						// It's possible that some ADO.NET provider will not allow two open IDbCommands for the same connection,
						// in that case we'll have to rewrite the code to use some sort of lock on IBatcher.
						SqlString idselectSql = new SqlString(SqlIdentitySelect(IdentifierColumnNames[0], GetTableName(0)));
						IDbCommand idselect = session.Batcher.PrepareCommand(CommandType.Text, idselectSql, SqlTypeFactory.NoTypes);
						IDataReader rs = null;

						try
						{
							rs = session.Batcher.ExecuteReader(idselect);
							return GetGeneratedIdentity(obj, session, rs);
						}
						finally
						{
							session.Batcher.CloseCommand(idselect, rs);
						}
					}
					finally
					{
						session.Batcher.CloseCommand(statement, null);
					}
				}
			}
			catch (HibernateException)
			{
				// Do not call Convert on HibernateExceptions
				throw;
			}
			catch (Exception sqle)
			{
				throw Convert(sqle, "could not insert: " + MessageHelper.InfoString(this), sql.Text);
			}
		}
		protected void Update(
			object id,
			object[] fields,
			object[] oldFields,
			bool[] includeProperty,
			int j,
			object oldVersion,
			object obj,
			SqlCommandInfo sql,
			ISessionImplementor session)
		{
			bool useVersion = j == 0 && IsVersioned;
			IExpectation expectation = Expectations.AppropriateExpectation(updateResultCheckStyles[j]);
			bool useBatch = j == 0 && expectation.CanBeBatched && IsBatchable;
				//note: updates to joined tables can't be batched...

			if (log.IsDebugEnabled)
			{
				log.Debug("Updating entity: " + MessageHelper.InfoString(this, id));
				if (useVersion)
				{
					log.Debug("Existing version: " + oldVersion + " -> New Version: " + fields[VersionProperty]);
				}
			}

			try
			{
				IDbCommand statement = useBatch
				                       	? session.Batcher.PrepareBatchCommand(sql.CommandType, sql.Text, sql.ParameterTypes)
				                       	: session.Batcher.PrepareCommand(sql.CommandType, sql.Text, sql.ParameterTypes);
				try
				{
					int index = 0;

					//index += expectation.Prepare(statement, factory.ConnectionProvider.Driver);
					index = Dehydrate(id, fields, includeProperty, propertyColumnUpdateable, j, statement, session, index);

					// Write any appropriate versioning conditional parameters
					if (useVersion && OptimisticLockMode == OptimisticLockMode.Version)
					{
						VersionType.NullSafeSet(statement, oldVersion, index, session);
					}
					else if (OptimisticLockMode.Version < OptimisticLockMode && null != oldFields)
					{
						bool[] versionability = PropertyVersionability;
						bool[] includeOldField = OptimisticLockMode == OptimisticLockMode.All
						                         	? PropertyUpdateability
						                         	: includeProperty;

						for (int i = 0; i < entityMetamodel.PropertySpan; i++)
						{
							bool include = includeOldField[i] &&
							               IsPropertyOfTable(i, j) &&
							               versionability[i];
							if (include)
							{
								if (!PropertyTypes[i].IsDatabaseNull(oldFields[i]))
								{
									PropertyTypes[i].NullSafeSet(statement, oldFields[i], index, session);
									index += GetPropertyColumnSpan(i);
								}
							}
						}
					}

					if (useBatch)
					{
						session.Batcher.AddToBatch(expectation);
					}
					else
					{
						Check(session.Batcher.ExecuteNonQuery(statement), id, j, expectation, statement);
					}
				}
				catch (Exception e)
				{
					if (useBatch)
					{
						session.Batcher.AbortBatch(e);
					}

					throw;
				}
				finally
				{
					if (!useBatch)
					{
						session.Batcher.CloseCommand(statement, null);
					}
				}
			}
			catch (HibernateException)
			{
				// Do not call Convert on HibernateExceptions
				throw;
			}
			catch (Exception sqle)
			{
				throw Convert(sqle, "could not update: " + MessageHelper.InfoString(this, id), sql.Text);
			}
		}
		public void Update(
			object id,
			object[] fields,
			int[] dirtyFields,
			bool hasDirtyCollection,
			object[] oldFields,
			object oldVersion,
			object obj,
			ISessionImplementor session)
		{
			bool[] tableUpdateNeeded = GetTableUpdateNeeded(dirtyFields, hasDirtyCollection);
			int span = TableSpan;
			bool[] propsToUpdate;
			SqlCommandInfo[] updateStrings;

			if (entityMetamodel.IsDynamicUpdate && dirtyFields != null)
			{
				// For the case of dynamic-update="true", we need to generate the UPDATE SQL
				propsToUpdate = GetPropertiesToUpdate(dirtyFields, hasDirtyCollection);
				updateStrings = new SqlCommandInfo[span];
				for (int j = 0; j < span; j++)
				{
					updateStrings[j] = tableUpdateNeeded[j]
					                   	? GenerateUpdateString(propsToUpdate, j, oldFields)
					                   	: null;
				}
			}
			else
			{
				// For the case of dynamic-update="false", or no snapshot, we use the static SQL
				updateStrings = SqlUpdateStrings;
				propsToUpdate = PropertyUpdateability;
			}

			for (int j = 0; j < span; j++)
			{
				// Now update only the tables with dirty properties (and the table with the version number)
				if (tableUpdateNeeded[j])
				{
					Update(id, fields, oldFields, propsToUpdate, j, oldVersion, obj, updateStrings[j], session);
				}
			}
		}
		public virtual void Append(SqlCommandInfo commandInfo)
		{
			resultSetsCount++;
			sqlString = sqlString.Append(commandInfo.Text).Append(";").Append(Environment.NewLine);
			types.AddRange(commandInfo.ParameterTypes);
		}
			protected internal override IDbCommand Prepare(SqlCommandInfo insertSQL, ISessionImplementor session)
			{
				return session.Batcher.PrepareCommand(CommandType.Text, insertSQL.Text, insertSQL.ParameterTypes);
			}
		public AbstractCollectionPersister(Mapping.Collection collection, ICacheConcurrencyStrategy cache, Configuration cfg,
		                                   ISessionFactoryImplementor factory)
		{
			this.factory = factory;
			this.cache = cache;
			if (factory.Settings.IsStructuredCacheEntriesEnabled)
			{
				cacheEntryStructure = collection.IsMap
				                      	? (ICacheEntryStructure) new StructuredMapCacheEntry()
				                      	: (ICacheEntryStructure) new StructuredCollectionCacheEntry();
			}
			else
			{
				cacheEntryStructure = new UnstructuredCacheEntry();
			}

			dialect = factory.Dialect;
			sqlExceptionConverter = factory.SQLExceptionConverter;
			collectionType = collection.CollectionType;
			role = collection.Role;
			entityName = collection.OwnerEntityName;
			ownerPersister = factory.GetEntityPersister(entityName);
			queryLoaderName = collection.LoaderName;
			nodeName = collection.NodeName;
			isMutable = collection.IsMutable;

			Table table = collection.CollectionTable;
			fetchMode = collection.Element.FetchMode;
			elementType = collection.Element.Type;
			isPrimitiveArray = collection.IsPrimitiveArray;
			isArray = collection.IsArray;
			subselectLoadable = collection.IsSubselectLoadable;
			qualifiedTableName = table.GetQualifiedName(dialect, factory.Settings.DefaultCatalogName, factory.Settings.DefaultSchemaName);

			int spacesSize = 1 + collection.SynchronizedTables.Count;
			spaces = new string[spacesSize];
			int ispa = 0;
			spaces[ispa++] = qualifiedTableName;
			foreach (string s in collection.SynchronizedTables)
			{
				spaces[ispa++] = s;
			}

			sqlOrderByString = collection.OrderBy;
			hasOrder = sqlOrderByString != null;
			sqlOrderByStringTemplate = hasOrder
			                           	? Template.RenderOrderByStringTemplate(sqlOrderByString, dialect,
			                           	                                       factory.SQLFunctionRegistry)
			                           	: null;
			sqlWhereString = !string.IsNullOrEmpty(collection.Where) ? '(' + collection.Where + ')' : null;
			hasWhere = sqlWhereString != null;
			sqlWhereStringTemplate = hasWhere
			                         	? Template.RenderWhereStringTemplate(sqlWhereString, dialect, factory.SQLFunctionRegistry)
			                         	: null;
			hasOrphanDelete = collection.HasOrphanDelete;
			int batch = collection.BatchSize;
			if (batch == -1)
			{
				batch = factory.Settings.DefaultBatchFetchSize;
			}
			batchSize = batch;

			isVersioned = collection.IsOptimisticLocked;

			keyType = collection.Key.Type;
			int keySpan = collection.Key.ColumnSpan;
			keyColumnNames = new string[keySpan];
			keyColumnAliases = new string[keySpan];
			int k = 0;
			foreach (Column col in collection.Key.ColumnIterator)
			{
				keyColumnNames[k] = col.GetQuotedName(dialect);
				keyColumnAliases[k] = col.GetAlias(dialect);
				k++;
			}
			ISet distinctColumns = new HashedSet();
			CheckColumnDuplication(distinctColumns, collection.Key.ColumnIterator);

			#region Element

			IValue element = collection.Element;
			if (!collection.IsOneToMany)
			{
				CheckColumnDuplication(distinctColumns, element.ColumnIterator);
			}

			string elemNode = collection.ElementNodeName;
			if (elementType.IsEntityType)
			{
				string _entityName = ((EntityType) elementType).GetAssociatedEntityName();
				elementPersister = factory.GetEntityPersister(_entityName);
				if (elemNode == null)
				{
					elemNode = cfg.GetClassMapping(_entityName).NodeName;
				}
				// NativeSQL: collect element column and auto-aliases
			}
			else
			{
				elementPersister = null;
			}
			elementNodeName = elemNode;

			int elementSpan = element.ColumnSpan;
			elementColumnAliases = new string[elementSpan];
			elementColumnNames = new string[elementSpan];
			elementFormulaTemplates = new string[elementSpan];
			elementFormulas = new string[elementSpan];
			elementColumnIsSettable = new bool[elementSpan];
			elementColumnIsInPrimaryKey = new bool[elementSpan];
			bool isPureFormula = true;
			bool hasNotNullableColumns = false;
			int j = 0;
			foreach (ISelectable selectable in element.ColumnIterator)
			{
				elementColumnAliases[j] = selectable.GetAlias(dialect);
				if (selectable.IsFormula)
				{
					Formula form = (Formula) selectable;
					elementFormulaTemplates[j] = form.GetTemplate(dialect, factory.SQLFunctionRegistry);
					elementFormulas[j] = form.FormulaString;
				}
				else
				{
					Column col = (Column) selectable;
					elementColumnNames[j] = col.GetQuotedName(dialect);
					elementColumnIsSettable[j] = true;
					elementColumnIsInPrimaryKey[j] = !col.IsNullable;
					if (!col.IsNullable)
					{
						hasNotNullableColumns = true;
					}

					isPureFormula = false;
				}
				j++;
			}
			elementIsPureFormula = isPureFormula;

			//workaround, for backward compatibility of sets with no
			//not-null columns, assume all columns are used in the
			//row locator SQL
			if (!hasNotNullableColumns)
			{
				ArrayHelper.Fill(elementColumnIsInPrimaryKey, true);
			}

			#endregion

			#region INDEX AND ROW SELECT

			hasIndex = collection.IsIndexed;
			if (hasIndex)
			{
				// NativeSQL: collect index column and auto-aliases
				IndexedCollection indexedCollection = (IndexedCollection) collection;
				indexType = indexedCollection.Index.Type;
				int indexSpan = indexedCollection.Index.ColumnSpan;
				indexColumnNames = new string[indexSpan];
				indexFormulaTemplates = new string[indexSpan];
				indexFormulas = new string[indexSpan];
				indexColumnIsSettable = new bool[indexSpan];
				indexColumnAliases = new string[indexSpan];
				bool hasFormula = false;
				int i = 0;
				foreach (ISelectable selectable in indexedCollection.Index.ColumnIterator)
				{
					indexColumnAliases[i] = selectable.GetAlias(dialect);
					if (selectable.IsFormula)
					{
						Formula indexForm = (Formula) selectable;
						indexFormulaTemplates[i] = indexForm.GetTemplate(dialect, factory.SQLFunctionRegistry);
						indexFormulas[i] = indexForm.FormulaString;
						hasFormula = true;
					}
					else
					{
						Column indexCol = (Column) selectable;
						indexColumnNames[i] = indexCol.GetQuotedName(dialect);
						indexColumnIsSettable[i] = true;
					}
					i++;
				}
				indexContainsFormula = hasFormula;
				baseIndex = indexedCollection.IsList ? ((List) indexedCollection).BaseIndex : 0;

				indexNodeName = indexedCollection.IndexNodeName;
				CheckColumnDuplication(distinctColumns, indexedCollection.Index.ColumnIterator);
			}
			else
			{
				indexContainsFormula = false;
				indexColumnIsSettable = null;
				indexFormulaTemplates = null;
				indexFormulas = null;
				indexType = null;
				indexColumnNames = null;
				indexColumnAliases = null;
				baseIndex = 0;
				indexNodeName = null;
			}

			hasIdentifier = collection.IsIdentified;
			if (hasIdentifier)
			{
				if (collection.IsOneToMany)
				{
					throw new MappingException("one-to-many collections with identifiers are not supported.");
				}
				IdentifierCollection idColl = (IdentifierCollection) collection;
				identifierType = idColl.Identifier.Type;

				Column col = null;
				foreach (Column column in idColl.Identifier.ColumnIterator)
				{
					col = column;
					break;
				}

				identifierColumnName = col.GetQuotedName(dialect);
				identifierColumnAlias = col.GetAlias(dialect);
				identifierGenerator =
					idColl.Identifier.CreateIdentifierGenerator(factory.Dialect, factory.Settings.DefaultCatalogName,
					                                            factory.Settings.DefaultSchemaName, null);
				// NH see : identityDelegate declaration
				IPostInsertIdentifierGenerator pig = (identifierGenerator as IPostInsertIdentifierGenerator);
				if (pig != null)
				{
					identityDelegate = pig.GetInsertGeneratedIdentifierDelegate(this, Factory, UseGetGeneratedKeys());
				}
				else
				{
					identityDelegate = null;
				}

				CheckColumnDuplication(distinctColumns, idColl.Identifier.ColumnIterator);
			}
			else
			{
				identifierType = null;
				identifierColumnName = null;
				identifierColumnAlias = null;
				identifierGenerator = null;
				identityDelegate = null;
			}

			#endregion

			#region GENERATE THE SQL

			// NH Different behavior : for the Insert SQL we are managing isPostInsertIdentifier (not supported in H3.2.5) 
			if (collection.CustomSQLInsert == null)
			{
				if (!IsIdentifierAssignedByInsert)
				{
					sqlInsertRowString = GenerateInsertRowString();
				}
				else
				{
					sqlInsertRowString = GenerateIdentityInsertRowString();
				}
				insertCallable = false;
				insertCheckStyle = ExecuteUpdateResultCheckStyle.Count;
			}
			else
			{
				SqlType[] parmsTypes = GenerateInsertRowString().ParameterTypes;
				sqlInsertRowString = new SqlCommandInfo(collection.CustomSQLInsert, parmsTypes);
				insertCallable = collection.IsCustomInsertCallable;
				insertCheckStyle = collection.CustomSQLInsertCheckStyle
				                   ?? ExecuteUpdateResultCheckStyle.DetermineDefault(collection.CustomSQLInsert, insertCallable);
			}

			sqlUpdateRowString = GenerateUpdateRowString();
			if (collection.CustomSQLUpdate == null)
			{
				updateCallable = false;
				updateCheckStyle = ExecuteUpdateResultCheckStyle.Count;
			}
			else
			{
				sqlUpdateRowString = new SqlCommandInfo(collection.CustomSQLUpdate, sqlUpdateRowString.ParameterTypes);
				updateCallable = collection.IsCustomUpdateCallable;
				updateCheckStyle = collection.CustomSQLUpdateCheckStyle
				                   ?? ExecuteUpdateResultCheckStyle.DetermineDefault(collection.CustomSQLUpdate, updateCallable);
			}

			sqlDeleteRowString = GenerateDeleteRowString();
			if (collection.CustomSQLDelete == null)
			{
				deleteCallable = false;
				deleteCheckStyle = ExecuteUpdateResultCheckStyle.None;
			}
			else
			{
				sqlDeleteRowString = new SqlCommandInfo(collection.CustomSQLDelete, sqlDeleteRowString.ParameterTypes);
				deleteCallable = collection.IsCustomDeleteCallable;
				deleteCheckStyle = ExecuteUpdateResultCheckStyle.None;
			}

			sqlDeleteString = GenerateDeleteString();
			if (collection.CustomSQLDeleteAll == null)
			{
				deleteAllCallable = false;
				deleteAllCheckStyle = ExecuteUpdateResultCheckStyle.None;
			}
			else
			{
				sqlDeleteString = new SqlCommandInfo(collection.CustomSQLDeleteAll, sqlDeleteString.ParameterTypes);
				deleteAllCallable = collection.IsCustomDeleteAllCallable;
				deleteAllCheckStyle = ExecuteUpdateResultCheckStyle.None;
			}

		    isCollectionIntegerIndex = collection.IsIndexed && !collection.IsMap;
			sqlDetectRowByIndexString = GenerateDetectRowByIndexString();
			sqlDetectRowByElementString = GenerateDetectRowByElementString();
			sqlSelectRowByIndexString = GenerateSelectRowByIndexString();

			LogStaticSQL();

			#endregion

			isLazy = collection.IsLazy;
			isExtraLazy = collection.ExtraLazy;
			isInverse = collection.IsInverse;

			if (collection.IsArray)
			{
				elementClass = ((Array) collection).ElementClass;
			}
			else
			{
				// for non-arrays, we don't need to know the element class
				elementClass = null;
			}

			if (elementType.IsComponentType)
			{
				elementPropertyMapping =
					new CompositeElementPropertyMapping(elementColumnNames, elementFormulaTemplates,
					                                    (IAbstractComponentType) elementType, factory);
			}
			else if (!elementType.IsEntityType)
			{
				elementPropertyMapping = new ElementPropertyMapping(elementColumnNames, elementType);
			}
			else
			{
				elementPropertyMapping = elementPersister as IPropertyMapping;
				if (elementPropertyMapping == null)
				{
					elementPropertyMapping = new ElementPropertyMapping(elementColumnNames, elementType);
				}
			}

			// Handle any filters applied to this collection
			filterHelper = new FilterHelper(collection.FilterMap, dialect, factory.SQLFunctionRegistry);

			// Handle any filters applied to this collection for many-to-many
			manyToManyFilterHelper = new FilterHelper(collection.ManyToManyFilterMap, dialect, factory.SQLFunctionRegistry);
			manyToManyWhereString = !string.IsNullOrEmpty(collection.ManyToManyWhere)
			                        	? "( " + collection.ManyToManyWhere + " )"
			                        	: null;
			manyToManyWhereTemplate = manyToManyWhereString == null
			                          	? null
			                          	: Template.RenderWhereStringTemplate(manyToManyWhereString, factory.Dialect,
			                          	                                     factory.SQLFunctionRegistry);
			manyToManyOrderByString = collection.ManyToManyOrdering;
			manyToManyOrderByTemplate = manyToManyOrderByString == null
			                            	? null
			                            	: Template.RenderOrderByStringTemplate(manyToManyOrderByString, factory.Dialect,
			                            	                                       factory.SQLFunctionRegistry);
			InitCollectionPropertyMap();
		}
        public void Update(object id, object[] fields, int[] dirtyFields, bool hasDirtyCollection,
			object[] oldFields, object oldVersion, object obj, object rowId, ISessionImplementor session)
        {
            //note: dirtyFields==null means we had no snapshot, and we couldn't get one using select-before-update
            //	  oldFields==null just means we had no snapshot to begin with (we might have used select-before-update to get the dirtyFields)

            bool[] tableUpdateNeeded = GetTableUpdateNeeded(dirtyFields, hasDirtyCollection);
            int span = TableSpan;
            bool[] propsToUpdate;
            SqlCommandInfo[] updateStrings;

            if (entityMetamodel.IsDynamicUpdate && dirtyFields != null)
            {
                // For the case of dynamic-update="true", we need to generate the UPDATE SQL
                propsToUpdate = GetPropertiesToUpdate(dirtyFields, hasDirtyCollection);
                // don't need to check laziness (dirty checking algorithm handles that)
                updateStrings = new SqlCommandInfo[span];
                for (int j = 0; j < span; j++)
                {
                    updateStrings[j] = tableUpdateNeeded[j]
                                                            ? GenerateUpdateString(propsToUpdate, j, oldFields, j == 0 && rowId != null)
                                                            : null;
                }
            }
            else
            {
                // For the case of dynamic-update="false", or no snapshot, we use the static SQL
                updateStrings = GetUpdateStrings(rowId != null, HasUninitializedLazyProperties(obj, session.EntityMode));
                propsToUpdate = GetPropertyUpdateability(obj, session.EntityMode);
            }

            for (int j = 0; j < span; j++)
            {
                // Now update only the tables with dirty properties (and the table with the version number)
                if (tableUpdateNeeded[j])
                {
                    UpdateOrInsert(id, fields, oldFields, j == 0 ? rowId : null, propsToUpdate, j, oldVersion, obj, updateStrings[j], session);
                }
            }
        }
		public virtual void PostInstantiate()
		{
			int tableSpan = TableSpan;

			sqlInsertStrings = new SqlCommandInfo[tableSpan];
			sqlDeleteStrings = new SqlCommandInfo[tableSpan];
			sqlUpdateStrings = new SqlCommandInfo[tableSpan];

			for (int j = 0; j < tableSpan; j++)
			{
				SqlCommandInfo defaultInsert = GenerateInsertString(false, PropertyInsertability, j);
				sqlInsertStrings[j] = customSQLInsert[j] == null
				                      	? defaultInsert
				                      	: new SqlCommandInfo(customSQLInsert[j], defaultInsert.ParameterTypes);
				SqlCommandInfo defaultUpdate = GenerateUpdateString(PropertyUpdateability, j, null);
				sqlUpdateStrings[j] = customSQLUpdate[j] == null
				                      	? defaultUpdate
				                      	: new SqlCommandInfo(customSQLUpdate[j], defaultUpdate.ParameterTypes);
				SqlCommandInfo defaultDelete = GenerateDeleteString(j);
				sqlDeleteStrings[j] = customSQLDelete[j] == null
				                      	? defaultDelete
				                      	: new SqlCommandInfo(customSQLDelete[j], defaultDelete.ParameterTypes);
			}

			sqlIdentityInsertString = IsIdentifierAssignedByInsert
			                          	? GenerateIdentityInsertString(PropertyInsertability)
			                          	: null;

			sqlSnapshotSelectString = GenerateSnapshotSelectString();
			sqlVersionSelectString = GenerateSelectVersionString();
			if (HasInsertGeneratedProperties)
			{
				sqlInsertGeneratedValuesSelectString = GenerateInsertGeneratedValuesSelectString();
			}
			if (HasUpdateGeneratedProperties)
			{
				sqlUpdateGeneratedValuesSelectString = GenerateUpdateGeneratedValuesSelectString();
			}

			// This is used to determine updates for objects that came in via update()
			tableHasColumns = new bool[sqlUpdateStrings.Length];
			for (int j = 0; j < sqlUpdateStrings.Length; j++)
			{
				tableHasColumns[j] = sqlUpdateStrings[j] != null;
			}

			CreateLoaders();
			CreateUniqueKeyLoaders();
			CreateQueryLoader();
			InitLockers();
		}
		/// <summary>
		/// Perform an SQL INSERT.
		/// </summary>
		/// <remarks>
		/// This for is used for all non-root tables as well as the root table
		/// in cases where the identifier value is known before the insert occurs.
		/// </remarks>
		protected void Insert(object id, object[] fields, bool[] notNull, int j,
			SqlCommandInfo sql, object obj, ISessionImplementor session)
		{
			if (IsInverseTable(j))
			{
				return;
			}

			//note: it is conceptually possible that a UserType could map null to
			//	  a non-null value, so the following is arguable:
			if (IsNullableTable(j) && IsAllNull(fields, j))
			{
				return;
			}

			if (log.IsDebugEnabled)
			{
				log.Debug("Inserting entity: " + MessageHelper.InfoString(this, id, Factory));
				if (j == 0 && IsVersioned)
				{
					log.Debug("Version: " + Versioning.GetVersion(fields, this));
				}
			}

			IExpectation expectation = Expectations.AppropriateExpectation(insertResultCheckStyles[j]);
			//bool callable = IsInsertCallable(j);
			// we can't batch joined inserts, *especially* not if it is an identity insert;
			// nor can we batch statements where the expectation is based on an output param
			bool useBatch = j == 0 && expectation.CanBeBatched;

			try
			{
				// Render the SQL query
				IDbCommand insertCmd = useBatch
																? session.Batcher.PrepareBatchCommand(sql.CommandType, sql.Text, sql.ParameterTypes)
																: session.Batcher.PrepareCommand(sql.CommandType, sql.Text, sql.ParameterTypes);

				try
				{
					int index = 0;
					//index += expectation.Prepare(insertCmd, factory.ConnectionProvider.Driver);

					// Write the values of the field onto the prepared statement - we MUST use the
					// state at the time the insert was issued (cos of foreign key constraints).
					// Not necessarily the obect's current state

					Dehydrate(id, fields, null, notNull, propertyColumnInsertable, j, insertCmd, session, index);

					if (useBatch)
					{
						session.Batcher.AddToBatch(expectation);
					}
					else
					{
						expectation.VerifyOutcomeNonBatched(session.Batcher.ExecuteNonQuery(insertCmd), insertCmd);
					}
				}
				catch (Exception e)
				{
					if (useBatch)
					{
						session.Batcher.AbortBatch(e);
					}
					throw;
				}
				finally
				{
					if (!useBatch)
					{
						session.Batcher.CloseCommand(insertCmd, null);
					}
				}
			}
			catch (DbException sqle)
			{
				var exceptionContext = new AdoExceptionContextInfo
				                       	{
				                       		SqlException = sqle,
				                       		Message = "could not insert: " + MessageHelper.InfoString(this, id),
				                       		Sql = sql.ToString(),
				                       		EntityName = EntityName,
				                       		EntityId = id
				                       	};
				throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, exceptionContext);
			}
		}
		/// <summary>
		/// Delete an object.
		/// </summary>
		public void Delete(object id, object version, int j, object obj, SqlCommandInfo sql, ISessionImplementor session,
		                   object[] loadedState)
		{
			bool useVersion = j == 0 && IsVersioned;
			IExpectation expectation = Expectations.AppropriateExpectation(deleteResultCheckStyles[j]);
			bool useBatch = j == 0 && expectation.CanBeBatched && IsBatchable;

			if (log.IsDebugEnabled)
			{
				log.Debug("Deleting entity: " + MessageHelper.InfoString(this, id));
				if (useVersion)
				{
					log.Debug("Version: " + version);
				}
			}

			try
			{
				IDbCommand statement;
				if (useBatch)
				{
					statement = session.Batcher.PrepareBatchCommand(sql.CommandType, sql.Text, sql.ParameterTypes);
				}
				else
				{
					statement = session.Batcher.PrepareCommand(sql.CommandType, sql.Text, sql.ParameterTypes);
				}

				try
				{
					int index = 0;

					//index += expectation.Prepare(statement, factory.ConnectionProvider.Driver);

					// Do the key. The key is immutable so we can use the _current_ object state
					IdentifierType.NullSafeSet(statement, id, index, session);
					index += IdentifierColumnSpan;

					if (useVersion)
					{
						VersionType.NullSafeSet(statement, version, index, session);
					}
					else if (entityMetamodel.OptimisticLockMode > OptimisticLockMode.Version && loadedState != null)
					{
						IType[] types = PropertyTypes;
						bool[] versionability = PropertyVersionability;
						for (int i = 0; i < entityMetamodel.PropertySpan; i++)
						{
							if (IsPropertyOfTable(i, j) && versionability[i] && !types[i].IsDatabaseNull(loadedState[i]))
							{
								types[i].NullSafeSet(statement, loadedState[i], index, session);
								index += GetPropertyColumnSpan(i);
							}
						}
					}

					if (useBatch)
					{
						session.Batcher.AddToBatch(expectation);
					}
					else
					{
						Check(session.Batcher.ExecuteNonQuery(statement), id, j, expectation, statement);
					}
				}
				catch (Exception e)
				{
					if (useBatch)
					{
						session.Batcher.AbortBatch(e);
					}
					throw;
				}
				finally
				{
					if (!useBatch)
					{
						session.Batcher.CloseCommand(statement, null);
					}
				}
			}
			catch (HibernateException)
			{
				// Do not call Convert on HibernateExceptions
				throw;
			}
			catch (Exception sqle)
			{
				throw Convert(sqle, "could not delete: " + MessageHelper.InfoString(this, id), sql.Text);
			}
		}
		protected bool Update(object id, object[] fields, object[] oldFields, object rowId, bool[] includeProperty, int j,
			object oldVersion, object obj, SqlCommandInfo sql, ISessionImplementor session)
		{
			bool useVersion = j == 0 && IsVersioned;
			IExpectation expectation = Expectations.AppropriateExpectation(updateResultCheckStyles[j]);
			//bool callable = IsUpdateCallable(j);
			bool useBatch = j == 0 && expectation.CanBeBatched && IsBatchable; //note: updates to joined tables can't be batched...

			if (log.IsDebugEnabled)
			{
				log.Debug("Updating entity: " + MessageHelper.InfoString(this, id, Factory));
				if (useVersion)
				{
					log.Debug("Existing version: " + oldVersion + " -> New Version: " + fields[VersionProperty]);
				}
			}

			try
			{
				int index = 0;
				IDbCommand statement = useBatch
																? session.Batcher.PrepareBatchCommand(sql.CommandType, sql.Text, sql.ParameterTypes)
																: session.Batcher.PrepareCommand(sql.CommandType, sql.Text, sql.ParameterTypes);
				try
				{
					//index += expectation.Prepare(statement, factory.ConnectionProvider.Driver);

					//Now write the values of fields onto the prepared statement
					index = Dehydrate(id, fields, rowId, includeProperty, propertyColumnUpdateable, j, statement, session, index);

					// Write any appropriate versioning conditional parameters
					if (useVersion && Versioning.OptimisticLock.Version == entityMetamodel.OptimisticLockMode)
					{
						if (CheckVersion(includeProperty))
							VersionType.NullSafeSet(statement, oldVersion, index, session);
					}
					else if (entityMetamodel.OptimisticLockMode > Versioning.OptimisticLock.Version && oldFields != null)
					{
						bool[] versionability = PropertyVersionability;
						bool[] includeOldField = OptimisticLockMode == Versioning.OptimisticLock.All
																			? PropertyUpdateability
																			: includeProperty;
						IType[] types = PropertyTypes;

						for (int i = 0; i < entityMetamodel.PropertySpan; i++)
						{
							bool include = includeOldField[i] &&
														 IsPropertyOfTable(i, j) &&
														 versionability[i];
							if (include)
							{
								bool[] settable = types[i].ToColumnNullness(oldFields[i], Factory);
								types[i].NullSafeSet(statement, oldFields[i], index, settable, session);
								index += ArrayHelper.CountTrue(settable);
							}
						}
					}

					if (useBatch)
					{
						session.Batcher.AddToBatch(expectation);
						return true;
					}
					else
					{
						return Check(session.Batcher.ExecuteNonQuery(statement), id, j, expectation, statement);
					}
				}
				catch (StaleStateException e)
				{
					if (useBatch)
					{
						session.Batcher.AbortBatch(e);
					}

					throw new StaleObjectStateException(EntityName, id);
				}
				catch (Exception e)
				{
					if (useBatch)
					{
						session.Batcher.AbortBatch(e);
					}

					throw;
				}
				finally
				{
					if (!useBatch)
					{
						session.Batcher.CloseCommand(statement, null);
					}
				}
			}
			catch (DbException sqle)
			{
				var exceptionContext = new AdoExceptionContextInfo
				                       	{
				                       		SqlException = sqle,
				                       		Message = "could not update: " + MessageHelper.InfoString(this, id, Factory),
				                       		Sql = sql.Text.ToString(),
																	EntityName = EntityName,
																	EntityId = id
				                       	};
				throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, exceptionContext);
			}
		}
		protected SqlCommandInfo[] GenerateDeleteStrings(object[] loadedState)
		{
			int span = TableSpan;
			SqlCommandInfo[] deleteStrings = new SqlCommandInfo[span];

			for (int j = span - 1; j >= 0; j--)
			{
				SqlDeleteBuilder delete = new SqlDeleteBuilder(Factory)
					.SetTableName(GetTableName(j))
					.SetIdentityColumn(GetKeyColumns(j), IdentifierType);

				IType[] types = PropertyTypes;

				bool[] versionability = PropertyVersionability;
				//bool[] includeInWhere = PropertyUpdateability;

				for (int i = 0; i < entityMetamodel.PropertySpan; i++)
				{
					bool include = versionability[i] &&
					               IsPropertyOfTable(i, j);

					if (include)
					{
						// this property belongs to the table and it is not specifically
						// excluded from optimistic locking by optimistic-lock="false"
						string[] propertyColumnNames = GetPropertyColumnNames(i);
						if (types[i].IsDatabaseNull(loadedState[i]))
						{
							for (int k = 0; k < propertyColumnNames.Length; k++)
							{
								delete.AddWhereFragment(propertyColumnNames[k] + " is null");
							}
						}
						else
						{
							delete.AddWhereFragment(propertyColumnNames, PropertyTypes[i], " = ");
						}
					}
				}
				deleteStrings[j] = delete.ToSqlCommandInfo();
			}

			return deleteStrings;
		}
		public void Update(object id, object[] fields, int[] dirtyFields, bool hasDirtyCollection,
			object[] oldFields, object oldVersion, object obj, object rowId, ISessionImplementor session)
		{
			//note: dirtyFields==null means we had no snapshot, and we couldn't get one using select-before-update
			//	  oldFields==null just means we had no snapshot to begin with (we might have used select-before-update to get the dirtyFields)

			bool[] tableUpdateNeeded = GetTableUpdateNeeded(dirtyFields, hasDirtyCollection);
			int span = TableSpan;
			bool[] propsToUpdate;
			SqlCommandInfo[] updateStrings;
			EntityEntry entry = session.PersistenceContext.GetEntry(obj);

			// Ensure that an immutable or non-modifiable entity is not being updated unless it is
			// in the process of being deleted.
			if (entry == null && !IsMutable)
				throw new InvalidOperationException("Updating immutable entity that is not in session yet!");
			
			if (entityMetamodel.IsDynamicUpdate && dirtyFields != null)
			{
				// For the case of dynamic-update="true", we need to generate the UPDATE SQL
				propsToUpdate = GetPropertiesToUpdate(dirtyFields, hasDirtyCollection);
				// don't need to check laziness (dirty checking algorithm handles that)
				updateStrings = new SqlCommandInfo[span];
				for (int j = 0; j < span; j++)
				{
					updateStrings[j] = tableUpdateNeeded[j]
															? GenerateUpdateString(propsToUpdate, j, oldFields, j == 0 && rowId != null)
															: null;
				}
			}
			else if (!IsModifiableEntity(entry))
			{
				// We need to generate UPDATE SQL when a non-modifiable entity (e.g., read-only or immutable)
				// needs:
				// - to have references to transient entities set to null before being deleted
				// - to have version incremented do to a "dirty" association
				// If dirtyFields == null, then that means that there are no dirty properties to
				// to be updated; an empty array for the dirty fields needs to be passed to
				// getPropertiesToUpdate() instead of null.
				propsToUpdate = this.GetPropertiesToUpdate(
					(dirtyFields == null ? ArrayHelper.EmptyIntArray : dirtyFields), hasDirtyCollection);
				
				// don't need to check laziness (dirty checking algorithm handles that)
				updateStrings = new SqlCommandInfo[span];
				for (int j = 0; j < span; j++)
				{
					updateStrings[j] = tableUpdateNeeded[j] ? GenerateUpdateString(propsToUpdate, j, oldFields, j == 0 && rowId != null) : null;
				}
			}
			else
			{
				// For the case of dynamic-update="false", or no snapshot, we use the static SQL
				updateStrings = GetUpdateStrings(rowId != null, HasUninitializedLazyProperties(obj, session.EntityMode));
				propsToUpdate = GetPropertyUpdateability(obj, session.EntityMode);
			}

			for (int j = 0; j < span; j++)
			{
				// Now update only the tables with dirty properties (and the table with the version number)
				if (tableUpdateNeeded[j])
				{
					UpdateOrInsert(id, fields, oldFields, j == 0 ? rowId : null, propsToUpdate, j, oldVersion, obj, updateStrings[j], session);
				}
			}
		}
		/// <summary>
		/// Persist an object
		/// </summary>
		protected void Insert(
			object id,
			object[] fields,
			bool[] notNull,
			int j,
			SqlCommandInfo sql,
			object obj,
			ISessionImplementor session)
		{
			if (log.IsDebugEnabled)
			{
				log.Debug("Inserting entity: " + MessageHelper.InfoString(this, id));
				if (IsVersioned)
				{
					log.Debug("Version: " + Versioning.GetVersion(fields, this));
				}
			}


			IExpectation expectation = Expectations.AppropriateExpectation(insertResultCheckStyles[j]);
			bool useBatch = j == 0 && expectation.CanBeBatched;

			try
			{
				// Render the SQL query
				IDbCommand insertCmd = useBatch
				                       	? session.Batcher.PrepareBatchCommand(sql.CommandType, sql.Text, sql.ParameterTypes)
				                       	: session.Batcher.PrepareCommand(sql.CommandType, sql.Text, sql.ParameterTypes);

				try
				{
					// Write the values of the field onto the prepared statement - we MUST use the
					// state at the time the insert was issued (cos of foreign key constraints)
					// not necessarily the obect's current state
					int index = 0;
					//index += expectation.Prepare(insertCmd, factory.ConnectionProvider.Driver);

					Dehydrate(id, fields, notNull, propertyColumnInsertable, j, insertCmd, session, index);

					if (useBatch)
					{
						session.Batcher.AddToBatch(expectation);
					}
					else
					{
						Check(session.Batcher.ExecuteNonQuery(insertCmd), id, j, expectation, insertCmd);
					}
				}
				catch (Exception e)
				{
					if (useBatch)
					{
						session.Batcher.AbortBatch(e);
					}
					throw;
				}
				finally
				{
					if (!useBatch)
					{
						session.Batcher.CloseCommand(insertCmd, null);
					}
				}
			}
			catch (HibernateException)
			{
				// Do not call Convert on HibernateExceptions
				throw;
			}
			catch (Exception sqle)
			{
				throw Convert(sqle, "could not insert: " + MessageHelper.InfoString(this, id), sql.Text);
			}
		}
		public virtual void PostInstantiate()
		{
			#region insert/update/delete SQLs

			// this section was moved from PostConstruct() method (know difference in NH)
			int joinSpan = TableSpan;
			sqlDeleteStrings = new SqlCommandInfo[joinSpan];
			sqlInsertStrings = new SqlCommandInfo[joinSpan];
			sqlUpdateStrings = new SqlCommandInfo[joinSpan];
			sqlLazyUpdateStrings = new SqlCommandInfo[joinSpan];

			sqlUpdateByRowIdString = rowIdName == null ? null : GenerateUpdateString(PropertyUpdateability, 0, true);
			sqlLazyUpdateByRowIdString = rowIdName == null ? null : GenerateUpdateString(NonLazyPropertyUpdateability, 0, true);

			for (int j = 0; j < joinSpan; j++)
			{
				SqlCommandInfo defaultInsert = GenerateInsertString(PropertyInsertability, j);
				SqlCommandInfo defaultUpdate = GenerateUpdateString(PropertyUpdateability, j, false);
				SqlCommandInfo defaultDelete = GenerateDeleteString(j);

				sqlInsertStrings[j] = customSQLInsert[j] != null
																? new SqlCommandInfo(customSQLInsert[j], defaultInsert.ParameterTypes)
																: defaultInsert;

				sqlUpdateStrings[j] = customSQLUpdate[j] != null
																? new SqlCommandInfo(customSQLUpdate[j], defaultUpdate.ParameterTypes)
																: defaultUpdate;

				// NH: in practice for lazy update de update sql is the same any way.
				sqlLazyUpdateStrings[j] = customSQLUpdate[j] != null
																		? new SqlCommandInfo(customSQLUpdate[j], defaultUpdate.ParameterTypes)
																		: GenerateUpdateString(NonLazyPropertyUpdateability, j, false);

				sqlDeleteStrings[j] = customSQLDelete[j] != null
																? new SqlCommandInfo(customSQLDelete[j], defaultDelete.ParameterTypes)
																: defaultDelete;
			}

			tableHasColumns = new bool[joinSpan];
			for (int j = 0; j < joinSpan; j++)
			{
				tableHasColumns[j] = sqlUpdateStrings[j] != null;
			}

			//select SQL
			sqlSnapshotSelectString = GenerateSnapshotSelectString();
			sqlLazySelectString = GenerateLazySelectString();
			sqlVersionSelectString = GenerateSelectVersionString();
			if (HasInsertGeneratedProperties)
			{
				sqlInsertGeneratedValuesSelectString = GenerateInsertGeneratedValuesSelectString();
			}
			if (HasUpdateGeneratedProperties)
			{
				sqlUpdateGeneratedValuesSelectString = GenerateUpdateGeneratedValuesSelectString();
			}
			if (IsIdentifierAssignedByInsert)
			{
				identityDelegate = ((IPostInsertIdentifierGenerator)IdentifierGenerator).GetInsertGeneratedIdentifierDelegate(this, Factory, UseGetGeneratedKeys());
				SqlCommandInfo defaultInsert = GenerateIdentityInsertString(PropertyInsertability);
				sqlIdentityInsertString = customSQLInsert[0] != null
																		? new SqlCommandInfo(customSQLInsert[0], defaultInsert.ParameterTypes)
																		: defaultInsert;
			}
			else
			{
				sqlIdentityInsertString = null;
			}

			LogStaticSQL();

			#endregion

			CreateLoaders();
			CreateUniqueKeyLoaders();
			CreateQueryLoader();
		}
		public AbstractCollectionPersister(Mapping.Collection collection, ICacheConcurrencyStrategy cache,
		                                   ISessionFactoryImplementor factory)
		{
			this.factory = factory;
			dialect = factory.Dialect;
			this.cache = cache;
			//sqlExceptionConverter = factory.SQLExceptionConverter;
			collectionType = collection.CollectionType;
			role = collection.Role;
			ownerClass = collection.OwnerClass;
			ownerPersister = factory.GetEntityPersister(ownerClass);
			queryLoaderName = collection.LoaderName;
			Alias alias = new Alias("__");

			sqlOrderByString = collection.OrderBy;
			hasOrder = sqlOrderByString != null;
			sqlOrderByStringTemplate = hasOrder
			                           	? Template.RenderOrderByStringTemplate(sqlOrderByString, dialect,
			                           	                                       factory.SQLFunctionRegistry) : null;

			sqlWhereString = collection.Where;
			hasWhere = sqlWhereString != null;
			sqlWhereStringTemplate = hasWhere
			                         	? Template.RenderWhereStringTemplate(sqlWhereString, dialect, factory.SQLFunctionRegistry)
			                         	: null;

			hasOrphanDelete = collection.OrphanDelete;

			batchSize = collection.BatchSize;
			isVersioned = collection.IsOptimisticLocked;

			keyType = collection.Key.Type;
			int keySpan = collection.Key.ColumnSpan;
			keyColumnNames = new string[keySpan];
			string[] keyAliases = new string[keySpan];
			int k = 0;
			foreach (Column col in collection.Key.ColumnCollection)
			{
				keyColumnNames[k] = col.GetQuotedName(dialect);
				keyAliases[k] = col.GetAlias(dialect);
				k++;
			}
			keyColumnAliases = alias.ToAliasStrings(keyAliases, dialect);
			//unquotedKeyColumnNames = StringHelper.Unquote( keyColumnAliases );
			ISet distinctColumns = new HashedSet();
			CheckColumnDuplication(distinctColumns, collection.Key.ColumnCollection);

			//isSet = collection.IsSet;
			//isSorted = collection.IsSorted;
			primitiveArray = collection.IsPrimitiveArray;
			array = collection.IsArray;
			subselectLoadable = collection.IsSubselectLoadable;

			IValue element = collection.Element;
			int elementSpan = element.ColumnSpan;
			Table table = collection.CollectionTable;
			fetchMode = element.FetchMode;
			elementType = element.Type;

			if (!collection.IsOneToMany)
			{
				CheckColumnDuplication(distinctColumns, element.ColumnCollection);
			}

			if (elementType.IsEntityType)
			{
				elementPersister = factory.GetEntityPersister(((EntityType) elementType).AssociatedClass);
			}
			else
			{
				elementPersister = null;
			}

			qualifiedTableName = table.GetQualifiedName(dialect, factory.DefaultSchema);
			elementColumnAliases = new string[elementSpan];
			elementColumnNames = new string[elementSpan];
			elementFormulaTemplates = new string[elementSpan];
			elementFormulas = new string[elementSpan];
			elementColumnIsSettable = new bool[elementSpan];
			elementColumnIsInPrimaryKey = new bool[elementSpan];
			int j = 0;
			foreach (ISelectable selectable in element.ColumnCollection)
			{
				elementColumnAliases[j] = selectable.GetAlias(dialect);
				if (selectable.IsFormula)
				{
					Formula form = (Formula) selectable;
					elementFormulaTemplates[j] = form.GetTemplate(dialect, factory.SQLFunctionRegistry);
					elementFormulas[j] = form.FormulaString;
				}
				else
				{
					Column col = (Column) selectable;
					elementColumnNames[j] = col.GetQuotedName(dialect);
					elementColumnIsSettable[j] = true;
					elementColumnIsInPrimaryKey[j] = !col.IsNullable;
				}
				j++;
			}

			hasIndex = collection.IsIndexed;

			if (hasIndex)
			{
				IndexedCollection indexedCollection = (IndexedCollection) collection;

				indexType = indexedCollection.Index.Type;
				int indexSpan = indexedCollection.Index.ColumnSpan;
				indexColumnNames = new string[indexSpan];

				string[] indexAliases = new string[indexSpan];
				int i = 0;
				foreach (Column indexCol in indexedCollection.Index.ColumnCollection)
				{
					indexAliases[i] = indexCol.GetAlias(dialect);
					indexColumnNames[i] = indexCol.GetQuotedName(dialect);
					i++;
				}
				indexColumnAliases = alias.ToAliasStrings(indexAliases, dialect);
				CheckColumnDuplication(distinctColumns, indexedCollection.Index.ColumnCollection);
			}
			else
			{
				indexType = null;
				indexColumnNames = null;
				indexColumnAliases = null;
			}

			hasIdentifier = collection.IsIdentified;

			if (hasIdentifier)
			{
				if (collection.IsOneToMany)
				{
					throw new MappingException("one-to-many collections with identifiers are not supported.");
				}
				IdentifierCollection idColl = (IdentifierCollection) collection;
				identifierType = idColl.Identifier.Type;

				Column col = null;
				foreach (Column column in idColl.Identifier.ColumnCollection)
				{
					col = column;
					break;
				}

				identifierColumnName = col.GetQuotedName(dialect);
				identifierColumnAlias = alias.ToAliasString(col.GetAlias(dialect), dialect);
				identifierGenerator = idColl.Identifier.CreateIdentifierGenerator(dialect);
				CheckColumnDuplication(distinctColumns, idColl.Identifier.ColumnCollection);
			}
			else
			{
				identifierType = null;
				identifierColumnName = null;
				identifierColumnAlias = null;
				identifierGenerator = null;
			}

			sqlInsertRowString = GenerateInsertRowString();
			if (collection.CustomSQLInsert == null)
			{
				insertCallable = false;
				insertCheckStyle = ExecuteUpdateResultCheckStyle.Count;
			}
			else
			{
				sqlInsertRowString = new SqlCommandInfo(collection.CustomSQLInsert, sqlInsertRowString.ParameterTypes);
				insertCallable = collection.IsCustomInsertCallable;
				insertCheckStyle = collection.CustomSQLInsertCheckStyle == null
				                   	? ExecuteUpdateResultCheckStyle.DetermineDefault(collection.CustomSQLInsert, insertCallable)
				                   	: collection.CustomSQLInsertCheckStyle;
			}

			sqlUpdateRowString = GenerateUpdateRowString();
			if (collection.CustomSQLUpdate == null)
			{
				updateCallable = false;
				updateCheckStyle = ExecuteUpdateResultCheckStyle.Count;
			}
			else
			{
				sqlUpdateRowString = new SqlCommandInfo(collection.CustomSQLUpdate, sqlUpdateRowString.ParameterTypes);
				updateCallable = collection.IsCustomUpdateCallable;
				updateCheckStyle = collection.CustomSQLUpdateCheckStyle == null
				                   	? ExecuteUpdateResultCheckStyle.DetermineDefault(collection.CustomSQLUpdate, updateCallable)
				                   	: collection.CustomSQLUpdateCheckStyle;
			}

			sqlDeleteRowString = GenerateDeleteRowString();
			if (collection.CustomSQLDelete == null)
			{
				deleteCallable = false;
				deleteCheckStyle = ExecuteUpdateResultCheckStyle.None;
			}
			else
			{
				sqlDeleteRowString = new SqlCommandInfo(collection.CustomSQLDelete, sqlDeleteRowString.ParameterTypes);
				deleteCallable = collection.IsCustomDeleteCallable;
				deleteCheckStyle = ExecuteUpdateResultCheckStyle.None;
			}

			sqlDeleteString = GenerateDeleteString();
			if (collection.CustomSQLDeleteAll == null)
			{
				deleteAllCallable = false;
				deleteAllCheckStyle = ExecuteUpdateResultCheckStyle.None;
			}
			else
			{
				sqlDeleteString = new SqlCommandInfo(collection.CustomSQLDeleteAll, sqlDeleteString.ParameterTypes);
				deleteAllCallable = collection.IsCustomDeleteAllCallable;
				deleteAllCheckStyle = ExecuteUpdateResultCheckStyle.None;
			}

			isLazy = collection.IsLazy;

			isInverse = collection.IsInverse;

			if (collection.IsArray)
			{
				elementClass = ((Array) collection).ElementClass;
			}
			else
			{
				// for non-arrays, we don't need to know the element class
				elementClass = null;
			}

			if (elementType.IsComponentType)
			{
				elementPropertyMapping = new CompositeElementPropertyMapping(
					elementColumnNames, elementFormulaTemplates,
					(IAbstractComponentType) elementType, factory);
			}
			else if (!elementType.IsEntityType)
			{
				elementPropertyMapping = new ElementPropertyMapping(elementColumnNames, elementType);
			}
			else
			{
				IEntityPersister persister = factory.GetEntityPersister(((EntityType) elementType).AssociatedClass);
				// Not all classpersisters implement IPropertyMapping!
				if (persister is IPropertyMapping)
				{
					elementPropertyMapping = (IPropertyMapping) persister;
				}
				else
				{
					elementPropertyMapping = new ElementPropertyMapping(elementColumnNames, elementType);
				}
			}

			// Handle any filters applied to this collection
			filterHelper = new FilterHelper(collection.FilterMap, dialect, factory.SQLFunctionRegistry);

			// Handle any filters applied to this collection for many-to-many
			manyToManyFilterHelper = new FilterHelper(collection.ManyToManyFilterMap, dialect, factory.SQLFunctionRegistry);
			manyToManyWhereString = StringHelper.IsNotEmpty(collection.ManyToManyWhere) ?
			                        "( " + collection.ManyToManyWhere + " )" :
			                        null;
			manyToManyWhereTemplate = manyToManyWhereString == null
			                          	?
			                          null
			                          	:
			                          Template.RenderWhereStringTemplate(manyToManyWhereString, factory.Dialect,
			                                                             factory.SQLFunctionRegistry);
				// , factory.getSqlFunctionRegistry() );
			manyToManyOrderByString = collection.ManyToManyOrdering;
			manyToManyOrderByTemplate = manyToManyOrderByString == null
			                            	? null
			                            	: Template.RenderOrderByStringTemplate(manyToManyOrderByString, factory.Dialect,
			                            	                                       factory.SQLFunctionRegistry);
				// , factory.getSqlFunctionRegistry() );

			InitCollectionPropertyMap();
		}