From f5ea5bcc3eb25d2972481143a54f49d5a1bfaf56 Mon Sep 17 00:00:00 2001 From: Michael Charalambous Date: Wed, 16 May 2012 12:08:19 +0100 Subject: [PATCH] Adds Support to Select Post Insert Generator for: 1. Natural id with more than one property. 2. IDBag. --- .../NHSpecificTest/NH3150/Class.cs | 27 ++++++ .../NHSpecificTest/NH3150/Fixture.cs | 76 +++++++++++++++ .../NHSpecificTest/NH3150/Mappings.hbm.xml | 88 +++++++++++++++++ src/NHibernate.Test/NHibernate.Test.csproj | 3 + .../Id/IPostInsertIdentityPersister.cs | 4 +- .../Id/Insert/AbstractSelectingDelegate.cs | 4 +- src/NHibernate/Id/SelectGenerator.cs | 94 ++++++++++++++----- .../Collection/AbstractCollectionPersister.cs | 13 ++- .../Entity/AbstractEntityPersister.cs | 16 +++- 9 files changed, 290 insertions(+), 35 deletions(-) create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3150/Class.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3150/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/NH3150/Mappings.hbm.xml diff --git a/src/NHibernate.Test/NHSpecificTest/NH3150/Class.cs b/src/NHibernate.Test/NHSpecificTest/NH3150/Class.cs new file mode 100644 index 00000000000..52bf34e504e --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3150/Class.cs @@ -0,0 +1,27 @@ + +using System.Collections.Generic; +namespace NHibernate.Test.NHSpecificTest.NH3150 +{ + public class Worker + { + + public virtual int? id { get; set; } + + public virtual string name { get; set; } + public virtual string position { get; set; } + + } + + public class Worker2 + { + + public virtual int? id { get; set; } + public virtual IList roles { get; set; } + } + + public class Role + { + public virtual int? id { get; set; } + public virtual string description { get; set; } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/NH3150/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3150/Fixture.cs new file mode 100644 index 00000000000..abf7ab9e7c2 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3150/Fixture.cs @@ -0,0 +1,76 @@ +using NUnit.Framework; +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.NH3150 +{ + + [TestFixture] + public class Fixture : BugTestCase + { + public override string BugNumber + { + get { return "NH3150"; } + } + + protected override void OnTearDown() + { + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + s.Delete("from Worker"); + s.Delete("from Worker2"); + s.Delete("from Role"); + tx.Commit(); + } + } + + [Test] + public void CanHaveNaturalIdWithMoreThanOneProperty() + { + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + var worker = new Worker(); + worker.name = "Mr Black"; + worker.position = "Managing Director"; + s.Save(worker); + + tx.Commit(); + Assert.AreEqual(1, worker.id, "id should be 1"); + } + } + + [Test] + public void IdBagWithSelectPOID() + { + var worker_id = 0; + var role_id = 0; + + using (ISession s = OpenSession()) + using (ITransaction tx = s.BeginTransaction()) + { + var worker = new Worker2(); + var role = new Role() { description = "monkey keeper" }; + + worker.roles = new List(); + worker.roles.Add(role); + + s.Save(worker); + s.Save(role); + + tx.Commit(); + + worker_id = worker.id.Value; + role_id = role.id.Value; + } + + using (ISession s = OpenSession()) + { + var saved_worker = s.Get(worker_id); + Assert.NotNull(saved_worker.roles, "roles should not be null"); + Assert.AreEqual(1, saved_worker.roles.Count, "roles count should be 1"); + } + + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH3150/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH3150/Mappings.hbm.xml new file mode 100644 index 00000000000..b30f53c9447 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH3150/Mappings.hbm.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CREATE TRIGGER dbo.id_gen_Worker ON dbo.Worker + INSTEAD OF INSERT + AS + BEGIN + SET NOCOUNT ON; + + declare @lastval int + set @lastval = (select max(id) from Worker) + if @lastval is null set @lastval = 0 + + SELECT * INTO #Inserted FROM Inserted + UPDATE #Inserted set id = @lastval+1 + SET NOCOUNT OFF; + INSERT INTO Worker SELECT * FROM #Inserted + END + GO + + + DROP TRIGGER dbo.id_gen_Worker; + + + + + + + + CREATE TRIGGER dbo.id_gen_workerRoles ON dbo.workerRoles + INSTEAD OF INSERT + AS + BEGIN + SET NOCOUNT ON; + + declare @lastval int + set @lastval = (select max(id) from workerRoles) + if @lastval is null set @lastval = 0 + + SELECT * INTO #Inserted FROM Inserted + UPDATE #Inserted set id = @lastval+1 + SET NOCOUNT OFF; + INSERT INTO workerRoles SELECT * FROM #Inserted + END + GO + + + DROP TRIGGER dbo.id_gen_workerRoles; + + + + + \ No newline at end of file diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index de2565490b7..a713c8b488f 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -662,6 +662,8 @@ + + @@ -2817,6 +2819,7 @@ + diff --git a/src/NHibernate/Id/IPostInsertIdentityPersister.cs b/src/NHibernate/Id/IPostInsertIdentityPersister.cs index 280ad81cd1a..5a617f3b7e4 100644 --- a/src/NHibernate/Id/IPostInsertIdentityPersister.cs +++ b/src/NHibernate/Id/IPostInsertIdentityPersister.cs @@ -24,14 +24,14 @@ public interface IPostInsertIdentityPersister /// /// Get a SQL select string that performs a select based on a unique - /// key determined by the given property name). + /// key determined by the given array of property names). /// /// /// The name of the property which maps to the /// column(s) to use in the select statement restriction. /// /// The SQL select string - SqlString GetSelectByUniqueKeyString(string propertyName); + SqlString GetSelectByUniqueKeyString(string[] propertyNames); #region NH specific /// diff --git a/src/NHibernate/Id/Insert/AbstractSelectingDelegate.cs b/src/NHibernate/Id/Insert/AbstractSelectingDelegate.cs index 8776247ad66..b5bdfc8b3fe 100644 --- a/src/NHibernate/Id/Insert/AbstractSelectingDelegate.cs +++ b/src/NHibernate/Id/Insert/AbstractSelectingDelegate.cs @@ -56,7 +56,7 @@ public object PerformInsert(SqlCommandInfo insertSQL, ISessionImplementor sessio IDbCommand idSelect = session.Batcher.PrepareCommand(CommandType.Text, selectSQL, ParametersTypes); try { - BindParameters(session, idSelect, binder.Entity); + BindParameters(session, idSelect, binder); IDataReader rs = session.Batcher.ExecuteReader(idSelect); try { @@ -97,7 +97,7 @@ public object PerformInsert(SqlCommandInfo insertSQL, ISessionImplementor sessio /// The session /// The prepared command /// The entity being saved. - protected internal virtual void BindParameters(ISessionImplementor session, IDbCommand ps, object entity) { } + protected internal virtual void BindParameters(ISessionImplementor session, IDbCommand ps, IBinder binder) { } #region NH Specific diff --git a/src/NHibernate/Id/SelectGenerator.cs b/src/NHibernate/Id/SelectGenerator.cs index e3e6d4e1447..ef4ae95c2cd 100644 --- a/src/NHibernate/Id/SelectGenerator.cs +++ b/src/NHibernate/Id/SelectGenerator.cs @@ -2,6 +2,7 @@ using System.Data; using NHibernate.Engine; using NHibernate.Id.Insert; +using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using NHibernate.SqlTypes; @@ -37,30 +38,35 @@ public void Configure(IType type, IDictionary parms, Dialect.Dia #endregion - private static string DetermineNameOfPropertyToUse(IEntityPersister persister, string supplied) + private static string[] DetermineNameOfPropertiesToUse(IEntityPersister persister, string supplied) { if (supplied != null) { - return supplied; + return new string[] { supplied }; } int[] naturalIdPropertyIndices = persister.NaturalIdentifierProperties; + if (naturalIdPropertyIndices == null) { throw new IdentifierGenerationException("no natural-id property defined; need to specify [key] in " + "generator parameters"); } - if (naturalIdPropertyIndices.Length > 1) + foreach (var naturalIdPropertyIndex in naturalIdPropertyIndices) { - throw new IdentifierGenerationException("select generator does not currently support composite " - + "natural-id properties; need to specify [key] in generator parameters"); + ValueInclusion inclusion = persister.PropertyInsertGenerationInclusions[naturalIdPropertyIndex]; + if (inclusion != ValueInclusion.None) + { + throw new IdentifierGenerationException("natural-id also defined as insert-generated; need to specify [key] " + + "in generator parameters"); + } } - ValueInclusion inclusion = persister.PropertyInsertGenerationInclusions[naturalIdPropertyIndices[0]]; - if (inclusion != ValueInclusion.None) + + string[] result = new string[naturalIdPropertyIndices.Length]; + for (int i = 0; i < naturalIdPropertyIndices.Length; i++) { - throw new IdentifierGenerationException("natural-id also defined as insert-generated; need to specify [key] " - + "in generator parameters"); + result[i] = persister.PropertyNames[naturalIdPropertyIndices[i]]; } - return persister.PropertyNames[naturalIdPropertyIndices[0]]; + return result; } #region Nested type: SelectGeneratorDelegate @@ -73,19 +79,48 @@ public class SelectGeneratorDelegate : AbstractSelectingDelegate private readonly IType idType; private readonly IPostInsertIdentityPersister persister; - private readonly string uniqueKeyPropertyName; - private readonly IType uniqueKeyType; + private readonly string[] uniqueKeyPropertyNames; + private readonly IType[] uniqueKeyTypes; internal SelectGeneratorDelegate(IPostInsertIdentityPersister persister, ISessionFactoryImplementor factory, string suppliedUniqueKeyPropertyName) : base(persister) { this.persister = persister; this.factory = factory; - uniqueKeyPropertyName = DetermineNameOfPropertyToUse((IEntityPersister) persister, suppliedUniqueKeyPropertyName); - idSelectString = persister.GetSelectByUniqueKeyString(uniqueKeyPropertyName); - uniqueKeyType = ((IEntityPersister) persister).GetPropertyType(uniqueKeyPropertyName); + if (persister is IEntityPersister) + { + uniqueKeyPropertyNames = DetermineNameOfPropertiesToUse((IEntityPersister)persister, suppliedUniqueKeyPropertyName); + + uniqueKeyTypes = new IType[uniqueKeyPropertyNames.Length]; + for (int i = 0; i < uniqueKeyPropertyNames.Length; i++) + { + uniqueKeyTypes[i] = ((IEntityPersister)persister).GetPropertyType(uniqueKeyPropertyNames[i]); + } + + } + else if (persister is AbstractCollectionPersister) + { + var collectionPersister = (AbstractCollectionPersister)persister; + + uniqueKeyPropertyNames = new string[2]; + uniqueKeyTypes = new IType[2]; + + uniqueKeyPropertyNames[0] = collectionPersister.KeyColumnNames[0]; + uniqueKeyTypes[0] = collectionPersister.KeyType; + + uniqueKeyPropertyNames[1] = collectionPersister.ElementColumnNames[0]; + uniqueKeyTypes[1] = collectionPersister.ElementType; + } + else + { + throw new IdentifierGenerationException(string.Format("Persister if type {0} is not supported by Select Generator", persister.GetType())); + } + + idSelectString = persister.GetSelectByUniqueKeyString(uniqueKeyPropertyNames); idType = persister.IdentifierType; + + } protected internal override SqlString SelectSQL @@ -95,7 +130,14 @@ protected internal override SqlString SelectSQL protected internal override SqlType[] ParametersTypes { - get { return uniqueKeyType.SqlTypes(factory); } + get + { + var sqlTypes = new List(); + foreach (var uniqueKeyType in uniqueKeyTypes) + sqlTypes.AddRange(uniqueKeyType.SqlTypes(factory)); + + return sqlTypes.ToArray(); + } } public override IdentifierGeneratingInsert PrepareIdentifierGeneratingInsert() @@ -103,11 +145,21 @@ public override IdentifierGeneratingInsert PrepareIdentifierGeneratingInsert() return new IdentifierGeneratingInsert(factory); } - protected internal override void BindParameters(ISessionImplementor session, IDbCommand ps, object entity) + protected internal override void BindParameters(ISessionImplementor session, IDbCommand ps, IBinder binder) { - object uniqueKeyValue = ((IEntityPersister) persister).GetPropertyValue(entity, uniqueKeyPropertyName, - session.EntityMode); - uniqueKeyType.NullSafeSet(ps, uniqueKeyValue, 0, session); + for (int i = 0; i < uniqueKeyPropertyNames.Length; i++) + { + if (persister is IEntityPersister) + { + object uniqueKeyValue = ((IEntityPersister)persister).GetPropertyValue(binder.Entity, uniqueKeyPropertyNames[i], + session.EntityMode); + uniqueKeyTypes[i].NullSafeSet(ps, uniqueKeyValue, i, session); + } + else if (persister is AbstractCollectionPersister) + { + binder.BindValues(ps); + } + } } protected internal override object GetResult(ISessionImplementor session, IDataReader rs, object entity) @@ -115,7 +167,7 @@ protected internal override object GetResult(ISessionImplementor session, IDataR if (!rs.Read()) { throw new IdentifierGenerationException("the inserted row could not be located by the unique key: " - + uniqueKeyPropertyName); + + uniqueKeyPropertyNames); } return idType.NullSafeGet(rs, persister.RootTableKeyColumnNames, session, entity); } diff --git a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs index c46f7e44cad..14a0f3a03ec 100644 --- a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs @@ -2054,11 +2054,14 @@ public string[] RootTableKeyColumnNames get { return new string[] {IdentifierColumnName}; } } - public SqlString GetSelectByUniqueKeyString(string propertyName) - { - return - new SqlSimpleSelectBuilder(Factory.Dialect, Factory).SetTableName(qualifiedTableName).AddColumns(KeyColumnNames). - AddWhereFragment(KeyColumnNames, KeyType, " = ").ToSqlString(); + public SqlString GetSelectByUniqueKeyString(string[] propertyNames) + { + return + new SqlSimpleSelectBuilder(Factory.Dialect, Factory).SetTableName(qualifiedTableName) + .AddColumns(new string[] {identifierColumnName}) + .AddWhereFragment(KeyColumnNames, KeyType, " = ") + .AddWhereFragment(ElementColumnNames, ElementType, " = ") + .ToSqlString(); } public string GetInfoString() diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index cea61588399..8d2a9c3dbff 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -2575,12 +2575,18 @@ protected object Insert(object[] fields, bool[] notNull, SqlCommandInfo sql, obj return identityDelegate.PerformInsert(sql, session, binder); } - public virtual SqlString GetSelectByUniqueKeyString(string propertyName) + public virtual SqlString GetSelectByUniqueKeyString(string[] propertyNames) { - return new SqlSimpleSelectBuilder(Factory.Dialect, Factory) - .SetTableName(GetTableName(0)) - .AddColumns(GetKeyColumns(0)) - .AddWhereFragment(GetPropertyColumnNames(propertyName), GetPropertyType(propertyName), " = ").ToSqlString(); + var uniqueKey = new SqlSimpleSelectBuilder(Factory.Dialect, Factory) + .SetTableName(GetTableName(0)) + .AddColumns(GetKeyColumns(0)); + + foreach (string propertyName in propertyNames) + { + uniqueKey.AddWhereFragment(GetPropertyColumnNames(propertyName), GetPropertyType(propertyName), " = "); + } + + return uniqueKey.ToSqlString(); } ///