2013-04-24 2 views
1

я борюсь с Fluent NHibernate из Fluent Nhibernate Many-to-Many mapping with extra columnFluent NHibernate многие ко многим с дополнительной колонкой не вставляет

Я скопировал отображение и написал маленькую программу, которую я могу ... но это привычка экономить ... Кто-нибудь сможет дать некоторое представление?

public class Product 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public IList<Inventory> Inventory { get; set; } 

    public Product() 
    { 
     Inventory = new List<Inventory>(); 
    } 
} 

public class Warehouse 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public IList<Inventory> Inventory { get; set; } 

    public Warehouse() 
    { 
     Inventory = new List<Inventory>(); 
    } 
} 

public class Inventory 
{ 
    public Product Product { get; set; } 
    public Warehouse Warehouse { get; set; } 
    public bool StockInHand { get; set; } 


    // override object.Equals 
    public override bool Equals(object obj) 
    { 
     if (obj == null || GetType() != obj.GetType()) 
     { 
      return false; 
     } 
     var i = obj as Inventory; 

     return ((i.Product.Id == this.Product.Id) 
      && (i.Warehouse.Id == this.Warehouse.Id)); 
    } 

    // override object.GetHashCode 
    public override int GetHashCode() 
    { 
     return 9999; 
    } 
} 
public class ProductMap : ClassMap<Product> 
{ 
    public ProductMap() 
    { 
     Not.LazyLoad(); 
     Table("Product"); 
     Id(x => x.Id).GeneratedBy.Assigned(); 
     Map(x => x.Name); 
     HasMany(x => x.Inventory).AsBag() 
     .Cascade.All() 
     //.Inverse() 
     .Table("Inventory"); 
    } 
} 
public class WarehouseMap : ClassMap<Warehouse> 
{ 
    public WarehouseMap() 
    { 
     Not.LazyLoad(); 
     Table("Warehouse"); 
     Id(x => x.Id).GeneratedBy.Assigned(); 
     Map(x => x.Name); 
     HasMany(x => x.Inventory).AsBag() 
     .Cascade.All() 
     .Inverse() 
     .Table("Inventory"); 
    } 
} 
public class InventoryMap : ClassMap<Inventory> 
{ 
    public InventoryMap() 
    { 
     Not.LazyLoad(); 
     Table("Inventory"); 
     CompositeId() 
      .KeyReference(x => x.Product, "Product_id") 
      .KeyReference(x => x.Warehouse, "Warehouse_id"); 

     Map(x => x.StockInHand); 
    } 
} 

И программа ...

using (var session = sessionFactory.OpenSession()) 
{ 
    using (var transaction = session.BeginTransaction()) 
    { 
     Product p = new Product() { Id = 1, Name="product" }; 
     Inventory i = new Inventory() { StockInHand = true }; 
     i.Product = p; 
     p.Inventory.Add(i); 
     Warehouse w = new Warehouse() { Id = 1, Name = "warehouse" }; 
     i.Warehouse = w; 
     w.Inventory.Add(i); 

     session.SaveOrUpdate(p); 

     session.Flush(); 

     transaction.Commit(); 
    } 
} 

Исключение я получаю

constraint failed\r\nforeign key constraint failed 

Я также выводить создавать отчеты, которые выглядят правильно мне ...

create table Inventory (
    Product_id INT not null, 
    Warehouse_id INT not null, 
    StockInHand BOOL, 
    primary key (Product_id, Warehouse_id), 
    constraint FK2B4C61665C5B845 foreign key (Product_id) references Product, 
    constraint FK2B4C616A6DE7382 foreign key (Warehouse_id) references Warehouse) 

create table Product (
    Id INT not null, 
    Name TEXT, 
    primary key (Id) 
) 

create table Warehouse (
    Id INT not null, 
    Name TEXT, 
    primary key (Id) 
) 

И SQL, который запускается при или к исключению ....

NHibernate: 
INSERT 
INTO 
    Warehouse 
    (Name, Id) 
VALUES 
    (@p0, @p1); 
@p0 = 'warehouse' [Type: String (0)], @p1 = 1 [Type: Int32 (0)] 
NHibernate: 
INSERT 
INTO 
    Inventory 
    (StockInHand, Product_id, Warehouse_id) 
VALUES 
    (@p0, @p1, @p2); 
@p0 = True [Type: Boolean (0)], @p1 = 1 [Type: Int32 (0)], @p2 = 1 [Type: Int32 (0)] 

Так как же это должно работать правильно?!?

+0

Там есть ошибка в вас классе 'WarehouseMap'. Не следует ли «Таблица (« Инвентарь ») быть чем-то вроде« Таблица («Склад»)? – mickfold

+0

@Penfold Я обновил вопрос на основе вашего комментария. Я думаю, что инструкции create table показывают, что (я думаю) я должен ожидать в этом сценарии – 0909EM

ответ

4

Причина вашей проблемы в том, что NHibernate пытается вставить запись Inventory перед записью Warehouse. Это связано с тем, что порядок вставки определяется порядком, в котором вызывается session.Save. Основываясь на этой информации, я попробовал несколько вариантов кода, которые предотвратят ошибку ограничения внешнего ключа. Я опубликовал мое лучшее решение ниже.

using (var session = sessionFactory.OpenSession()) 
using (var transaction = session.BeginTransaction()) 
{ 
    var warehouse = new Warehouse() { Id = 1, Name = "warehouse" }; 
    session.Save(warehouse); 

    var product = new Product() {Id = 1, Name = "product"}; 
    var inventory = new Inventory 
        { StockInHand = true, Product = product, Warehouse = warehouse}; 

    product.Inventory.Add(inventory); 
    warehouse.Inventory.Add(inventory); 

    session.Save(product); 

    transaction.Commit(); 
} 

Одна вещи, я обнаружил, что меня удивило совсем немного, что если ставят session.Save(warehouse) после warehouse.Inventory.Add(inventory) затем NHibernate не вставляет Warehouse записи первого и брошен иностранная Ключевая ошибка.

В качестве окончательного примечания для получения трех операторов вставки, перечисленных ниже, Inverse() должен быть восстановлен в классе отображения ProductMap. В противном случае NHixernate выдает дополнительную инструкцию по обновлению.

INSERT INTO Warehouse (Name, Id) VALUES (@p0, @p1);@p0 = 'warehouse' 
[Type: String (4000)], @p1 = 1 [Type: Int32 (0)] 

INSERT INTO Product (Name, Id) VALUES (@p0, @p1); 
@p0 = 'product' [Type: String (4000)], @p1 = 1 [Type: Int32 (0)] 

INSERT INTO Inventory (StockInHand, Product_id, Warehouse_id) VALUES (@p0, @p1, @p2); 
@p0 = True [Type: Boolean (0)], @p1 = 1 [Type: Int32 (0)], @p2 = 1 [Type: Int32 (0)] 
+0

То, что вы здесь говорите, довольно разумно. Проблема заключается в том, что Nhibernate (или, скорее, свободное владение) требует, чтобы ключи были на месте, прежде чем вставлять в таблицу ссылок (которая имеет ссылку внешнего ключа для каждой соседней таблицы). Проблема в том, что Fluent NHibernate * должен * выработать порядок заполнения этих таблиц - т. Е. До таблицы ссылок. В моем случае в таблице ссылок есть дополнительные данные, которые описывают что-то о взаимосвязи между сущностями. Я относительно рад принять ваш ответ, но считаю его неоптимальным (лучший ответ, который я получил)! – 0909EM

+0

Как я уже сказал в своем ответе, NHibernate не работал точно так, как я ожидал. Похоже, вы попали в кромку. NHibernate очень хорош, но он не идеален. Извините, я не могу быть более полезным. – mickfold

+0

нет, ваш ответ был блестящим - зеркала, с которыми я столкнулся (см. Ниже, если вы хотите прочитать). Может быть, я что-то пропустил, ** возможно **, это тихо где-то документировано. Я продолжу искать. Спасибо за ввод. – 0909EM

3

Для всех, кто может следовать по моим стопам ...

Моя проблема заключается в том, что я хочу сохранить некоторую информацию о взаимосвязи между объектами в таблице ссылок. Классически отношение многих к многим в терминах базы данных имеет таблицу ссылок между объектами домена.

Со структурой, очерченной в вопросе, необходимо вставить склад и продукт, прежде чем можно будет добавить элемент инвентаря. Элемент инвентаризации должен быть вставлен последним, чтобы оба ограничения внешнего ключа были установлены до начала сохранения.

Insert into Product 

Insert into Warehouse 

Insert into Inventory (Note this happens **after** the primary keys are 
inserted in Warehouse and Product!) 

Однако я работаю мои сопоставления Fluent NHibernate генерирует следующий ...

Insert into Product 

Insert into Inventory (Foreign Key constraint violated, no Warehouse) 

.. который должен быть неправильно, потому что нет первичного ключа на складе! Я понимаю проблему, но не решение ... Мне удалось создать два решения, как показано ниже, но я считаю, что они являются субоптимальными.

public class ProductMap : ClassMap<Product> 
{ 
    public ProductMap() 
    { 
     Not.LazyLoad(); 
     Table("Product"); 
     Id(x => x.Id, "Product_id").GeneratedBy.Assigned(); 
     Map(x => x.Name).Column("Name").Length(10); 
     HasMany(x => x.Inventory) 
      .Cascade.Delete() 
      .KeyColumn("Product_id"); 
    } 
} 
public class WarehouseMap : ClassMap<Warehouse> 
{ 
    public WarehouseMap() 
    { 
     Not.LazyLoad(); 
     Table("Warehouse"); 
     Id(x => x.Id, "Warehouse_id").GeneratedBy.Assigned(); 
     Map(x => x.Name).Column("Name").Length(10); 
     HasMany(x => x.Inventory) 
      .Cascade.All() 
      .KeyColumn("Warehouse_id"); 
    } 
} 
public class InventoryMap : ClassMap<Inventory> 
{ 
    public InventoryMap() 
    { 
     Not.LazyLoad(); 
     Table("Inventory"); 
     CompositeId() 
      .KeyReference(x => x.Product, "Product_id") 
      .KeyReference(x => x.Warehouse, "Warehouse_id"); 
     Map(x => x.StockInHand); 
    } 
} 

Я могу выполнить сохранение и иметь половину кода, как я ожидаю, но он предполагает обыденность. То есть, я должен иметь некоторое представление о порядке, в котором эти объекты нуждаются в сохранении. Я не тестировал, что происходит с удалением, но это означает, что я должен следовать правильному порядку сохранения этих объектов. /* works */ session.Save (продукт); session.Save (склад); // также сохранить Инвентарь

/* would fail */ 
session.Save(warehouse); 
session.Save(product); 

В качестве альтернативы (и мне это нравится еще меньше), я могу сказать NHibernate, что я хочу быть ответственным за все ...

public class ProductMap : ClassMap<Product> 
{ 
    public ProductMap() 
    { 
     Not.LazyLoad(); 
     Table("Product"); 
     Id(x => x.Id, "Product_id").GeneratedBy.Assigned(); 
     Map(x => x.Name).Column("Name").Length(10); 
     HasMany(x => x.Inventory).Inverse(); 
    } 
} 
public class WarehouseMap : ClassMap<Warehouse> 
{ 
    public WarehouseMap() 
    { 
     Not.LazyLoad(); 
     Table("Warehouse"); 
     Id(x => x.Id, "Warehouse_id").GeneratedBy.Assigned(); 
     Map(x => x.Name).Column("Name").Length(10); 
     HasMany(x => x.Inventory).Inverse(); 
    } 
} 
public class InventoryMap : ClassMap<Inventory> 
{ 
    public InventoryMap() 
    { 
     Not.LazyLoad(); 
     Table("Inventory"); 
     CompositeId() 
      .KeyReference(x => x.Product, "Product_id") 
      .KeyReference(x => x.Warehouse, "Warehouse_id"); 
     Map(x => x.StockInHand); 
    } 
} 

Теперь я получаю следующее

Может ли кто-нибудь улучшить это и дать мне сопоставления, которые я действительно хочу, чтобы я мог сохранить склад или продукт и Fluent NHibernate, чтобы получить правильность заказа! ?? например.

session.Save(warehouse); // or session.Save(product); 

так, что это приводит к

Insert into Warehouse... 
Insert into Product... 
Insert into Inventory... // where NHibernate determines this goes last so that primary keys are in place on both previous tables! 
Смежные вопросы