2010-05-04 26 views
13

Tôi có hai thực thể trong một mối quan hệ hai chiều one-to-many:Làm cách nào để thay đổi cha mẹ của trẻ trong NHibernate khi thác là xóa toàn bộ trẻ mồ côi?

public class Storage 
{ 
    public IList<Box> Boxes { get; set; } 
} 

public class Box 
{ 
    public Storage CurrentStorage { get; set; } 
} 

Và ánh xạ:

<class name="Storage"> 
    <bag name="Boxes" cascade="all-delete-orphan" inverse="true"> 
     <key column="Storage_Id" /> 
     <one-to-many class="Box" /> 
    </bag> 
</class> 

<class name="Box"> 
    <many-to-one name="CurrentStorage" column="Storage_Id" /> 
</class> 

Một Storage có thể có nhiều Boxes, nhưng một Box chỉ có thể thuộc về một Storage. Tôi đã ánh xạ chúng sao cho một người có nhiều tầng là all-delete-orphan.

Sự cố của tôi nảy sinh khi tôi cố gắng thay đổi Hộp Storage. Giả sử tôi đã chạy mã này:

var storage1 = new Storage(); 
var storage2 = new Storage(); 
storage1.Boxes.Add(new Box()); 

Session.Create(storage1); 
Session.Create(storage2); 

Đoạn mã dưới đây sẽ cung cấp cho tôi một ngoại lệ:

// get the first and only box in the DB 
var existingBox = Database.GetBox().First(); 

// remove the box from storage1 
existingBox.CurrentStorage.Boxes.Remove(existingBox); 

// add the box to storage2 after it's been removed from storage1 
var storage2 = Database.GetStorage().Second(); 
storage2.Boxes.Add(existingBox); 

Session.Flush(); // commit changes to DB 

tôi nhận được ngoại lệ sau đây:

NHibernate.ObjectDeletedException: đối tượng bị xóa sẽ được lưu lại bằng cách xếp chồng (xóa đối tượng đã xóa khỏi các liên kết)

Trường hợp ngoại lệ này xảy ra vì tôi đã đặt tầng thác là all-delete-orphan. Đầu tiên Storage phát hiện rằng tôi đã xóa Box khỏi bộ sưu tập của nó và đánh dấu nó để xóa. Tuy nhiên, khi tôi thêm nó vào thứ hai Storage (trong cùng một phiên), nó cố gắng để lưu hộp một lần nữa và ObjectDeletedException được ném.

Câu hỏi của tôi là, làm cách nào để nhận được số Box để thay đổi phụ huynh Storage mà không gặp phải ngoại lệ này? Tôi biết một giải pháp có thể là để thay đổi các thác chỉ all, nhưng sau đó tôi mất khả năng có NHibernate tự động xóa một Box bằng cách loại bỏ nó từ một Storage và không tái kết hợp nó với một số khác. Hay đây là cách duy nhất để làm điều đó và tôi phải tự gọi Session.Delete trên hộp để xóa nó?

+0

Điều gì sẽ xảy ra nếu bạn không bao giờ xóa hộp khỏi bộ nhớ 1? Nếu bạn chỉ di chuyển nó vào bộ nhớ 2, liệu CurrentStorage có bị ghi đè không? Tôi không chắc chắn nếu điều đó hoạt động nếu storage1 đã được nạp trong phiên. – dotjoe

+0

Nó hoạt động, nhưng cho đến khi tôi làm mới các thực thể của tôi, tôi sẽ có một bản sao của hộp trong cả hai kho. Tôi muốn có mô hình dữ liệu của tôi được chính xác hơn là dựa vào NHib để làm điều đúng khi tôi lấy thực thể. –

+0

À vâng, đứa trẻ mồ côi sẽ luôn bị xóa nếu bạn xóa khỏi bộ sưu tập. Tôi nghĩ trong trường hợp này bạn muốn làm như bạn đã nói ở phần cuối, bạn muốn 'cascade =" all "' và xóa một hộp bằng cách xóa khỏi bộ sưu tập và gọi 'session.Delete (box)'. Tôi không nghĩ rằng bạn có thể có tốt nhất của cả hai thế giới: ( – dotjoe

Trả lời

9

Xem http://fabiomaulo.blogspot.com/2009/09/nhibernate-tree-re-parenting.html

Về cơ bản, nó nắm này ... Bạn cần phải xác định một loại bộ sưu tập tùy chỉnh cho NHibernate rằng tái định nghĩa những gì nó có nghĩa là một đứa trẻ mồ côi. Hành vi mặc định của NHibernate là làm như bạn đã khám phá - để xem xét một đứa trẻ bị mồ côi nếu nó đã được gỡ bỏ khỏi cha mẹ. Thay vào đó, bạn cần NHibernate để kiểm tra xem liệu nó có được gán cho một phụ huynh mới hay không. NHibernate không làm điều này theo mặc định bởi vì nó sẽ yêu cầu thông tin bổ sung về ánh xạ một-nhiều - nó sẽ cần phải biết tên của thuộc tính nhiều-một trong tương ứng trên con.

Thay đổi bản đồ Storage của bạn trông như thế này:

<class name="Storage"> 
    <bag name="Boxes" cascade="all-delete-orphan" inverse="true" collection-type="StorageBoxBag"> 
     <key column="Storage_Id" /> 
     <one-to-many class="Box" /> 
    </bag> 
</class> 

Xác định một loại mới có tên StorageBoxBag (lưu ý - mã này được viết với NHibernate 2.1 - nếu bạn đang sử dụng NH3 bạn có thể phải tinh chỉnh này một chút):

public class StorageBoxBag : IUserCollectionType 
{ 
    public object Instantiate(int anticipatedSize) 
    { 
     return new List<Box>(); 
    } 

    public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister) 
    { 
     return new PersistentStorageBoxBag(session); 
    } 

    public IPersistentCollection Wrap(ISessionImplementor session, object collection) 
    { 
     return new PersistentStorageBoxBag(session, (IList<Box>)collection); 
    } 

    public IEnumerable GetElements(object collection) 
    { 
     return (IEnumerable)collection; 
    } 

    public bool Contains(object collection, object entity) 
    { 
     return ((IList<Box>)collection).Contains((Box)entity); 
    } 

    public object IndexOf(object collection, object entity) 
    { 
     return ((IList<Box>) collection).IndexOf((Box) entity); 
    } 

    public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session) 
    { 
     var result = (IList<Box>)target; 
     result.Clear(); 

     foreach (var box in (IEnumerable)original) 
      result.Add((Box)box); 

     return result; 
    } 
} 

...và một loại mới có tên PersistentStorageBoxBag:

public class PersistentStorageBoxBag: PersistentGenericBag<Box> 
{ 
    public PersistentStorageBoxBag(ISessionImplementor session) 
     : base(session) 
    { 
    } 

    public PersistentStorageBoxBag(ISessionImplementor session, ICollection<Box> original) 
     : base(session, original) 
    { 
    } 

    public override ICollection GetOrphans(object snapshot, string entityName) 
    { 
     var orphans = base.GetOrphans(snapshot, entityName) 
      .Cast<Box>() 
      .Where(b => ReferenceEquals(null, b.CurrentStorage)) 
      .ToArray(); 

     return orphans; 
    } 
} 

Phương pháp GetOrphans là nơi sự kỳ diệu xảy ra. Chúng tôi yêu cầu NHibernate cho danh sách Box es rằng nó nghĩ rằng là trẻ mồ côi, sau đó lọc xuống chỉ một tập hợp số Box es rằng thực sự là là trẻ mồ côi.

+0

Câu trả lời hay, Daniel! Có cách nào để chỉ định tùy chỉnh không thu thập-loại bằng cách sử dụng Fluent NHibernate? – MylesRip

+0

Google đã dẫn tôi đến câu trả lời này: http://stackoverflow.com/questions/2899576/automapping-custom-collections-with-fluentnhibernate/2900889#2900889 ... mặc dù tôi chưa bao giờ thử nó Nếu tất cả thất bại, bạn có thể thiết lập mã khởi tạo NHibernate của bạn để tự động ánh xạ các lớp nhất định, sau đó ClassMap những lớp không thể tự động hóa và cuối cùng ghi các tệp * .hbm.xml cho những tệp không thể được ClassMapped –

+0

Đã thử, hoạt động hoàn hảo! Cảm ơn bài đăng này! – Falcon

Các vấn đề liên quan