Skip to content

A generic interface with a primary key generic type parameter used with two different primary key types causes a domain build failure #332

Open

Description

@ondrejtucny

When the following two entities are declared:

[HierarchyRoot(InheritanceSchema.SingleTable)]
[TableMapping("a_list")]
public sealed class AList : LegacyUserTrackedEntity, ICodebookEntity<short>
{
    [Key(0)]
    [Field]
    [FieldMapping("id_list")]
    public short Id { get; set; }
}

[HierarchyRoot(InheritanceSchema.SingleTable)]
[TableMapping("c_system")]
public sealed class CSystem : LegacyUserTrackedEntity, ICodebookEntity<int>
{
    [Key]
    [Field]
    [FieldMapping("id")]
    public int Id { get; set; }
}

DataObjects throws this error: Implementors of ICodebookEntity interface belong to hierarchies with different key structure: AList & CSystem.

However, the interfaces are ICodebookEntity<short> and ICodebookEntity<int> — two different things.

Using version 7.0.3.

Activity

  1. alex-kulakov commented on Aug 15, 2023

    @alex-kulakov
    Contributor

    Hello @ondrejtucny,

    I think you have misunderstood the error. The problem is not with generic interface itself. DO counts implementors of ICodeBookEntity and ICookBookEntity separately, and if an interface has more than one implementor then all implementors will be checked for key structure - for number of key fields and for types of those fields.

    Following model I have created works just fine:

      public interface ISomeEntity<TEntityKey> : IEntity
      {
        [Field]
        TEntityKey Id { get; }
    
        [Field]
        string Name { get; set; }
    
        [Field]
        DateTime CreationDate { get; set; }
      }
    
      [HierarchyRoot(InheritanceSchema.SingleTable)]
      public sealed class IntImplementor : Entity, ISomeEntity<int>
      {
        [Field, Key(0)]
        public int Id { get; private set; }
    
        [Field]
        public string Name { get; set; }
    
        [Field]
        public DateTime CreationDate { get; set; }
      }
    
      [HierarchyRoot(InheritanceSchema.SingleTable)]
      public sealed class LongImplementor : Entity, ISomeEntity<long>
      {
        [Field, Key]
        public long Id { get; private set; }
    
        [Field]
        public string Name { get; set; }
    
        [Field]
        public DateTime CreationDate { get; set; }
      }
    
      [HierarchyRoot(InheritanceSchema.SingleTable)]
      public sealed class ShortImplementor : Entity, ISomeEntity<short>
      {
        [Field, Key(0)]
        public short Id { get; private set; }
    
        [Field]
        public string Name { get; set; }
    
        [Field]
        public DateTime CreationDate { get; set; }
      }

    I believe that you have various key fields number within each interface.

    You can take a look at

    var count = hierarchies.Count;
    if (count == 0) {
    throw new DomainBuilderException(string.Format(
    Strings.ExXImplementorsDontBelongToAnyHierarchy, interfaceDef.Name));
    }
    if (count == 1) {
    context.ModelInspectionResult.SingleHierarchyInterfaces.Add(interfaceDef);
    }
    else {
    HierarchyDef master = null;
    foreach (var candidate in hierarchies) {
    if (master == null) {
    master = candidate;
    continue;
    }
    context.Validator.ValidateHierarchyEquality(interfaceDef, master, candidate);
    }
    }
    hierarchyDef = hierarchies.First();
    }

    you can see calling for validation if count is more that 1

    and the validation itself

    public void ValidateHierarchyEquality(TypeDef @interface, HierarchyDef first, HierarchyDef second)
    {
    // TypeId mode must match
    if (first.IncludeTypeId != second.IncludeTypeId) {
    throw new DomainBuilderException(string.Format(
    Strings.ExImplementorsOfXInterfaceBelongToHierarchiesOneOfWhichIncludesTypeIdButAnotherDoesntYZ,
    @interface.Name, first.Root.Name, second.Root.Name));
    }
    // Number of key fields must match
    if (first.KeyFields.Count != second.KeyFields.Count) {
    throw new DomainBuilderException(string.Format(
    Strings.ExImplementorsOfXInterfaceBelongToHierarchiesWithDifferentKeyStructureYZ,
    @interface.Name, first.Root.Name, second.Root.Name));
    }
    // Type of each key field must match
    for (var i = 0; i < first.KeyFields.Count; i++) {
    var masterField = first.Root.Fields[first.KeyFields[i].Name];
    var candidateField = second.Root.Fields[second.KeyFields[i].Name];
    if (masterField.ValueType != candidateField.ValueType) {
    throw new DomainBuilderException(string.Format(
    Strings.ExImplementorsOfXInterfaceBelongToHierarchiesWithDifferentKeyStructureYZ,
    @interface.Name, first.Root.Name, second.Root.Name));
    }
    }
    }

  2. alex-kulakov commented on Sep 6, 2023

    @alex-kulakov
    Contributor

    Hello @ondrejtucny,

    Any updates on this? Did my answer help to lid a problem with model? Should I dig dipper for this? In this case I need your assistance to reproduce the issue.

  3. ondrejtucny commented on Feb 6, 2025

    @ondrejtucny
    Author

    Update on when exactly this problem occurs: If the generic interface inherits from a normal interface which in turn inherits from IEntity, then this error occurs. If the generic interface directly inherits from IEntity, then it works.

    The following declaration causes the error:

        public interface ICodebookEntity : IEntity
        {
        }
    
        public interface ICodebookEntity<out TKey> : ICodebookEntity
            where TKey : struct
        {
            [Field]
            TKey Id { get; }
    
            [Field]
            string Code { get; }
    
            [Field]
            DateTime DateLastModified { get; }
        }
    

    However, when declared as follows, the hierarchy works just fine:

        public interface ICodebookEntity
        {
        }
    
        public interface ICodebookEntity<out TKey> : IEntity, ICodebookEntity
            where TKey : struct
        {
            [Field]
            TKey Id { get; }
    
            [Field]
            string Code { get; }
    
            [Field]
            DateTime DateLastModified { get; }
        }
    
  4. alex-kulakov commented on Feb 11, 2025

    @alex-kulakov
    Contributor

    @ondrejtucny,

    Thank you for the update, I will re-check and give you my update a bit later.

  5. alex-kulakov commented on Feb 12, 2025

    @alex-kulakov
    Contributor

    Hello @ondrejtucny ,

    I've created two models according to your latest examples, they look like so

    namespace Xtensive.Orm.Tests.Issues.IssueGithub0332_GenericIntefaceCausesErrorOnBuildModel
    {
      namespace GoodExample
      {
        public interface ICodebookEntity
        {
          
        }
    
        public interface ICodebookEntity<out TKey> : IEntity, ICodebookEntity
            where TKey : struct
        {
          [Field]
          TKey Id { get; }
    
          [Field]
          string Code { get; }
    
          [Field]
          DateTime DateLastModified { get; }
        }
    
        public class IntCodeBookEntity : Entity, ICodebookEntity<int>
        {
          [Field, Key]
          public int Id { get; private set; }
    
          [Field]
          public string Code { get; set; }
    
          [Field]
          public DateTime DateLastModified { get; set; }
        }
    
        public class LongCodeBookEntity : Entity, ICodebookEntity<long>
        {
          [Field, Key]
          public long Id { get; private set; }
    
          [Field]
          public string Code { get; set; }
    
          [Field]
          public DateTime DateLastModified { get; set; }
        }
      }
    
      namespace BadExample
      {
        public interface ICodebookEntity : IEntity
        {
          
        }
    
        public interface ICodebookEntity<out TKey> : ICodebookEntity
            where TKey : struct
        {
          [Field]
          TKey Id { get; }
    
          [Field]
          string Code { get; }
    
          [Field]
          DateTime DateLastModified { get; }
        }
    
        public class IntCodeBookEntity : Entity, ICodebookEntity<int>
        {
          [Field, Key]
          public int Id { get; private set; }
    
          [Field]
          public string Code { get; set; }
    
          [Field]
          public DateTime DateLastModified { get; set; }
        }
    
        public class LongCodeBookEntity : Entity, ICodebookEntity<long>
        {
          [Field, Key]
          public long Id { get; private set; }
    
          [Field]
          public string Code { get; set; }
    
          [Field]
          public DateTime DateLastModified { get; set; }
        }
      }
    }

    And I've created two tests that build domain with these types in model like so

      public class IssueGithub0332_GenericIntefaceCausesErrorOnBuild
      {
        [Test]
        public void WorkingCase()
        {
          //the factory just creates configuration with connection to test storage environment
          var configuration = DomainConfigurationFactory.Create();
          configuration.UpgradeMode = DomainUpgradeMode.Recreate;
          configuration.Types.Register(typeof(Models.GoodExample.IntCodeBookEntity));
          configuration.Types.Register(typeof(Models.GoodExample.LongCodeBookEntity));
          configuration.Types.Register(typeof(Models.GoodExample.ICodebookEntity));
          configuration.Types.Register(typeof(Models.GoodExample.ICodebookEntity<>));
    
          using (var domain = Domain.Build(configuration)) { }
        }
    
        [Test]
        public void NotWorkingCase()
        {
          //the factory just creates configuration with connection to test storage environment
          var configuration = DomainConfigurationFactory.Create();
          configuration.UpgradeMode = DomainUpgradeMode.Recreate;
          configuration.Types.Register(typeof(Models.BadExample.IntCodeBookEntity));
          configuration.Types.Register(typeof(Models.BadExample.LongCodeBookEntity));
          configuration.Types.Register(typeof(Models.BadExample.ICodebookEntity));
          configuration.Types.Register(typeof(Models.BadExample.ICodebookEntity<>));
    
          using (var domain = Domain.Build(configuration)) { }
        }
      }

    Both tests have completed successfully.

    I also tried not to declare [Field] in classes and tried to move one of non-key fields to non-generic ICodebookEntity interface. Works just fine.
    The tests were run on the latest for 7.0 branch version - 7.0.6 version.

    Can you guide me to what I should change to have such error as you're getting? Also, if you can, please, verify that your case still doesn't work on v7.0.6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions