3

I'm trying to figure out how to use CompositeId to map another class. Here's a test case:

The tables:

TestParent:
  TestParentId (PK)
  FavoriteColor

TestChild:
  TestParentId (PK)
  ChildName (PK)
  Age

The classes in C#:

public class TestParent
{
    public TestParent()
    {
        TestChildList = new List<TestChild>();
    }

    public virtual int TestParentId { get; set; }
    public virtual string FavoriteColor { get; set; }
    public virtual IList<TestChild> TestChildList { get; set; }
}

public class TestChild
{
    public virtual TestParent Parent { get; set; }
    public virtual string ChildName { get; set; }
    public virtual int Age { get; set; }

    public override int GetHashCode()
    {
        return Parent.GetHashCode() ^ ChildName.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        if (obj is TestChild)
        {
            var toCompare = obj as TestChild;
            return this.GetHashCode() != toCompare.GetHashCode();
        }
        return false;
    }
}

The Fluent NHibernate maps:

public class TestParentMap : ClassMap<TestParent>
{
    public TestParentMap()
    {
        Table("TestParent");
        Id(x => x.TestParentId).Column("TestParentId").GeneratedBy.Native();
        Map(x => x.FavoriteColor);

        HasMany(x => x.TestChildList).KeyColumn("TestParentId").Inverse().Cascade.None();
    }
}

public class TestChildMap : ClassMap<TestChild>
{
    public TestChildMap()
    {
        Table("TestChild");
        CompositeId()
            .KeyProperty(x => x.ChildName, "ChildName")
            .KeyReference(x => x.Parent, "TestParentId");

        Map(x => x.Age);
        References(x => x.Parent, "TestParentId");  /**  breaks insert **/
    }
}

When I try to add a new record, I get this error:

System.ArgumentOutOfRangeException : Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index

I know this error is due to the TestParentId column being mapped in the CompositeId and References calls. However, removing the References call causes another error when querying TestChild based on the TestParentId.

Here's the code that does the queries:

var session = _sessionBuilder.GetSession();
using (var tx = session.BeginTransaction())
{
    // create parent
    var p = new TestParent() { FavoriteColor = "Red" };
    session.Save(p);

    // creat child
    var c = new TestChild()
                {
                    ChildName = "First child",
                    Parent = p,
                    Age = 4
                };
    session.Save(c);  // breaks with References call in TestChildMap 

    tx.Commit();
}

// breaks without the References call in TestChildMap 
var children = _sessionBuilder.GetSession().CreateCriteria<TestChild>()
    .CreateAlias("Parent", "p")
    .Add(Restrictions.Eq("p.TestParentId", 1))
    .List<TestChild>();

Any ideas on how to create a composite key for this scenario?

david.mchonechase
  • 2,219
  • 3
  • 22
  • 24

2 Answers2

12

I found a better solution that will allow querying and inserting. The key is updating the map for TestChild to not insert records. The new map is:

public class TestChildMap : ClassMap<TestChild>
{
    public TestChildMap()
    {
        Table("TestChild");
        CompositeId()
            .KeyProperty(x => x.ChildName, "ChildName")
            .KeyReference(x => x.Parent, "TestParentId");

        Map(x => x.Age);
        References(x => x.Parent, "TestParentId")
            .Not.Insert();  //  will avoid "Index was out of range" error on insert
    }
}
david.mchonechase
  • 2,219
  • 3
  • 22
  • 24
  • 1
    You just made my day! Big thanks to asker and answerer! I too was able to work out that it worked with inserts one way but not reads and vice versa but wouldn't have guessed this for a solution. – Sam Jul 11 '14 at 03:56
  • solution fits me, specially line of code .Not.Insert() – Anil Mar 28 '17 at 09:44
2

Any reason you can't modify your query to just be

_sessionBuilder.GetSession().CreateCriteria<TestChild>()
   .Add(Restrictions.Eq("Parent.TestParentId", 1))
   .List<TestChild>()

Then get rid of the reference?

Vadim
  • 17,897
  • 4
  • 38
  • 62
  • Thanks - that worked. I'm not sure it will fix all occurrences (might need to join TestChild and TestParent for other queries), but I guess I'll worry about that later. – david.mchonechase Mar 21 '11 at 16:23