2

OK, I'm scratching my head once again. I have a class like so:

public class ReconciliationReportLineItem
{
    public virtual int ID { get; set; }
    public virtual int Category { get; set; }
    public virtual string FileName { get; set; }
    public virtual decimal Amount { get; set; }
    public virtual string BatchType { get; set; }
    public virtual DateTime CreatedTimeStamp { get; set; }
    public virtual string Currency { get; set; }
    public virtual decimal LocalAmount { get; set; }
    public virtual int NumberOfInvoices { get; set; }

    public override bool Equals(object obj)
    {
        var t = obj as ReconciliationReportLineItem;
        if (t == null) return false;
        return
            t.ID == this.ID
            && t.Category == this.Category;
    }

    public override int GetHashCode()
    {
        return string.Format("{0}{1}{2:yyyyMMddHHmmss}{3}{4}{5}{6}",
            this.FileName, this.BatchType, this.CreatedTimeStamp,
            this.NumberOfInvoices, this.LocalAmount, this.Amount,
            this.Currency).GetHashCode();
    }
}

and my fluent mapping file as such:

public class ReconciliationReportLineItemMapping : ClassMap<ReconciliationReportLineItem>
{
    public ReconciliationReportLineItemMapping()
    {
        Table("ReconciliationReportLineItem");

        CompositeId()
            .KeyProperty(x => x.ID, "id")
            .KeyProperty(x => x.Category, "category");

        Map(x => x.FileName)
            .Length(500)
            .Nullable()
            .Index("ixDatroseReconciliationReportLineItemFileName");

        Map(x => x.Amount)
            .Not.Nullable();

        Map(x => x.BatchType)
            .Not.Nullable();

        Map(x => x.CreatedTimeStamp)
            .Index("ixDatroseReconciliationReportLineItemCreatedTimeStamp")
            .Not.Nullable();

        Map(x => x.Currency)
            .Not.Nullable();

        Map(x => x.LocalAmount)
            .Not.Nullable();

        Map(x => x.NumberOfInvoices)
            .Not.Nullable();
    }
}

I populate the object, try to commit it, and I get an error like so:

ERROR: 23505: duplicate key value violates unique constraint "reconciliationreportlineitem_pkey"

The error sql shows that the members of the composite key (which were preset) are set to zero:

INSERT INTO ReconciliationReportLineItem (FileName, Amount, BatchType, CreatedTimeStamp, Currency, LocalAmount, NumberOfInvoices, id, category) 
VALUES (((NULL)::text), ((E'1065.47')::numeric), ((E'X200 batch created 20121027')::text), ((E'2012-10-27 08:39:00.000000')::timestamp), ((E'USD')::text), ((E'1065.47')::numeric), ((7)::int4), ((0)::int4), ((0)::int4))

...but I had specified the values before I tried to merge the records into the table. With a breakpoint, I was able to verify the objects did in fact have values before committing the session transaction.

What am I doing wrong? I need to specify the values of the keys.

Jeremy Holovacs
  • 22,480
  • 33
  • 117
  • 254

1 Answers1

2

I don't remember what the NHibernate default key generator is but try adding this to tell NH you're going to assign the keys using a component as an identifier. There aren't many examples for this approach but this forum post is partial one. Here is the updated code:

// snipped >%

CompositeId()
    .ComponentCompositeIdentifier<ReconciliationReportLineItemKey>
       (rrli => rrli.Key)
    .KeyProperty(k => k.Key.Id)
    .KeyProperty(k => k.Key.Category);

// snipped >%

You'll need to add a new class for the key to make assignments for:

public class ReconciliationReportLineItemKey
{
    public virtual int Id { get; set; }
    public virtual int Category { get; set; }
}

and also add a property the component to your entity class:

public class ReconciliationReportLineItem
{
    // snipped >%

    public virtual ReconciliationReportLineItemKey Key { get; set; }

    // snipped >%
}
Sixto Saez
  • 12,610
  • 5
  • 43
  • 51
  • unfortunately that is not an option. – Jeremy Holovacs Nov 26 '12 at 19:14
  • In that case, you're still going to need replace the default generator. You can create a custom generator by creating a class that implements `IIdentifierGenerator`. Look at the code in [SO Q&A](http://stackoverflow.com/questions/2222704/stack-overflow-whilst-cascade-saving-nhibernate-entity-with-custom-generator) for an example. In Fluent NHibernate, you'll need to use `.GeneratedBy.Custom(...` to get this mapped to the custom generator class. – Sixto Saez Nov 26 '12 at 19:30
  • `.GeneratedBy` is not an available property off `CompositeIdentityPart`; I think if it was, the `Assigned()` method would be available. – Jeremy Holovacs Nov 26 '12 at 19:47
  • You're absolutely right, I was writing code off the top of my head and assumed what worked for single value keys also worked for composite keys. I've updated the answer with another approach that may work for what you're trying to do. – Sixto Saez Nov 26 '12 at 20:18