public static string GetComputeHashIdSelector(IsSubtypeOfInfo conceptInfo) { return("item => " + (conceptInfo.ImplementationName == "" ? "item.ID" : "DomUtility.GetSubtypeImplementationId(item.ID, " + DomUtility.GetSubtypeImplementationHash(conceptInfo.ImplementationName) + ")")); }
private string CreateComputedColumnSnippet() { return(string.Format( @"ALTER TABLE {0}.{1} ADD {2} AS CONVERT(UNIQUEIDENTIFIER, CONVERT(BINARY(4), CONVERT(INT, CONVERT(BINARY(4), ID)) ^ {3}) + SUBSTRING(CONVERT(BINARY(16), ID), 5, 12)) PERSISTED NOT NULL; CREATE UNIQUE INDEX IX_{1}_{2} ON {0}.{1}({2});", Subtype.Module.Name, Subtype.Name, GetComputedColumnName(), DomUtility.GetSubtypeImplementationHash(ImplementationName))); }
public void GetSubtypeImplementationId() { var testGuids = new Guid[] { new Guid("60CFE21A-1C36-45A0-9D57-DD635551B33B"), new Guid("12345678-2345-3456-4567-567890123456"), new Guid("FEDCBFED-CBFE-DCBF-EDCB-FEDCBFEDCBFE"), new Guid("00000000-0000-0000-0000-000000000000"), new Guid("11111111-1111-1111-1111-111111111111"), new Guid("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"), }; var testHashes = new int[] { 0, 1, -1, 123456, -123456, 123456789, -123456789, 1234567890, -1234567890, 2147483647, -2147483647, -2147483648 }; using (var container = new RhetosTestContainer()) { var sqlExecuter = container.Resolve <ISqlExecuter>(); foreach (var guid in testGuids) { foreach (var hash in testHashes) { Guid csId = DomUtility.GetSubtypeImplementationId(guid, hash); var sql = string.Format( @"SELECT ID = CONVERT(UNIQUEIDENTIFIER, CONVERT(BINARY(4), CONVERT(INT, CONVERT(BINARY(4), {1})) ^ {0}) + SUBSTRING(CONVERT(BINARY(16), {1}), 5, 12))", hash, "CONVERT(UNIQUEIDENTIFIER, '" + guid.ToString().ToUpper() + "')"); Guid sqlId = Guid.Empty; sqlExecuter.ExecuteReader(sql, reader => { sqlId = reader.GetGuid(0); }); Assert.AreEqual(csId, sqlId); } } } }
/// <summary> /// Same subtype may implement same supertype multiple time. Since ID of the supertype is usually same as subtype's ID, /// that might result with multiple supertype records with the same ID. To avoid duplicate IDs and still keep the /// deterministic ID values, the supertype's ID is XORed by a hash code taken from the ImplementationName. /// </summary> private string GetSubtypeImplementationIdSnippet() { if (IsSubtypeOf.ImplementationName == "") { return(""); } else if (IsSubtypeOf.SupportsPersistedSubtypeImplementationColum()) { return(",\r\n SubtypeImplementationID = " + PersistedSubtypeImplementationIdInfo.GetComputedColumnName(IsSubtypeOf.ImplementationName)); } else { int hash = DomUtility.GetSubtypeImplementationHash(IsSubtypeOf.ImplementationName); return(",\r\n SubtypeImplementationID = CONVERT(UNIQUEIDENTIFIER, CONVERT(BINARY(4), CONVERT(INT, CONVERT(BINARY(4), ID)) ^ " + hash + ") + SUBSTRING(CONVERT(BINARY(16), ID), 5, 12))"); } }
public void FilterSubtype() { using (var container = new RhetosTestContainer()) { var repository = container.Resolve <Common.DomRepository>(); repository.TestPolymorphic.ComplexImplementationData.Delete(repository.TestPolymorphic.ComplexImplementationData.Load()); repository.TestPolymorphic.ComplexImplementationSql.Delete(repository.TestPolymorphic.ComplexImplementationSql.Load()); Func <Guid, string, Guid> hash = (id, implementation) => DomUtility.GetSubtypeImplementationId(id, DomUtility.GetSubtypeImplementationHash(implementation)); var d = new TestPolymorphic.ComplexImplementationData { a = "d", ID = Guid.NewGuid() }; var sx = new TestPolymorphic.ComplexImplementationSql { s = "sx", ID = Guid.NewGuid() }; var sy = new TestPolymorphic.ComplexImplementationSql { s = "sy", ID = Guid.NewGuid() }; // Automatic update of _Materialized entity will not work if AlternativeId does not match ID hashed with implementation name. sx.AlternativeId = hash(sx.ID, "sql2"); sy.AlternativeId = hash(sy.ID, "sql2"); repository.TestPolymorphic.ComplexImplementationData.Insert(new[] { d }); repository.TestPolymorphic.ComplexImplementationSql.Insert(new[] { sx, sy }); Assert.AreEqual("abc1, abc2, sx3, sx4, sy3, sy4", TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase.Query(), item => item.Name1)); Action <string, IEnumerable <Guid>, string, string> test = (expected, ids, subtype, implementationName) => Assert.AreEqual(expected, TestUtility.DumpSorted( repository.TestPolymorphic.ComplexBase.Filter(new FilterSubtype { Ids = ids, Subtype = subtype, ImplementationName = implementationName }), item => item.Name1)); // Testing filter: test("abc1", new[] { d.ID }, "TestPolymorphic.ComplexImplementationQuery", null); test("abc1", new[] { d.ID }, "TestPolymorphic.ComplexImplementationQuery", ""); test("abc2", new[] { hash(d.ID, "q2") }, "TestPolymorphic.ComplexImplementationQuery", "q2"); test("", new[] { d.ID }, "TestPolymorphic.ComplexImplementationSql", null); test("sx3", new[] { sx.ID }, "TestPolymorphic.ComplexImplementationSql", null); test("sx4", new[] { sx.AlternativeId.Value }, "TestPolymorphic.ComplexImplementationSql", "sql2"); TestUtility.ShouldFail <Rhetos.ClientException>(() => repository.TestPolymorphic.ComplexBase.Filter(new FilterSubtype { Ids = new Guid[] {}, Subtype = "nonexisting", ImplementationName = "" }), "nonexisting"); // Testing update of materialized data (it uses the Subtype filter): Assert.AreEqual( TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase.Query(), item => item.ID), TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase_Materialized.Query(), item => item.ID)); sx.s = "sxx"; repository.TestPolymorphic.ComplexImplementationSql.Update(new[] { sx }); Assert.AreEqual("abc1, abc2, sxx3, sxx4, sy3, sy4", TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase.Query(), item => item.Name1)); Assert.AreEqual( TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase.Query(), item => item.ID), TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase_Materialized.Query(), item => item.ID)); repository.TestPolymorphic.ComplexImplementationSql.Delete(new[] { sx }); Assert.AreEqual("abc1, abc2, sy3, sy4", TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase.Query(), item => item.Name1)); Assert.AreEqual( TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase.Query(), item => item.ID), TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase_Materialized.Query(), item => item.ID)); repository.TestPolymorphic.ComplexImplementationData.Delete(new[] { d }); Assert.AreEqual("sy3, sy4", TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase.Query(), item => item.Name1)); Assert.AreEqual( TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase.Query(), item => item.ID), TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase_Materialized.Query(), item => item.ID)); repository.TestPolymorphic.ComplexImplementationSql.Delete(new[] { sy }); Assert.AreEqual("", TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase.Query(), item => item.Name1)); Assert.AreEqual( TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase.Query(), item => item.ID), TestUtility.DumpSorted(repository.TestPolymorphic.ComplexBase_Materialized.Query(), item => item.ID)); } }
public void MultipleImplementations() { foreach (bool useDatabaseNullSemantics in new[] { false, true }) { using (var container = new RhetosTestContainer()) { container.SetUseDatabaseNullSemantics(useDatabaseNullSemantics); var repository = container.Resolve <Common.DomRepository>(); // Initialize data: repository.TestPolymorphic.MultipleImplementations.Delete(repository.TestPolymorphic.MultipleImplementations.Query()); var mi1 = new TestPolymorphic.MultipleImplementations { Name1 = "abc", Name2 = "123" }; var mi2 = new TestPolymorphic.MultipleImplementations { Name1 = "def", Name2 = "456" }; repository.TestPolymorphic.MultipleImplementations.Insert(new[] { mi1, mi2 }); // Testing unions: var base1 = repository.TestPolymorphic.Base1.Load(); Assert.AreEqual("abc, cba, def, fed", TestUtility.DumpSorted(base1, item => item.Name1)); var base2 = repository.TestPolymorphic.Base2.Load(); Assert.AreEqual("123, 321, 456, 654", TestUtility.DumpSorted(base2, item => item.Name2)); var base3 = repository.TestPolymorphic.Base3.Load(); Assert.AreEqual("abc-3, def-3", TestUtility.DumpSorted(base3, item => item.Name1)); // Testing specific implementation ID uniqueness: var base1IDs = base1.Select(item => item.ID).ToList(); Assert.AreEqual(base1IDs.Count, base1IDs.Distinct().Count()); // Testing specific implementation ID stability: var secondRead = repository.TestPolymorphic.Base1.Query(); Assert.AreEqual( TestUtility.DumpSorted(base1IDs), TestUtility.DumpSorted(secondRead, item => item.ID)); // Testing querying by specific implementation subtype: Assert.AreEqual( "abc-, cba-abc, def-, fed-def", TestUtility.DumpSorted(repository.TestPolymorphic.Base1.Query() .ToList() .Select(item => item.Name1 + "-" + item.MultipleImplementationsReverse?.Name1))); // Testing C# implementation: int implementationHash = DomUtility.GetSubtypeImplementationHash("Reverse"); var expected = new[] { new TestPolymorphic.Base1 { ID = DomUtility.GetSubtypeImplementationId(mi1.ID, implementationHash), MultipleImplementationsReverseID = mi1.ID }, new TestPolymorphic.Base1 { ID = DomUtility.GetSubtypeImplementationId(mi2.ID, implementationHash), MultipleImplementationsReverseID = mi2.ID }, }; var actual = base1.Where(item => item.MultipleImplementationsReverseID != null); Assert.AreEqual( TestUtility.DumpSorted(expected, item => item.MultipleImplementationsReverseID.ToString() + "/" + item.ID.ToString()), TestUtility.DumpSorted(actual, item => item.MultipleImplementationsReverseID.ToString() + "/" + item.ID.ToString())); // Testing persisted IDs for specific implementation subtype: Assert.AreEqual( TestUtility.DumpSorted(base1IDs), TestUtility.DumpSorted(repository.TestPolymorphic.Base1_Materialized.Query().Select(item => item.ID))); } } }
public IEnumerable <IConceptInfo> CreateNewConcepts(IsSubtypeOfInfo conceptInfo, IDslModel existingConcepts) { var newConcepts = new List <IConceptInfo>(); // Add a subtype reference (for each subtype) to the supertype data structure: var subtypeReference = new ReferencePropertyInfo { DataStructure = conceptInfo.Supertype, Referenced = conceptInfo.Subtype, Name = conceptInfo.GetSubtypeReferenceName() }; newConcepts.Add(subtypeReference); newConcepts.Add(new PolymorphicPropertyInfo { Property = subtypeReference, SubtypeReference = conceptInfo.Subtype.GetKeyProperties() }); // Append subtype implementation to the supertype union: newConcepts.Add(new SubtypeExtendPolymorphicInfo { IsSubtypeOf = conceptInfo, SubtypeImplementationView = conceptInfo.GetImplementationViewPrototype(), PolymorphicUnionView = conceptInfo.Supertype.GetUnionViewPrototype() }); var filterBySubtypePrototype = new FilterByInfo { Source = conceptInfo.Supertype, Parameter = "Rhetos.Dom.DefaultConcepts.FilterSubtype" }; newConcepts.Add(new SubtypeExtendFilterInfo { IsSubtypeOf = conceptInfo, FilterBySubtype = filterBySubtypePrototype }); // Add metadata for supertype computation (union): string hashId = conceptInfo.ImplementationName == "" ? "item.ID" : "DomUtility.GetSubtypeImplementationId(item.ID, " + DomUtility.GetSubtypeImplementationHash(conceptInfo.ImplementationName) + ")"; foreach (DataStructureInfo dependsOn in DslUtility.GetBaseChangesOnDependency(conceptInfo.Subtype, existingConcepts)) { newConcepts.Add(new ChangesOnChangedItemsInfo { Computation = conceptInfo.Supertype, DependsOn = dependsOn, FilterType = "Rhetos.Dom.DefaultConcepts.FilterSubtype", FilterFormula = @"changedItems => new Rhetos.Dom.DefaultConcepts.FilterSubtype { Ids = changedItems.Select(item => " + hashId + @").ToArray(), Subtype = " + CsUtility.QuotedString(conceptInfo.Subtype.Module.Name + "." + conceptInfo.Subtype.Name) + @", ImplementationName = " + CsUtility.QuotedString(conceptInfo.ImplementationName) + @" }" }); } // Add metadata for subtype implementation: PersistedSubtypeImplementationIdInfo subtypeImplementationColumn = null; if (conceptInfo.SupportsPersistedSubtypeImplementationColum()) { subtypeImplementationColumn = new PersistedSubtypeImplementationIdInfo { Subtype = conceptInfo.Subtype, ImplementationName = conceptInfo.ImplementationName }; newConcepts.Add(subtypeImplementationColumn); } // Automatic interface implementation: var implementationView = (SqlViewInfo)existingConcepts.FindByKey(conceptInfo.GetImplementationViewPrototype().GetKey()); if (implementationView == null) { implementationView = new ExtensibleSubtypeSqlViewInfo { IsSubtypeOf = conceptInfo }; newConcepts.Add(implementationView); if (subtypeImplementationColumn != null) { newConcepts.Add(new SqlDependsOnSqlObjectInfo { // The subtype implementation view will use the PersistedSubtypeImplementationColumn. DependsOn = subtypeImplementationColumn.GetSqlObjectPrototype(), Dependent = implementationView }); } } // Redirect the developer-provided SQL dependencies from the "Is" concept to the implementation view: newConcepts.AddRange(existingConcepts.FindByReference <SqlDependsOnDataStructureInfo>(dep => dep.Dependent, conceptInfo) .Select(dep => new SqlDependsOnDataStructureInfo { Dependent = implementationView, DependsOn = dep.DependsOn })); newConcepts.AddRange(existingConcepts.FindByReference <SqlDependsOnModuleInfo>(dep => dep.Dependent, conceptInfo) .Select(dep => new SqlDependsOnModuleInfo { Dependent = implementationView, DependsOn = dep.DependsOn })); newConcepts.AddRange(existingConcepts.FindByReference <SqlDependsOnPropertyInfo>(dep => dep.Dependent, conceptInfo) .Select(dep => new SqlDependsOnPropertyInfo { Dependent = implementationView, DependsOn = dep.DependsOn })); newConcepts.AddRange(existingConcepts.FindByReference <SqlDependsOnSqlFunctionInfo>(dep => dep.Dependent, conceptInfo) .Select(dep => new SqlDependsOnSqlFunctionInfo { Dependent = implementationView, DependsOn = dep.DependsOn })); newConcepts.AddRange(existingConcepts.FindByReference <SqlDependsOnSqlIndexInfo>(dep => dep.Dependent, conceptInfo) .Select(dep => new SqlDependsOnSqlIndexInfo { Dependent = implementationView, DependsOn = dep.DependsOn })); newConcepts.AddRange(existingConcepts.FindByReference <SqlDependsOnSqlObjectInfo>(dep => dep.Dependent, conceptInfo) .Select(dep => new SqlDependsOnSqlObjectInfo { Dependent = implementationView, DependsOn = dep.DependsOn })); newConcepts.AddRange(existingConcepts.FindByReference <SqlDependsOnSqlViewInfo>(dep => dep.Dependent, conceptInfo) .Select(dep => new SqlDependsOnSqlViewInfo { Dependent = implementationView, DependsOn = dep.DependsOn })); return(newConcepts); }