2

using FluentNhibernate;

I am trying to persist a seemingly simple object model:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Config Config { get; set; }
}

public class Config
{
    public int ConfigId { get; set; }
    public int ProductId { get; set; }
    public string ConfigField1 { get; set; }
    public string ConfigField2 { get; set; }
}

and the database looks like: (not syntactically correct)

CREATE TABLE [dbo].[Products](
[ProductId] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NULL
)

CREATE TABLE [dbo].[Config](
[ConfigId] [int] IDENTITY(1,1) NOT NULL,
[ProductId] [int] NOT NULL,
[ConfigField1] [varchar](50) NULL,
[ConfigField2] [varchar](50) NULL
) ON [PRIMARY]

Out of the box fluent-nhibernate will try and map this as a foreign key on the Products table, eg:

INSERT INTO Products (Name, Config_id) VALUES (?, ?);

I don't want it to do this, rather I was hoping the mapping would insert Products first then the config second with ProductId being inserted into the Config table.

I've pulled my hair out trying overrides and reading links such as this and this but still can't get it to do what I want. I am working with existing data and code so I would rather not change the database table definitions or the domain object. There is a bit more going on than what this example paints so if we could avoid discussions on domain model design that would be great. I have a link to a spike of this project here (assumes database exists)

my current fluent mappings are:

public class ProductOverrides : IAutoMappingOverride<Product>
{
    public void Override(AutoMapping<Product> mapping)
    {
        mapping.Id(x => x.Id).Column("ProductId");            
        mapping.Table("Products");
    }
}

public class ConfigOverrides : IAutoMappingOverride<Config>
{
    public void Override(AutoMapping<Config> mapping)
    {
        mapping.Id(x => x.ConfigId);
    }
}
wal
  • 17,409
  • 8
  • 74
  • 109
  • have you tried this? http://brunoreis.com/tech/fluent-nhibernate-hasone-how-implement-one-to-one-relationship/ – J. Ed Feb 02 '12 at 11:22
  • Can you explain how that helps? thats a one to one relationship which isnt what i need as `Config` has its own primary key `ConfigId` but needs to be linked back to Products via `ProductId` – wal Feb 02 '12 at 12:47

3 Answers3

4

You are trying to map a one-to-one relationship as a one-to-many by mapping what would be the many side twice. That won't work. NHibernate is strict in its definition of one-to-one and requires that both sides have the same primary key, so that won't work either.

I've butted heads with this before and the best workaround I found is to model it as a standard on-to-many but only allow one item in the collection by encapsulating access to the it. In your case I would guess that Product would be the one side and Config the many.

Community
  • 1
  • 1
Jamie Ide
  • 48,427
  • 16
  • 81
  • 117
  • to confirm, that would require adding a `List Configs` property to the `Product` class? – wal Feb 02 '12 at 13:11
  • No, add the collection (I recommend set mapping instead of list) as a private member and map it using an access strategy. Then control the collection through a public Config property that only allows one Config in the collection. See the linked question. – Jamie Ide Feb 02 '12 at 13:34
  • yea ok, its not ideal firstly as i'm trying to avoid changing my domain objects just to please nhibernate. secondly i'm sending it across the wire and a `Set` I'm assuming is an nhibernate class which i can't have. – wal Feb 02 '12 at 13:40
2

I'm not sure if Config is used elsewhere but you could ignore ConfigId as its identity

public class Config
{
    public int Id { get; set; }
    public Product Product { get; set; }
    public string ConfigField1 { get; set; }
    public string ConfigField2 { get; set; }
}

public class ProductMap : ClassMap<Product>
{
    public class ProductMap()
    {
        HasOne(p => p.Config);
    }
}

public class ConfigMap : ClassMap<Config>
{
    public class ConfigMap()
    {
        Id(c => c.Id, "ProductId").GeneratedBy.Foreign("Product");

        References(c => c.Product, "ProductId");
        Map(c => ...);
    }
}
Firo
  • 30,626
  • 4
  • 55
  • 94
  • Thanks as always for both answers. The issue with both of these solutions is that Config is also used by *another* class as a 'component' which rules out using ProductId as the key column (as its not defined on `Config` when `Config` is a property of a class other than `Product`. I'm thinking the easiest thing is to redefine the database to store `ConfigId` in the Product table – wal Feb 03 '12 at 10:49
  • of course changing the db if possible is the easiest. but you can map Config on the other class also as component with a different joincolumn() – Firo Feb 03 '12 at 11:02
  • What would you then do about this line tho: Id(c => c.Id, "ProductId").GeneratedBy.Foreign("Product"); that is, how will the other class add its own Id into the Config table given the aforementioned mapping is required for the `Product` Config to save? – wal Feb 03 '12 at 12:45
  • "by another class as a 'component'" --> my comment was for the component mapping you see in my other answer. – Firo Feb 03 '12 at 13:13
  • ok understood. i will get that method working with my spike and mark it as answered when complete. irl i'm going to just update the columns as thats much easier for nhibernate; Generally speaking does nHibernate do this the 'standard' way or is it arbitrary? – wal Feb 03 '12 at 13:26
  • NHibernate makes it easy to work with the standard way but has a lot of features aimed at non-standard database schemas – Firo Feb 03 '12 at 14:07
1

Another idea is to join and map as Component

public class ProductMap : ClassMap<Product>
{
    public class ProductMap()
    {
        Join("Config", join => 
        {
            join.KeyColumn("ProductId");
            join.Component(p => p.Config, c =>
            {
                c.Map(...);
            });
        }
    }
}

Disadvantage is that you can not query Config directly, you have to query through Product

Firo
  • 30,626
  • 4
  • 55
  • 94