From 84f7596dbda088f9026e7da9594671e51966405d Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 20 Jan 2025 16:00:16 +0500 Subject: [PATCH 1/5] Add tests that checks Validator results --- .../Model/HierarchyRootValidationTest.cs | 3346 +++++++++++++++++ 1 file changed, 3346 insertions(+) create mode 100644 Orm/Xtensive.Orm.Tests/Model/HierarchyRootValidationTest.cs diff --git a/Orm/Xtensive.Orm.Tests/Model/HierarchyRootValidationTest.cs b/Orm/Xtensive.Orm.Tests/Model/HierarchyRootValidationTest.cs new file mode 100644 index 0000000000..aecea0931c --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Model/HierarchyRootValidationTest.cs @@ -0,0 +1,3346 @@ +// Copyright (C) 2025 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using NUnit.Framework; +using Xtensive.Core; +using Xtensive.IoC; +using Xtensive.Orm.Building; +using Xtensive.Orm.Building.Builders; +using Xtensive.Orm.Building.Definitions; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Model; +using Xtensive.Tuples; +using Models = Xtensive.Orm.Tests.Model.HierarchyRootValidationTestModel; + +namespace Xtensive.Orm.Tests.Model +{ + [TestFixture] + public sealed class HierarchyRootValidationTest + { + private const string CantBeOfMsg = "can't be of"; + private const string UnableToCreateKeyForMsg = "Unable to create key for"; + private const string UnsupportedTypeMsg = "Unsupported type"; + private const string KeyGeneratorCanServeHierarchyWithOneKeyFieldMsg = "generator can serve hierarchy with exactly one key field"; + + [Test] + public void ValidNonPersistentKeyTypesForDefaultGeneratorTest() + { + Domain domain = null; + + var configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.ByteKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.ByteKeyEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.Int16KeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.Int16KeyEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.UInt16KeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.UInt16KeyEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.Int32KeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.Int32KeyEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.UInt32KeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.UInt32KeyEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.Int64KeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.Int64KeyEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.UInt64KeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.UInt64KeyEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.LimitedStringKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.LimitedStringKeyEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.StringKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.StringKeyEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.GuidKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.GuidKeyEntity(session); + session.SaveChanges(); + }); + } + } + + [Test] + public void InvalidNonPersistentKeyTypesForDefaultGeneratorTest() + { + // NOTE!!! + // To make user's life easier on writing model classes DO implicitly changes + // key generator kind to KeyGeneratorKind.None for the hierarchies which key fields + // don't match certain conditions. + // Such "correction" makes Domain seem functional but exceptions can happen + // while working with domain. + + // This benifits in short term but in long term creates another "caviar" for user to remember + // along his project live, newcomers will probably not know this which can cause instability. + + Domain domain = null; + var configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.FloatKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + var entityType = domain.Model.Types[typeof(Models.NonPersistentStorageSupporedTypesAsKeys.FloatKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.FloatKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.DoubleKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.NonPersistentStorageSupporedTypesAsKeys.DoubleKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.DoubleKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.DecimalKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.NonPersistentStorageSupporedTypesAsKeys.DecimalKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.DecimalKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.FractionalDecimalKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.NonPersistentStorageSupporedTypesAsKeys.FractionalDecimalKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.FractionalDecimalKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.TimeSpanKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.NonPersistentStorageSupporedTypesAsKeys.TimeSpanKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.TimeSpanKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + +#if NET6_0_OR_GREATER + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.TimeOnlyKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.NonPersistentStorageSupporedTypesAsKeys.TimeOnlyKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.TimeOnlyKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.DateOnlyKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.NonPersistentStorageSupporedTypesAsKeys.DateOnlyKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.DateOnlyKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } +#endif + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.DateTimeKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.NonPersistentStorageSupporedTypesAsKeys.DateTimeKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.DateTimeKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + if (StorageProviderInfo.Instance.CheckProviderIs(StorageProvider.SqlServer)) { + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.DateTimeOffsetKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.NonPersistentStorageSupporedTypesAsKeys.DateTimeOffsetKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.DateTimeOffsetKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.CharKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.NonPersistentStorageSupporedTypesAsKeys.CharKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentStorageSupporedTypesAsKeys.CharKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + } + + [Test] + public void NonPersistentComplexKeyTest() + { + // NOTE!!! + // To make user's life easier on writing model classes DO implicitly changes + // key generator kind to KeyGeneratorKind.None for the hierarchies which key fields + // don't match certain conditions. + // Such "correction" makes Domain seem functional but exceptions can happen + // while working with domain. + + // This benifits in short term but in long term creates another "caviar" for user to remember + // along his project live, newcomers will probably not know this which can cause instability. + + Domain domain = null; + var configuration = CreateDomainConfiguration(new[] { typeof(Models.NonPersistentComplexKey.ByteKeyEntity) }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + var entityType = domain.Model.Types[typeof(Models.NonPersistentComplexKey.ByteKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.NonPersistentComplexKey.ByteKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + } + + [Test] + public void PersistentInterfaceKeyTest() + { + // NOTE!!! + // To make user's life easier on writing model classes DO implicitly changes + // key generator kind to KeyGeneratorKind.None for the hierarchies which key fields + // don't match certain conditions. + // Such "correction" makes Domain seem functional but exceptions can happen + // while working with domain. + + // This benifits in short term but in long term creates another "caviar" for user to remember + // along his project live, newcomers will probably not know this which can cause instability. + + Domain domain = null; + var configuration = CreateDomainConfiguration( + new[] { + typeof(Models.PersistentInterfaceAsKey.DefaultGeneratorSingleKeyEntity), + typeof(Models.PersistentInterfaceAsKey.ISomeEntity), + typeof(Models.PersistentInterfaceAsKey.SomeEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + var entityType = domain.Model.Types[typeof(Models.PersistentInterfaceAsKey.DefaultGeneratorSingleKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.PersistentInterfaceAsKey.DefaultGeneratorSingleKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.PersistentInterfaceAsKey.DefaultGeneratorComplexKeyEntity), + typeof(Models.PersistentInterfaceAsKey.ISomeEntity), + typeof(Models.PersistentInterfaceAsKey.SomeEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.PersistentInterfaceAsKey.DefaultGeneratorComplexKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.PersistentInterfaceAsKey.DefaultGeneratorComplexKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.PersistentInterfaceAsKey.NamedDefaultGeneratorSingleKeyEntity), + typeof(Models.PersistentInterfaceAsKey.ISomeEntity), + typeof(Models.PersistentInterfaceAsKey.SomeEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.PersistentInterfaceAsKey.NamedDefaultGeneratorSingleKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.PersistentInterfaceAsKey.NamedDefaultGeneratorSingleKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.PersistentInterfaceAsKey.NamedDefaultGeneratorComplexKeyEntity), + typeof(Models.PersistentInterfaceAsKey.ISomeEntity), + typeof(Models.PersistentInterfaceAsKey.SomeEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.PersistentInterfaceAsKey.NamedDefaultGeneratorComplexKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.PersistentInterfaceAsKey.NamedDefaultGeneratorComplexKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.PersistentInterfaceAsKey.CustomGeneratorSingleKeyEntity), + typeof(Models.PersistentInterfaceAsKey.ISomeEntity), + typeof(Models.PersistentInterfaceAsKey.SomeEntity), + typeof(Models.PersistentInterfaceAsKey.SingleKeyGenerator) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.PersistentInterfaceAsKey.CustomGeneratorSingleKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.PersistentInterfaceAsKey.CustomGeneratorSingleKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.PersistentInterfaceAsKey.CustomGeneratorComplexKeyEntity), + typeof(Models.PersistentInterfaceAsKey.ISomeEntity), + typeof(Models.PersistentInterfaceAsKey.SomeEntity), + typeof(Models.PersistentInterfaceAsKey.ComplexKeyGenerator), + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.PersistentInterfaceAsKey.CustomGeneratorComplexKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.PersistentInterfaceAsKey.CustomGeneratorComplexKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.PersistentInterfaceAsKey.NoGeneratorSingleKeyEntity), + typeof(Models.PersistentInterfaceAsKey.ISomeEntity), + typeof(Models.PersistentInterfaceAsKey.SomeEntity) + }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.PersistentInterfaceAsKey.NoGeneratorSingleKeyEntity)]; + + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + var keyEntity = new Models.PersistentInterfaceAsKey.SomeEntity(session); + _ = new Models.PersistentInterfaceAsKey.NoGeneratorSingleKeyEntity(session, keyEntity); // suppose to be this way + _ = new Models.PersistentInterfaceAsKey.NoGeneratorSingleKeyEntity(session); // tries to trigger generator + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.PersistentInterfaceAsKey.NoGeneratorComplexKeyEntity), + typeof(Models.PersistentInterfaceAsKey.ISomeEntity), + typeof(Models.PersistentInterfaceAsKey.SomeEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.PersistentInterfaceAsKey.NoGeneratorComplexKeyEntity)]; + + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + var keyEntity = new Models.PersistentInterfaceAsKey.SomeEntity(session); + _ = new Models.PersistentInterfaceAsKey.NoGeneratorComplexKeyEntity(session, 123, keyEntity); // suppose to be this way + _ = new Models.PersistentInterfaceAsKey.NoGeneratorComplexKeyEntity(session); // tries to trigger generator + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + } + + [Test] + public void EntityAsKeyTest() + { + // NOTE!!! + // To make user's life easier on writing model classes DO implicitly changes + // key generator kind to KeyGeneratorKind.None for the hierarchies which key fields + // don't match certain conditions. + // Such "correction" makes Domain seem functional but exceptions can happen + // while working with domain. + + // This benifits in short term but in long term creates another "caviar" for user to remember + // along his project live, newcomers will probably not know this which can cause instability. + + Domain domain = null; + var configuration = CreateDomainConfiguration( + new[] { + typeof(Models.EntityAsKey.DefaultGeneratorSingleKeyEntity), + typeof(Models.EntityAsKey.SomeEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + var entityType = domain.Model.Types[typeof(Models.EntityAsKey.DefaultGeneratorSingleKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.EntityAsKey.DefaultGeneratorSingleKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.EntityAsKey.DefaultGeneratorComplexKeyEntity), + typeof(Models.EntityAsKey.SomeEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.EntityAsKey.DefaultGeneratorComplexKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.EntityAsKey.DefaultGeneratorComplexKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.EntityAsKey.NamedDefaultGeneratorSingleKeyEntity), + typeof(Models.EntityAsKey.SomeEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.EntityAsKey.NamedDefaultGeneratorSingleKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.EntityAsKey.NamedDefaultGeneratorSingleKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.EntityAsKey.NamedDefaultGeneratorComplexKeyEntity), + typeof(Models.EntityAsKey.SomeEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.EntityAsKey.NamedDefaultGeneratorComplexKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.EntityAsKey.NamedDefaultGeneratorComplexKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.EntityAsKey.CustomGeneratorSingleKeyEntity), + typeof(Models.EntityAsKey.SomeEntity), + typeof(Models.EntityAsKey.SingleKeyGenerator) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.EntityAsKey.CustomGeneratorSingleKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.EntityAsKey.CustomGeneratorSingleKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.EntityAsKey.CustomGeneratorComplexKeyEntity), + typeof(Models.EntityAsKey.SomeEntity), + typeof(Models.EntityAsKey.ComplexKeyGenerator), + + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.EntityAsKey.CustomGeneratorComplexKeyEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.EntityAsKey.CustomGeneratorComplexKeyEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.EntityAsKey.NoGeneratorSingleKeyEntity), + typeof(Models.EntityAsKey.SomeEntity) + }); + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.EntityAsKey.NoGeneratorSingleKeyEntity)]; + + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + var keyEntity = new Models.EntityAsKey.SomeEntity(session); + _ = new Models.EntityAsKey.NoGeneratorSingleKeyEntity(session, keyEntity); // suppose to be this way + _ = new Models.EntityAsKey.NoGeneratorSingleKeyEntity(session); // tries to trigger generator + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.EntityAsKey.NoGeneratorComplexKeyEntity), + typeof(Models.EntityAsKey.SomeEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.EntityAsKey.NoGeneratorComplexKeyEntity)]; + + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + var keyEntity = new Models.EntityAsKey.SomeEntity(session); + _ = new Models.EntityAsKey.NoGeneratorComplexKeyEntity(session, 123, keyEntity); // suppose to be this way + _ = new Models.EntityAsKey.NoGeneratorComplexKeyEntity(session); // tries to trigger generator + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + } + + [Test] + public void ByteArrayKeyTest() + { + TestWithExceptionExpected(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.ByteArrayKeyEntity) }, + (ex) => ex.Message.Contains(CantBeOfMsg)); + } + + [Test] + public void IntArrayKeyTest() + { + TestWithExceptionExpected(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.IntArrayKeyEntity) }, + (ex) => ex.Message.Contains(CantBeOfMsg)); + } + + [Test] + public void ObjectKeyTest() + { + TestWithExceptionExpected(new[] { typeof(Models.NonPersistentStorageSupporedTypesAsKeys.ObjectKeyEntity) }, + (ex) => ex.Message.Contains("Unsupported type")); + } + + [Test] + public void LazyLoadKeyTest() + { + const string CannotBeLazyLoadMsg = "cannot be LazyLoad"; + + TestWithExceptionExpected(new[] { typeof(Models.LazyLoadKey.DefaultGeneratorSingleKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CannotBeLazyLoadMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.LazyLoadKey.DefaultGeneratorComplexKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CannotBeLazyLoadMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.LazyLoadKey.NamedDefaultGeneratorSingleKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CannotBeLazyLoadMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.LazyLoadKey.NamedDefaultGeneratorComplexKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CannotBeLazyLoadMsg))); + + TestWithExceptionExpected( + new[] { typeof(Models.LazyLoadKey.CustomGeneratorSingleKeyEntity), typeof(Models.LazyLoadKey.SingleKeyGenerator) }, + (ex) => Assert.That(ex.Message.Contains(CannotBeLazyLoadMsg))); + + TestWithExceptionExpected( + new[] { typeof(Models.LazyLoadKey.CustomGeneratorComplexKeyEntity), typeof(Models.LazyLoadKey.ComplexKeyGenerator) }, + (ex) => Assert.That(ex.Message.Contains(CannotBeLazyLoadMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.LazyLoadKey.NoGeneratorSingleKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CannotBeLazyLoadMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.LazyLoadKey.NoGeneratorComplexKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CannotBeLazyLoadMsg))); + } + + [Test] + public void EntitySetKeyTest() + { + TestWithExceptionExpected(new[] { typeof(Models.EntitySetAsKey.DefaultGeneratorSingleKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.EntitySetAsKey.DefaultGeneratorComplexKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.EntitySetAsKey.NamedDefaultGeneratorSingleKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.EntitySetAsKey.NamedDefaultGeneratorComplexKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected( + new[] { typeof(Models.EntitySetAsKey.CustomGeneratorSingleKeyEntity), typeof(Models.EntitySetAsKey.SingleKeyGenerator) }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected( + new[] { typeof(Models.EntitySetAsKey.CustomGeneratorComplexKeyEntity), typeof(Models.EntitySetAsKey.ComplexKeyGenerator) }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.EntitySetAsKey.NoGeneratorSingleKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.EntitySetAsKey.NoGeneratorComplexKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + } + + [Test] + public void IEntityKeyTest() + { + TestWithExceptionExpected(new[] { typeof(Models.IEntityAsKey.DefaultGeneratorSingleKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(UnsupportedTypeMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.IEntityAsKey.DefaultGeneratorComplexKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(UnsupportedTypeMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.IEntityAsKey.NamedDefaultGeneratorSingleKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(UnsupportedTypeMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.IEntityAsKey.NamedDefaultGeneratorComplexKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(UnsupportedTypeMsg))); + + TestWithExceptionExpected( + new[] { typeof(Models.IEntityAsKey.CustomGeneratorSingleKeyEntity), typeof(Models.IEntityAsKey.SingleKeyGenerator) }, + (ex) => Assert.That(ex.Message.Contains(UnsupportedTypeMsg))); + + TestWithExceptionExpected( + new[] { typeof(Models.IEntityAsKey.CustomGeneratorComplexKeyEntity), typeof(Models.IEntityAsKey.ComplexKeyGenerator) }, + (ex) => Assert.That(ex.Message.Contains(UnsupportedTypeMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.IEntityAsKey.NoGeneratorSingleKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(UnsupportedTypeMsg))); + + TestWithExceptionExpected(new[] { typeof(Models.IEntityAsKey.NoGeneratorComplexKeyEntity) }, + (ex) => Assert.That(ex.Message.Contains(UnsupportedTypeMsg))); + } + + [Test] + public void StructureKeyTest() + { + TestWithExceptionExpected( + new[] { + typeof(Models.StructureAsKey.DefaultGeneratorSingleKeyEntity), + typeof(Models.StructureAsKey.Point) + }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected( + new[] { + typeof(Models.StructureAsKey.DefaultGeneratorComplexKeyEntity), + typeof(Models.StructureAsKey.Point) + }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected( + new[] { + typeof(Models.StructureAsKey.NamedDefaultGeneratorSingleKeyEntity), + typeof(Models.StructureAsKey.Point) + }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected( + new[] { + typeof(Models.StructureAsKey.NamedDefaultGeneratorComplexKeyEntity), + typeof(Models.StructureAsKey.Point) + }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected( + new[] { + typeof(Models.StructureAsKey.CustomGeneratorSingleKeyEntity), + typeof(Models.StructureAsKey.SingleKeyGenerator), + typeof(Models.StructureAsKey.Point) + }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected( + new[] { + typeof(Models.StructureAsKey.CustomGeneratorComplexKeyEntity), + typeof(Models.StructureAsKey.ComplexKeyGenerator), + typeof(Models.StructureAsKey.Point) + }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected( + new[] { + typeof(Models.StructureAsKey.NoGeneratorSingleKeyEntity), + typeof(Models.StructureAsKey.Point) + }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + + TestWithExceptionExpected( + new[] { + typeof(Models.StructureAsKey.NoGeneratorComplexKeyEntity), + typeof(Models.StructureAsKey.Point) + }, + (ex) => Assert.That(ex.Message.Contains(CantBeOfMsg))); + } + + [Test] + public void NoKeyFieldsHierarchyTest() + { + TestWithExceptionExpected(typeof(Models.NoKeyFieldsHierarchy.NoKeyFieldsEntity), + (ex) => Assert.That(ex.Message.Contains("doesn't contain any key fields"))); + } + + [Test] + public void KeyNotInRootTest() + { + TestWithExceptionExpected( + new[] { + typeof(Models.KeyNotInRoot.DefaultGeneratorSingleKeyEntityRoot), + typeof(Models.KeyNotInRoot.DefaultGeneratorSingleKeyEntityLeaf) + }, + (ex) => Assert.That(ex.Message.Contains("doesn't contain any key fields"))); + + TestWithExceptionExpected( + new[] { + typeof(Models.KeyNotInRoot.NamedDefaultGeneratorSingleKeyEntityRoot), + typeof(Models.KeyNotInRoot.NamedDefaultGeneratorSingleKeyEntityLeaf) + }, + (ex) => Assert.That(ex.Message.Contains("doesn't contain any key fields"))); + + TestWithExceptionExpected( + new[] { + typeof(Models.KeyNotInRoot.CustomGeneratorSingleKeyEntityRoot), + typeof(Models.KeyNotInRoot.CustomGeneratorSingleKeyEntityLeaf), + typeof(Models.KeyNotInRoot.SingleKeyGenerator) + }, + (ex) => Assert.That(ex.Message.Contains("doesn't contain any key fields"))); + + TestWithExceptionExpected( + new[] { + typeof(Models.KeyNotInRoot.NoGeneratorSingleKeyEntityRoot), + typeof(Models.KeyNotInRoot.NoGeneratorSingleKeyEntityLeaf), + }, + (ex) => Assert.That(ex.Message.Contains("doesn't contain any key fields"))); + } + + [Test] + public void TwoKeysTest() + { + // NOTE!!! + // To make user's life easier on writing model classes DO implicitly changes + // key generator kind to KeyGeneratorKind.None for the hierarchies which key fields + // don't match certain conditions. + // Such "correction" makes Domain seem functional but exceptions can happen + // while working with domain. + + // This benifits in short term but in long term creates another "caviar" for user to remember + // along his project live, newcomers will probably not know this which can cause instability. + + Domain domain = null; + var configuration = CreateDomainConfiguration(new[] { typeof(Models.TwoKeys.DefaultGeneratorEntity) }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + var entityType = domain.Model.Types[typeof(Models.TwoKeys.DefaultGeneratorEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.TwoKeys.DefaultGeneratorEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.TwoKeys.NamedGeneratorEntity) }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.TwoKeys.NamedGeneratorEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + var ex = Assert.Throws(() => { + _ = new Models.TwoKeys.NamedGeneratorEntity(session); + session.SaveChanges(); + }); + Assert.That(ex.Message.Contains(UnableToCreateKeyForMsg)); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.TwoKeys.CustomGeneratorEntity), + typeof(Models.TwoKeys.TwoKeyFieldsGen) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.TwoKeys.CustomGeneratorEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.Custom)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.TwoKeys.CustomGeneratorEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.TwoKeys.NoGeneratorEntity) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.TwoKeys.NoGeneratorEntity)]; + + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.TwoKeys.NoGeneratorEntity(session, 1, 10); + session.SaveChanges(); + }); + } + } + + [Test] + public void SingleKeyWithTypeId() + { + Domain domain = null; + var configuration = CreateDomainConfiguration(new[] { typeof(Models.SingleKeyWithTypeId.DefaultGeneratorEntity) }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + var entityType = domain.Model.Types[typeof(Models.SingleKeyWithTypeId.DefaultGeneratorEntity)]; + + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.Default)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.SingleKeyWithTypeId.DefaultGeneratorEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.SingleKeyWithTypeId.NamedGeneratorEntity) }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.SingleKeyWithTypeId.NamedGeneratorEntity)]; + + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.Default)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.SingleKeyWithTypeId.NamedGeneratorEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration( + new[] { + typeof(Models.SingleKeyWithTypeId.CustomGeneratorEntity), + typeof(Models.SingleKeyWithTypeId.SingleKeyGenerator) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.SingleKeyWithTypeId.CustomGeneratorEntity)]; + + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.Custom)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.SingleKeyWithTypeId.CustomGeneratorEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration( + new[] { typeof(Models.SingleKeyWithTypeId.NoGeneratorEntity) }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.SingleKeyWithTypeId.NoGeneratorEntity)]; + + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.SingleKeyWithTypeId.NoGeneratorEntity(session, 10); + session.SaveChanges(); + }); + } + } + + [Test] + public void ThreeKeysTest() + { + TestWithExceptionExpected(new[] { typeof(Models.ThreeKeys.DefaultGeneratorEntity) }, + ex => ex.Message.Contains(KeyGeneratorCanServeHierarchyWithOneKeyFieldMsg)); + + TestWithExceptionExpected(new[] { typeof(Models.ThreeKeys.NamedGeneratorEntity) }, + ex => ex.Message.Contains(KeyGeneratorCanServeHierarchyWithOneKeyFieldMsg)); + + Domain domain = null; + + var configuration = CreateDomainConfiguration( + new[] { + typeof(Models.ThreeKeys.CustomGeneratorEntity), + typeof(Models.ThreeKeys.TwoKeyFieldsGen) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + var entityType = domain.Model.Types[typeof(Models.ThreeKeys.CustomGeneratorEntity)]; + + //generator kind is changed implicitly + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.Custom)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.ThreeKeys.CustomGeneratorEntity(session); + session.SaveChanges(); + }); + } + + configuration = CreateDomainConfiguration(new[] { typeof(Models.ThreeKeys.NoGeneratorEntity) }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + entityType = domain.Model.Types[typeof(Models.ThreeKeys.NoGeneratorEntity)]; + + Assert.That(entityType.Hierarchy.Key.GeneratorKind, Is.EqualTo(KeyGeneratorKind.None)); + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.ThreeKeys.NoGeneratorEntity(session, 1, 10, 100); + session.SaveChanges(); + }); + } + } + + [Test] + public void IModuleModifications() + { + // IModule manually adds existing TypeId column to key columns + + Domain domain = null; + var configuration = CreateDomainConfiguration(new[] { + typeof(Models.IModuleModifications.ManualIncludeTypeIdEntity), + typeof(Models.IModuleModifications.ModelDefModifier) + }); + + _ = Assert.Throws(() => domain = Domain.Build(configuration)); + + // IModule changes HierarhyRootDef.IncludeTypeId + configuration = CreateDomainConfiguration(new[] { + typeof(Models.IModuleModifications.IncludeTypeIdEntity), + typeof(Models.IModuleModifications.ModelDefModifier) + }); + + Assert.DoesNotThrow(() => domain = Domain.Build(configuration)); + Assert.That(domain, Is.Not.Null); + var entityType = domain.Model.Types[typeof(Models.IModuleModifications.IncludeTypeIdEntity)]; + + using (domain) + using (var session = domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + Assert.DoesNotThrow(() => { + _ = new Models.IModuleModifications.IncludeTypeIdEntity(session); + session.SaveChanges(); + }); + } + } + + private void TestWithExceptionExpected(Type baseTypeFromNs, Action exceptionValidator) + where TException : Exception + { + var exceptionInstance = Assert.Throws(() => BuildDomainStructureOnly(CreateDomainConfiguration(baseTypeFromNs))); + exceptionValidator?.Invoke(exceptionInstance); + } + + private void TestWithExceptionExpected(Type[] typesToRegister, Action exceptionValidator) + where TException : Exception + { + var exceptionInstance = Assert.Throws(() => BuildDomainStructureOnly(CreateDomainConfiguration(typesToRegister))); + exceptionValidator?.Invoke(exceptionInstance); + } + + private DomainConfiguration CreateDomainConfiguration(Type baseTypeFromNs) + { + var configuration = DomainConfigurationFactory.Create(); + configuration.UpgradeMode = DomainUpgradeMode.Recreate; + configuration.Types.Register(baseTypeFromNs.Assembly, baseTypeFromNs.Namespace); + return configuration; + } + + private DomainConfiguration CreateDomainConfiguration(Type[] typesToRegister) + { + var configuration = DomainConfigurationFactory.Create(); + configuration.UpgradeMode = DomainUpgradeMode.Recreate; + foreach(var t in typesToRegister) { + configuration.Types.Register(t); + } + return configuration; + } + + private Domain BuildDomainStructureOnly(DomainConfiguration domainConfiguration) + { + var upgradeContext = new Orm.Upgrade.UpgradeContext(domainConfiguration); + + using (upgradeContext.Activate()) + using (upgradeContext.Services) { + var configuration = CreateDomainBuilderConfiguration(upgradeContext); + return DomainBuilder.Run(configuration); + } + } + + private DomainBuilderConfiguration CreateDomainBuilderConfiguration(Orm.Upgrade.UpgradeContext upgradeContext) + { + var services = upgradeContext.Services; + + BuildServices(services, upgradeContext.Configuration); + + var configuration = new DomainBuilderConfiguration { + DomainConfiguration = upgradeContext.Configuration, + Stage = Orm.Upgrade.UpgradeStage.Final, + Services = services, + ModelFilter = new Orm.Upgrade.StageModelFilter(services.UpgradeHandlers, Orm.Upgrade.UpgradeStage.Final), + UpgradeContextCookie = new object(), + RecycledDefinitions = new List(), + DefaultSchemaInfo = new Sql.Info.DefaultSchemaInfo("dummyDb", "dummySchema") + }; + + configuration.Lock(); + return configuration; + } + + private void BuildServices(Orm.Upgrade.UpgradeServiceAccessor services, DomainConfiguration configuration) + { + services.Configuration = configuration; + services.IndexFilterCompiler = new Providers.PartialIndexFilterCompiler(); + + var descriptor = ProviderDescriptor.Get(configuration.ConnectionInfo.Provider); + var driverFactory = (Sql.SqlDriverFactory) Activator.CreateInstance(descriptor.DriverFactory); + var handlerFactory = (Providers.HandlerFactory) Activator.CreateInstance(descriptor.HandlerFactory); + var driver = Providers.StorageDriver.Create(driverFactory, configuration); + services.HandlerFactory = handlerFactory; + services.StorageDriver = driver; + services.NameBuilder = new Providers.NameBuilder(configuration, driver.ProviderInfo); + + + var dummyDefaultSchemaInfo = new Sql.Info.DefaultSchemaInfo("dummyDb", "dummySchema"); + services.MappingResolver = new Providers.SimpleMappingResolver(dummyDefaultSchemaInfo); + BuildExternalServices(services, configuration); + services.Lock(); + } + + private void BuildExternalServices(Orm.Upgrade.UpgradeServiceAccessor serviceAccessor, DomainConfiguration configuration) + { + var standardRegistrations = new[] { + new ServiceRegistration(typeof (DomainConfiguration), configuration), + //new ServiceRegistration(typeof (Orm.Upgrade.UpgradeContext), context) + }; + + var handlers = configuration.Types.UpgradeHandlers + .Select(type => new ServiceRegistration(typeof(Orm.Upgrade.IUpgradeHandler), type, false)); + + var registrations = standardRegistrations.Concat(handlers); + var serviceContainer = new ServiceContainer(registrations); + serviceAccessor.RegisterResource(serviceContainer); + + BuildModules(serviceAccessor); + BuildUpgradeHandlers(serviceAccessor, serviceContainer); + } + + private static void BuildModules(Orm.Upgrade.UpgradeServiceAccessor serviceAccessor) + { + serviceAccessor.Modules = new List().AsReadOnly(); + } + + private static void BuildUpgradeHandlers(Orm.Upgrade.UpgradeServiceAccessor serviceAccessor, IServiceContainer serviceContainer) + { + // Getting user handlers + var userHandlers = + from handler in serviceContainer.GetAll() + let assembly = handler.Assembly ?? handler.GetType().Assembly + where handler.IsEnabled + group handler by assembly; + + // Adding user handlers + var handlers = new Dictionary(); + foreach (var group in userHandlers) { + var candidates = group.ToList(); + if (candidates.Count > 1) { + throw new DomainBuilderException( + string.Format(Strings.ExMoreThanOneEnabledXIsProvidedForAssemblyY, typeof(Orm.Upgrade.IUpgradeHandler).Name, @group.Key)); + } + handlers.Add(group.Key, candidates[0]); + } + + // Adding default handlers + var assembliesWithUserHandlers = handlers.Select(pair => pair.Key); + var assembliesWithoutUserHandler = + serviceAccessor.Configuration.Types.PersistentTypes + .Select(type => type.Assembly) + .Distinct() + .Except(assembliesWithUserHandlers); + + foreach (var assembly in assembliesWithoutUserHandler) { + var handler = new Orm.Upgrade.UpgradeHandler(assembly); + handlers.Add(assembly, handler); + } + + // Building a list of handlers sorted by dependencies of their assemblies + var dependencies = handlers.Keys.ToDictionary( + assembly => assembly, + assembly => assembly.GetReferencedAssemblies().Select(assemblyName => assemblyName.ToString()).ToHashSet()); + var sortedHandlers = handlers + .SortTopologically((a0, a1) => dependencies[a1.Key].Contains(a0.Key.GetName().ToString())) + .Select(pair => pair.Value); + + // Storing the result + serviceAccessor.UpgradeHandlers = new ReadOnlyDictionary(handlers); + serviceAccessor.OrderedUpgradeHandlers = sortedHandlers.ToList().AsReadOnly(); + } + } +} + +namespace Xtensive.Orm.Tests.Model.HierarchyRootValidationTestModel +{ + namespace NonPersistentStorageSupporedTypesAsKeys + { + [HierarchyRoot] + public class ByteKeyEntity : Entity + { + [Field] + [Key] + public byte Id { get; private set; } + + [Field] + public int Value { get; set; } + + public ByteKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class Int16KeyEntity : Entity + { + [Field] + [Key] + public short Id { get; private set; } + + [Field] + public int Value { get; set; } + + public Int16KeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class UInt16KeyEntity : Entity + { + [Field] + [Key] + public ushort Id { get; private set; } + + [Field] + public int Value { get; set; } + + public UInt16KeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class Int32KeyEntity : Entity + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public int Value { get; set; } + + public Int32KeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class UInt32KeyEntity : Entity + { + [Field] + [Key] + public uint Id { get; private set; } + + [Field] + public int Value { get; set; } + + public UInt32KeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class Int64KeyEntity : Entity + { + [Field] + [Key] + public long Id { get; private set; } + + [Field] + public int Value { get; set; } + + public Int64KeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class UInt64KeyEntity : Entity + { + [Field] + [Key] + public ulong Id { get; private set; } + + [Field] + public int Value { get; set; } + + public UInt64KeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class FloatKeyEntity : Entity + { + [Field] + [Key] + public float Id { get; private set; } + + [Field] + public int Value { get; set; } + + public FloatKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class DoubleKeyEntity : Entity + { + [Field] + [Key] + public double Id { get; private set; } + + [Field] + public int Value { get; set; } + + public DoubleKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class DecimalKeyEntity : Entity + { + [Field(Precision = 12, Scale = 0)] + [Key] + public decimal Id { get; private set; } + + [Field] + public int Value { get; set; } + + public DecimalKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class FractionalDecimalKeyEntity : Entity + { + [Field(Precision = 12, Scale = 3)] + [Key] + public decimal Id { get; private set; } + + [Field] + public int Value { get; set; } + + public FractionalDecimalKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class TimeSpanKeyEntity : Entity + { + [Field] + [Key] + public TimeSpan Id { get; private set; } + + [Field] + public int Value { get; set; } + + public TimeSpanKeyEntity(Session session) + : base(session) + { + } + } + +#if NET6_0_OR_GREATER + + [HierarchyRoot] + public class TimeOnlyKeyEntity : Entity + { + [Field] + [Key] + public TimeOnly Id { get; private set; } + + [Field] + public int Value { get; set; } + + public TimeOnlyKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class DateOnlyKeyEntity : Entity + { + [Field] + [Key] + public DateOnly Id { get; private set; } + + [Field] + public int Value { get; set; } + + public DateOnlyKeyEntity(Session session) + : base(session) + { + } + } + +#endif + + [HierarchyRoot] + public class DateTimeKeyEntity : Entity + { + [Field] + [Key] + public DateTime Id { get; private set; } + + [Field] + public int Value { get; set; } + + public DateTimeKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class DateTimeOffsetKeyEntity : Entity + { + [Field] + [Key] + public DateTimeOffset Id { get; private set; } + + [Field] + public int Value { get; set; } + + public DateTimeOffsetKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class CharKeyEntity : Entity + { + [Field] + [Key] + public char Id { get; private set; } + + [Field] + public int Value { get; set; } + + public CharKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class LimitedStringKeyEntity : Entity + { + [Field(Length = 255)] + [Key] + public string Id { get; private set; } + + [Field] + public int Value { get; set; } + + public LimitedStringKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class StringKeyEntity : Entity + { + [Field] + [Key] + public string Id { get; private set; } + + [Field] + public int Value { get; set; } + + public StringKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class GuidKeyEntity : Entity + { + [Field] + [Key] + public Guid Id { get; private set; } + + [Field] + public int Value { get; set; } + + public GuidKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class ByteArrayKeyEntity : Entity + { + [Field] + [Key] + public byte[] Id { get; private set; } + + [Field] + public int Value { get; set; } + + public ByteArrayKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class IntArrayKeyEntity : Entity + { + [Field] + [Key] + public int[] Id { get; private set; } + + [Field] + public int Value { get; set; } + + public IntArrayKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class ObjectKeyEntity : Entity + { + [Field] + [Key] + public object Id { get; private set; } + + [Field] + public int Value { get; set; } + + public ObjectKeyEntity(Session session) + : base(session) + { + } + } + } + + namespace NonPersistentComplexKey + { + [HierarchyRoot] + public class ByteKeyEntity : Entity + { + [Field] + [Key(0)] + public byte Id1 { get; private set; } + + [Field] + [Key(1)] + public byte Id2 { get; private set; } + + [Field] + public int Value { get; set; } + + public ByteKeyEntity(Session session) + : base(session) + { + } + } + } + + namespace LazyLoadKey + { + [Service(typeof(KeyGenerator), Name = nameof(SingleKeyGenerator))] + public sealed class SingleKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + return Tuples.Tuple.Create(counter1++); + } + } + + [Service(typeof(KeyGenerator), Name = nameof(ComplexKeyGenerator))] + public sealed class ComplexKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + var now = DateTime.UtcNow; + return Tuples.Tuple.Create(counter1++, now.Minute); + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorSingleKeyEntity : Entity + { + [Field(LazyLoad = true)] + [Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorSingleKeyEntity : Entity + { + [Field(LazyLoad = true)] + [Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(SingleKeyGenerator))] + public class CustomGeneratorSingleKeyEntity : Entity + { + [Field(LazyLoad = true)] + [Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorSingleKeyEntity : Entity + { + [Field(LazyLoad = true)] + [Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field(LazyLoad = true)] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field(LazyLoad = true)] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(ComplexKeyGenerator))] + public class CustomGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field(LazyLoad = true)] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field(LazyLoad = true)] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + } + + namespace EntityAsKey + { + [Service(typeof(KeyGenerator), Name = nameof(SingleKeyGenerator))] + public sealed class SingleKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + return Tuples.Tuple.Create(counter1++); + } + } + + [Service(typeof(KeyGenerator), Name = nameof(ComplexKeyGenerator))] + public sealed class ComplexKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + var now = DateTime.UtcNow; + return Tuples.Tuple.Create(counter1++, now.Minute); + } + } + + [HierarchyRoot] + public class SomeEntity : Entity + { + [Field, Key] + public int Id { get; private set; } + + public SomeEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public SomeEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + + public DefaultGeneratorSingleKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public SomeEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + + public NamedDefaultGeneratorSingleKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(SingleKeyGenerator))] + public class CustomGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public SomeEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + + public CustomGeneratorSingleKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public SomeEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + + public NoGeneratorSingleKeyEntity(Session session) + : base(session) + { + } + + public NoGeneratorSingleKeyEntity(Session session, SomeEntity id) + : base(session, id) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public SomeEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public DefaultGeneratorComplexKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public SomeEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public NamedDefaultGeneratorComplexKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(ComplexKeyGenerator))] + public class CustomGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public SomeEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public CustomGeneratorComplexKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public SomeEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public NoGeneratorComplexKeyEntity(Session session) + : base(session) + { + } + + public NoGeneratorComplexKeyEntity(Session session, int id1, SomeEntity id2) + : base(session, id1, id2) + { + } + } + } + + namespace PersistentInterfaceAsKey + { + [Service(typeof(KeyGenerator), Name = nameof(SingleKeyGenerator))] + public sealed class SingleKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + return Tuples.Tuple.Create(counter1++); + } + } + + [Service(typeof(KeyGenerator), Name = nameof(ComplexKeyGenerator))] + public sealed class ComplexKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + var now = DateTime.UtcNow; + return Tuples.Tuple.Create(counter1++, now.Minute); + } + } + + public interface ISomeEntity : IEntity + { + [Field] + int Id { get; } + + [Field] + string Name { get; set; } + } + + [HierarchyRoot] + public class SomeEntity : Entity, ISomeEntity + { + [Field, Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + + public SomeEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public ISomeEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + + public DefaultGeneratorSingleKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public ISomeEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + + public NamedDefaultGeneratorSingleKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(SingleKeyGenerator))] + public class CustomGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public ISomeEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + + public CustomGeneratorSingleKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public ISomeEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + + public NoGeneratorSingleKeyEntity(Session session) + : base(session) + { + } + + public NoGeneratorSingleKeyEntity(Session session, ISomeEntity id) + : base(session, id) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public ISomeEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public DefaultGeneratorComplexKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public ISomeEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public NamedDefaultGeneratorComplexKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(ComplexKeyGenerator))] + public class CustomGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public ISomeEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public CustomGeneratorComplexKeyEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public ISomeEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public NoGeneratorComplexKeyEntity(Session session) + : base(session) + { + } + + public NoGeneratorComplexKeyEntity(Session session, int id1, ISomeEntity id2) + : base(session, id1, id2) + { + } + } + } + + namespace EntitySetAsKey + { + [Service(typeof(KeyGenerator), Name = nameof(SingleKeyGenerator))] + public sealed class SingleKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + return Tuples.Tuple.Create(counter1++); + } + } + + [Service(typeof(KeyGenerator), Name = nameof(ComplexKeyGenerator))] + public sealed class ComplexKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + var now = DateTime.UtcNow; + return Tuples.Tuple.Create(counter1++, now.Minute); + } + } + + [HierarchyRoot] + public class SomeEntity : Entity + { + [Field, Key] + public int Id { get; private set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public EntitySet Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public EntitySet Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(SingleKeyGenerator))] + public class CustomGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public EntitySet Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public EntitySet Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public EntitySet Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public EntitySet Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(ComplexKeyGenerator))] + public class CustomGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public EntitySet Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public EntitySet Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + } + + namespace IEntityAsKey + { + [Service(typeof(KeyGenerator), Name = nameof(SingleKeyGenerator))] + public sealed class SingleKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + return Tuples.Tuple.Create(counter1++); + } + } + + [Service(typeof(KeyGenerator), Name = nameof(ComplexKeyGenerator))] + public sealed class ComplexKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + var now = DateTime.UtcNow; + return Tuples.Tuple.Create(counter1++, now.Minute); + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public IEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public IEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(SingleKeyGenerator))] + public class CustomGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public IEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public IEntity Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public IEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public IEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(ComplexKeyGenerator))] + public class CustomGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public IEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public IEntity Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + } + + namespace StructureAsKey + { + [Service(typeof(KeyGenerator), Name = nameof(SingleKeyGenerator))] + public sealed class SingleKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + return Tuples.Tuple.Create(counter1++); + } + } + + [Service(typeof(KeyGenerator), Name = nameof(ComplexKeyGenerator))] + public sealed class ComplexKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + var now = DateTime.UtcNow; + return Tuples.Tuple.Create(counter1++, now.Minute); + } + } + + public class Point : Structure + { + [Field] + public int X { get; set; } + + [Field] + public int Y { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public Point Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public Point Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(SingleKeyGenerator))] + public class CustomGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public Point Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorSingleKeyEntity : Entity + { + [Field] + [Key] + public Point Id { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public Point Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntity))] // pretty much default generator + public class NamedDefaultGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public Point Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(ComplexKeyGenerator))] + public class CustomGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public Point Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorComplexKeyEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public Point Id2 { get; private set; } + + [Field] + public string Name { get; set; } + } + } + + namespace NoKeyFieldsHierarchy + { + [HierarchyRoot] + public class NoKeyFieldsEntity : Entity + { + [Field] + //No Key Attribute! + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + } + } + + namespace KeyNotInRoot + { + [Service(typeof(KeyGenerator), Name = nameof(SingleKeyGenerator))] + public sealed class SingleKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + return Tuples.Tuple.Create(counter1++); + } + } + + [Service(typeof(KeyGenerator), Name = nameof(ComplexKeyGenerator))] + public sealed class ComplexKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + var now = DateTime.UtcNow; + return Tuples.Tuple.Create(counter1++, now.Minute); + } + } + + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorSingleKeyEntityRoot : Entity + { + [Field] + public string Name { get; set; } + } + + public class DefaultGeneratorSingleKeyEntityLeaf : DefaultGeneratorSingleKeyEntityRoot + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public string Name2 { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedDefaultGeneratorSingleKeyEntityRoot))] // pretty much default generator + public class NamedDefaultGeneratorSingleKeyEntityRoot : Entity + { + [Field] + public string Name { get; set; } + } + + public class NamedDefaultGeneratorSingleKeyEntityLeaf : NamedDefaultGeneratorSingleKeyEntityRoot + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public string Name2 { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(SingleKeyGenerator))] + public class CustomGeneratorSingleKeyEntityRoot : Entity + { + [Field] + public string Name { get; set; } + } + + public class CustomGeneratorSingleKeyEntityLeaf : CustomGeneratorSingleKeyEntityRoot + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public string Name2 { get; set; } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorSingleKeyEntityRoot : Entity + { + [Field] + public string Name { get; set; } + } + + public class NoGeneratorSingleKeyEntityLeaf : NoGeneratorSingleKeyEntityRoot + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public string Name2 { get; set; } + } + } + + namespace TwoKeys + { + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public DefaultGeneratorEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedGeneratorEntity))] // still defaut key generator capabilities but dedicated instance + public class NamedGeneratorEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public NamedGeneratorEntity(Session session) + : base(session) + { + } + } + + [Service(typeof(KeyGenerator), Name = nameof(TwoKeyFieldsGen))] + public sealed class TwoKeyFieldsGen : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + return Tuples.Tuple.Create(counter1++, DateTime.UtcNow.Minute); + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(TwoKeyFieldsGen))] + public class CustomGeneratorEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public CustomGeneratorEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + public string Name { get; set; } + + public NoGeneratorEntity(Session session, int id1, int id2) + : base(session, id1, id2) + { + } + } + } + + namespace SingleKeyWithTypeId + { + [HierarchyRoot(IncludeTypeId = true)] // this setting doesn't cause addtional keyField on HierarchyDef level + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorEntity : Entity + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + + public DefaultGeneratorEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot(IncludeTypeId = true)] // this setting doesn't cause addtional keyField on HierarchyDef level + [KeyGenerator(Name = nameof(NamedGeneratorEntity))] // pretty much default generator + public class NamedGeneratorEntity : Entity + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + + public NamedGeneratorEntity(Session session) + : base(session) + { + } + } + + [Service(typeof(KeyGenerator), Name = nameof(SingleKeyGenerator))] + public sealed class SingleKeyGenerator : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + if (keyInfo.TypeIdColumnIndex >= 0) { + return Tuples.Tuple.Create(counter1++, -1);// -1 will be replaced with actual TypeId value + } + return Tuples.Tuple.Create(counter1++); + } + } + + [HierarchyRoot(IncludeTypeId = true)] // this setting doesn't cause addtional keyField on HierarchyDef level + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(SingleKeyGenerator))] + public class CustomGeneratorEntity : Entity + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + + public CustomGeneratorEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot(IncludeTypeId = true)] // this setting doesn't cause addtional keyField on HierarchyDef level + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorEntity : Entity + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + + public NoGeneratorEntity(Session session, int id) + : base(session, id) + { + } + } + } + + namespace ThreeKeys + { + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Default)] // equals no attribute at all, added for clearance + public class DefaultGeneratorEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + [Key(2)] + public int Id3 { get; private set; } + + [Field] + public string Name { get; set; } + + public DefaultGeneratorEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(Name = nameof(NamedGeneratorEntity))] // still defaut key generator capabilities but dedicated instance + public class NamedGeneratorEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + [Key(2)] + public int Id3 { get; private set; } + + [Field] + public string Name { get; set; } + + public NamedGeneratorEntity(Session session) + : base(session) + { + } + } + + [Service(typeof(KeyGenerator), Name = nameof(TwoKeyFieldsGen))] + public sealed class TwoKeyFieldsGen : KeyGenerator + { + private int counter1; + + public override void Initialize(Domain ownerDomain, TupleDescriptor keyTupleDescriptor) + { + } + + public override Tuples.Tuple GenerateKey(KeyInfo keyInfo, Session session) + { + var utcNow = DateTime.UtcNow; + return Tuples.Tuple.Create(counter1++, utcNow.Minute, utcNow.Second); + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.Custom, Name = nameof(TwoKeyFieldsGen))] + public class CustomGeneratorEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + [Key(2)] + public int Id3 { get; private set; } + + [Field] + public string Name { get; set; } + + public CustomGeneratorEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + [KeyGenerator(KeyGeneratorKind.None)] + public class NoGeneratorEntity : Entity + { + [Field] + [Key(0)] + public int Id1 { get; private set; } + + [Field] + [Key(1)] + public int Id2 { get; private set; } + + [Field] + [Key(2)] + public int Id3 { get; private set; } + + [Field] + public string Name { get; set; } + + public NoGeneratorEntity(Session session, int id1, int id2, int id3) + : base(session, id1, id2, id3) + { + } + } + } + + namespace IModuleModifications + { + public class ModelDefModifier : IModule2 + { + public void OnAutoGenericsBuilt(BuildingContext context, ICollection autoGenerics) + { + + } + public void OnBuilt(Domain domain) + { + + } + public void OnDefinitionsBuilt(BuildingContext context, DomainModelDef model) + { + var manualIncludeTypeIdHierarchy = model.Hierarchies.TryGetValue(typeof(ManualIncludeTypeIdEntity)); + if (manualIncludeTypeIdHierarchy is not null) { + var rootType = manualIncludeTypeIdHierarchy.Root; + manualIncludeTypeIdHierarchy.KeyFields.Add(new KeyField(WellKnown.TypeIdFieldName)); + } + + var includeTypeIdHierarchy = model.Hierarchies.TryGetValue(typeof(IncludeTypeIdEntity)); + if (includeTypeIdHierarchy is not null) { + includeTypeIdHierarchy.IncludeTypeId = true; + } + } + } + + [HierarchyRoot] + public class ManualIncludeTypeIdEntity : Entity + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + + public ManualIncludeTypeIdEntity(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class IncludeTypeIdEntity : Entity + { + [Field] + [Key] + public int Id { get; private set; } + + [Field] + public string Name { get; set; } + + public IncludeTypeIdEntity(Session session) + : base(session) + { + } + } + } +} From 607773501fccdb7ec89304f739143510f8710b81 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 20 Jan 2025 16:03:59 +0500 Subject: [PATCH 2/5] Explicitlly set KeyGeneratorKind.None instead of implicit conversion --- Orm/Xtensive.Orm.Tests/Issues/IssueJira0555_KeyRemapBug.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests/Issues/IssueJira0555_KeyRemapBug.cs b/Orm/Xtensive.Orm.Tests/Issues/IssueJira0555_KeyRemapBug.cs index be8fe1b2ac..a66363cfcf 100644 --- a/Orm/Xtensive.Orm.Tests/Issues/IssueJira0555_KeyRemapBug.cs +++ b/Orm/Xtensive.Orm.Tests/Issues/IssueJira0555_KeyRemapBug.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2014 Xtensive LLC. +// Copyright (C) 2014 Xtensive LLC. // All rights reserved. // For conditions of distribution and use, see license. // Created by: Alexey Kulakov @@ -57,7 +57,7 @@ public class TestEntity : Entity } [HierarchyRoot] - [KeyGenerator] + [KeyGenerator(KeyGeneratorKind.None)] public class TestEntity2 : Entity { private static int Increment = 1; From 5a807f309bf03a4392be7edaea243e9c4f09813f Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 20 Jan 2025 16:58:31 +0500 Subject: [PATCH 3/5] Write warning to build log about change of generator kind --- .../Orm/Building/Builders/TypeBuilder.cs | 4 +++ Orm/Xtensive.Orm/Strings.Designer.cs | 33 +++++++------------ Orm/Xtensive.Orm/Strings.resx | 3 ++ 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Building/Builders/TypeBuilder.cs b/Orm/Xtensive.Orm/Orm/Building/Builders/TypeBuilder.cs index 0db467dc80..01323e828a 100644 --- a/Orm/Xtensive.Orm/Orm/Building/Builders/TypeBuilder.cs +++ b/Orm/Xtensive.Orm/Orm/Building/Builders/TypeBuilder.cs @@ -527,6 +527,10 @@ private KeyInfo BuildKeyInfo(TypeInfo root, HierarchyDef hierarchyDef) } } + if (generatorKind != hierarchyDef.KeyGeneratorKind && generatorKind == KeyGeneratorKind.None) { + BuildLog.Warning(string.Format(Strings.LogKeyGeneratorKindForHierarchyWithRootXHasChangedToNone, root.Name)); + } + if (generatorKind == KeyGeneratorKind.None) { // No key generator is attached. // Each hierarchy has it's own equality identifier. diff --git a/Orm/Xtensive.Orm/Strings.Designer.cs b/Orm/Xtensive.Orm/Strings.Designer.cs index 13985efb13..fb5b8db136 100644 --- a/Orm/Xtensive.Orm/Strings.Designer.cs +++ b/Orm/Xtensive.Orm/Strings.Designer.cs @@ -2588,15 +2588,6 @@ internal static string ExJoinHasSameInnerAndOuterParameterInstances { } } - /// - /// Looks up a localized string similar to Key already exists.. - /// - internal static string ExKeyAlreadyExists { - get { - return ResourceManager.GetString("ExKeyAlreadyExists", resourceCulture); - } - } - /// /// Looks up a localized string similar to The Key belongs to different storage node.. /// @@ -4669,15 +4660,6 @@ internal static string ExTypeXShouldNotBeGeneric { } } - /// - /// Looks up a localized string similar to Query was compiled with DomainConfiguration.ShareStorageSchemaOverNodes option set to true, it requires PostCompilerConfiguration.SchemaMapping and PostCompilerConfiguration.DatabaseMapping collections to be provided.. - /// - internal static string ExUnableToActualizeSchemaNodeInQuery { - get { - return ResourceManager.GetString("ExUnableToActualizeSchemaNodeInQuery", resourceCulture); - } - } - /// /// Looks up a localized string similar to The type '{0}',which contains renaming field '{1}', doesn't exist in storage model.. /// @@ -4688,11 +4670,11 @@ internal static string ExTypeXWhichContainsRenamingFieldYDoesntExistInStorageMod } /// - /// Looks up a localized string similar to Unable to activate EntitySet for '{0}' field because it does not has association.. + /// Looks up a localized string similar to Query was compiled with DomainConfiguration.ShareStorageSchemaOverNodes option set to true, it requires PostCompilerConfiguration.SchemaMapping and PostCompilerConfiguration.DatabaseMapping collections to be provided.. /// - internal static string ExUnableToActivateEntitySetWithoutAssociation { + internal static string ExUnableToActualizeSchemaNodeInQuery { get { - return ResourceManager.GetString("ExUnableToActivateEntitySetWithoutAssociation", resourceCulture); + return ResourceManager.GetString("ExUnableToActualizeSchemaNodeInQuery", resourceCulture); } } @@ -6123,6 +6105,15 @@ internal static string LogItemFormat { } } + /// + /// Looks up a localized string similar to Key generator kind for hierarchy with root '{0}' has changed to None. Make sure that all key values are recieved by constructor and passed to one of Entity constructors.. + /// + internal static string LogKeyGeneratorKindForHierarchyWithRootXHasChangedToNone { + get { + return ResourceManager.GetString("LogKeyGeneratorKindForHierarchyWithRootXHasChangedToNone", resourceCulture); + } + } + /// /// Looks up a localized string similar to Metadata.Assembly created: '{0}'.. /// diff --git a/Orm/Xtensive.Orm/Strings.resx b/Orm/Xtensive.Orm/Strings.resx index 2419b0e101..aeda139b77 100644 --- a/Orm/Xtensive.Orm/Strings.resx +++ b/Orm/Xtensive.Orm/Strings.resx @@ -2609,4 +2609,7 @@ Error: {1} Translation of TimeOnly.ToString(string) with arbitrary arguments is not supported. Use TimeOnly.ToString("s"). + + Key generator kind for hierarchy with root '{0}' has changed to None. Make sure that all key values are recieved by constructor and passed to one of Entity constructors. + \ No newline at end of file From 8b9f50d36844c30354f49af8272d358f17b74774 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 21 Jan 2025 16:26:34 +0500 Subject: [PATCH 4/5] caviar->caveat in comments --- .../Model/HierarchyRootValidationTest.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests/Model/HierarchyRootValidationTest.cs b/Orm/Xtensive.Orm.Tests/Model/HierarchyRootValidationTest.cs index aecea0931c..277aaeae5e 100644 --- a/Orm/Xtensive.Orm.Tests/Model/HierarchyRootValidationTest.cs +++ b/Orm/Xtensive.Orm.Tests/Model/HierarchyRootValidationTest.cs @@ -164,7 +164,7 @@ public void InvalidNonPersistentKeyTypesForDefaultGeneratorTest() // Such "correction" makes Domain seem functional but exceptions can happen // while working with domain. - // This benifits in short term but in long term creates another "caviar" for user to remember + // This benifits in short term but in long term creates another caveat for user to remember // along his project live, newcomers will probably not know this which can cause instability. Domain domain = null; @@ -363,7 +363,7 @@ public void NonPersistentComplexKeyTest() // Such "correction" makes Domain seem functional but exceptions can happen // while working with domain. - // This benifits in short term but in long term creates another "caviar" for user to remember + // This benifits in short term but in long term creates another caveat for user to remember // along his project live, newcomers will probably not know this which can cause instability. Domain domain = null; @@ -396,7 +396,7 @@ public void PersistentInterfaceKeyTest() // Such "correction" makes Domain seem functional but exceptions can happen // while working with domain. - // This benifits in short term but in long term creates another "caviar" for user to remember + // This benifits in short term but in long term creates another caveat for user to remember // along his project live, newcomers will probably not know this which can cause instability. Domain domain = null; @@ -606,7 +606,7 @@ public void EntityAsKeyTest() // Such "correction" makes Domain seem functional but exceptions can happen // while working with domain. - // This benifits in short term but in long term creates another "caviar" for user to remember + // This benifits in short term but in long term creates another caveat for user to remember // along his project live, newcomers will probably not know this which can cause instability. Domain domain = null; @@ -1024,7 +1024,7 @@ public void TwoKeysTest() // Such "correction" makes Domain seem functional but exceptions can happen // while working with domain. - // This benifits in short term but in long term creates another "caviar" for user to remember + // This benifits in short term but in long term creates another caveat for user to remember // along his project live, newcomers will probably not know this which can cause instability. Domain domain = null; From f87d243d341dd8dca24bd4df1b1b236441233a6d Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 21 Jan 2025 18:09:48 +0500 Subject: [PATCH 5/5] Improve changelog --- ChangeLog/7.1.4_dev.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog/7.1.4_dev.txt b/ChangeLog/7.1.4_dev.txt index 129c659ad1..754f28c4ee 100644 --- a/ChangeLog/7.1.4_dev.txt +++ b/ChangeLog/7.1.4_dev.txt @@ -1 +1,2 @@ -[main] Addressed IndexOutOfRangeException on translation of certain queries with aggregates \ No newline at end of file +[main] Addressed IndexOutOfRangeException on translation of certain queries with aggregates +[main] Notification to BuildLog when a hierarchy changes KeyGeneratorKind value to None \ No newline at end of file