Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HasMany relationship causes "Found shared references to a collection" when reading from the db

I'm new to NHibernate having trouble with a mapping. I've failed to Google to an answer.

My entities look like this:

public class Triage
{
    public virtual Guid Id { get; set; }

    public virtual IDictionary<int, Discriminator> Discriminators { get; set; }

    // This is to keep FluentNHibernate happy
    public virtual int? SelectedDiscriminatorId { get; set; }
}

public class Discriminator
{
    public virtual int Id { get; set; }
    public virtual int LanguageId { get; set; }

    public override bool Equals(object obj)
    {
        var other = obj as Discriminator;
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;

        return Id == other.Id && LanguageId == other.LanguageId;
    }

    public override int GetHashCode()
    {
        return new { Id, LanguageId }.GetHashCode();
    }
}

My mappings look like this:

public class TriageMap : ClassMap<Triage>
{
    public TriageMap()
    {
        Id(x => x.Id).GeneratedBy.GuidComb();
        HasMany(x => x.Discriminators)
            .KeyColumn("Id")
            .PropertyRef("SelectedDiscriminatorId")
            .Inverse()
            .Cascade.All()
            .Not.LazyLoad()
            .AsMap(x => x.LanguageId);

        // This mapping is only needed to keep FluentNHibernate happy...
        Map(x => x.SelectedDiscriminatorId);
    }
}

public class DiscriminatorMap : ClassMap<Discriminator>
{
    public DiscriminatorMap()
    {
        CompositeId()
            .KeyProperty(x => x.Id)
            .KeyProperty(x => x.LanguageId);
    }
}

The idea is that Triage has a chosen Discriminator (SelectedDiscriminatorId) and the Discriminator-table contains the describing texts in several available languages. Not particularly fond of the construction that Triage is referring to Discriminator with SelectedDiscriminatorId that is only part of the composite key in Discriminator (Id and LanguageId), but that's how my database looks.

So when I fetch my triages like this:

_sessionFactory = CreateSessionFactory();
ISession session = _sessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
var triages = _sessionFactory
    .GetCurrentSession()
    .Query<Triage>()
    .Fetch(t => t.Discriminators)
    .ToList();
session.Flush();
session.Close();
CurrentSessionContext.Unbind(_sessionFactory);

then all works fine provided SelectedDiscriminatorId is unique within the fetched triages. However, when there are several triages with the same SelectedDiscriminatorId, I get a HibernateException saying "Additional information: Found shared references to a collection: TestProject.Triage.Discriminators" when it executes the session.Flush() statement.

Any idea what is wrong here and how I correct that? Thanks.

This is how the database looks:

CREATE TABLE [dbo].[Triage](
    [Id] [uniqueidentifier] NOT NULL CONSTRAINT [DF_Triage_Id]  DEFAULT (newid()),
    [SelectedDiscriminatorId] [int] NULL,
 CONSTRAINT [PK_Triage] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE TABLE [dbo].[Discriminator](
    [ID] [int] NOT NULL,
    [DisplayText] [nvarchar](255) NULL,
    [LanguageID] [int] NOT NULL,
    [Description] [nvarchar](4000) NULL,
 CONSTRAINT [PK_Discriminator] PRIMARY KEY CLUSTERED 
(
    [ID] ASC,
    [LanguageID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
like image 834
Jan-Jaap van der Geer Avatar asked Oct 31 '22 04:10

Jan-Jaap van der Geer


1 Answers

a much saner object model would be

public class Triage
{
    public virtual Guid Id { get; set; }

    public virtual Discriminator SelectedDiscriminator { get; set; }

    // left out Equals and GetHashcode
}

public class Discriminator
{
    public Discriminator()
    {
        LocalizedTexts = new Dictionary<int, string>();
    }

    public virtual int Id { get; set; }
    public virtual string Name
    {
        get
        {
            switch (Id)
            {
                case 1:
                    return "Discriminator A";
                case 2:
                    return "Discriminator B";
                case 3:
                    return "Discriminator C";
                default:
                    return "Unknown";
            }
        }
    }
    public virtual IDictionary<int, LocalizedText> LocalizedTexts { get; protected set; }

    public override bool Equals(object obj)
    {
        var other = obj as Discriminator;

        return other != null && (Id == 0 ? ReferenceEquals(this, other) : Id == other.Id);
    }

    public override int GetHashCode()
    {
        return Id;
    }
}

public class LocalizedText
{
    public string DisplayName { get; set; }
    public string Description { get; set; }
}

with mapping

public class TriageMap : ClassMap<Triage>
{
    public TriageMap()
    {
        Id(x => x.Id).GeneratedBy.GuidComb();

        References(x => x.SelectedDiscriminator, "SelectedDiscriminatorId");
    }
}

public class DiscriminatorMap : ClassMap<Discriminator>
{
    public DiscriminatorMap()
    {
        ReadOnly();
        SchemaExport.None();   // do not create Table for this. usefull for creating Schema for in memory unit-testing
        Table("Discriminators");
        Where("LanguageId = <some value all discriminators have>");
        Id(x => x.Id).GeneratedBy.Assigned();

        HasMany(x => x.LocalizedTexts)
            .Table("Discriminators")
            .KeyColumn("Id")
            .AsMap("LanguageId")
            .Component(c =>
            {
                c.Map(x => x.DisplayName);
                c.Map(x => x.Description);
            })
            .Cascade.AllDeleteOrphan()
            .Not.LazyLoad();
    }
}

only drawback is that the sql will look a bit weird because NHibernate thinks that discriminator will exists on its own.

like image 99
Firo Avatar answered Jan 04 '23 15:01

Firo