2009-01-25 28 views
5

Làm cách nào để bạn thay đổi kiểu phụ của một hàng trong NHibernate? Ví dụ: nếu tôi có một thực thể Khách hàng và một lớp con của TierOneCustomer, tôi có trường hợp tôi cần phải thay đổi một Khách hàng thành một TierOneCustomer nhưng TierOneCustomer phải có cùng một Id (PK) làm thực thể Khách hàng ban đầu.NHibernate - Thay đổi loại phụ

Việc lập bản đồ trông giống như sau:

<class name="Customer" table="SiteCustomer" discriminator-value="C"> 
    <id name="Id" column="Id" type="Int64"> 
    <generator class="identity" /> 
    </id> 
    <discriminator column="CustomerType" /> 
    ... properties snipped ... 

    <subclass name="TierOneCustomer" discriminator-value="P"> 
    ... more properties ... 
    </subclass> 
</class> 

Tôi đang sử dụng một bảng cho mỗi lớp mô hình phân cấp, vì vậy sử dụng đơn giản-sql, nó muốn được chỉ là vấn đề của một bản cập nhật sql của discriminator (CustomerType) và thiết lập các cột thích hợp có liên quan cho loại. Tôi không thể tìm thấy giải pháp trong NHibernate, vì vậy sẽ đánh giá cao bất kỳ con trỏ.

Tôi cũng đang suy nghĩ xem mô hình có chính xác khi xem xét trường hợp sử dụng này hay không, nhưng trước khi tôi đi xuống tuyến đường đó, tôi muốn đảm bảo thực hiện như đã mô tả ở trên. Nếu không, tôi gần như chắc chắn sẽ nghĩ về việc thay đổi mô hình.

Trả lời

9

Câu trả lời ngắn gọn là có, bạn có thể thay đổi giá trị phân biệt đối xử cho (các) hàng cụ thể bằng cách sử dụng native SQL. Tuy nhiên, tôi không nghĩ NHibernate có ý định làm việc theo cách này, vì phân biệt đối xử nói chung là "vô hình" với lớp Java, nơi giá trị của nó được cho là được thiết lập ban đầu theo lớp của đối tượng được tiếp tục tồn tại và không bao giờ thay đổi.

Tôi khuyên bạn nên xem xét một cách tiếp cận rõ ràng hơn. Từ quan điểm của mô hình đối tượng, bạn đang cố chuyển đổi một đối tượng siêu lớp thành một trong các kiểu phân lớp của nó trong khi không thay đổi nhận dạng của cá thể tồn tại của nó, và đó là nơi xung đột là (đối tượng được chuyển đổi không thực sự phải là giống nhau cả thôi). Hai phương pháp thay thế khác là:

  • Tạo phiên bản mới của TierOneCustomer dựa trên thông tin trong đối tượng Khách hàng ban đầu, sau đó xóa đối tượng gốc. Nếu bạn đang dựa vào Khóa chính của Khách hàng để truy xuất, bạn cần lưu ý về PK mới.

hoặc

  • Thay đổi cách tiếp cận của bạn để loại đối tượng (phân biệt) không cần phải thay đổi. Thay vì dựa vào phân lớp để phân biệt TierOneCustomer với Khách hàng, bạn có thể sử dụng thuộc tính mà bạn có thể sửa đổi tự do bất kỳ lúc nào, tức là Customer.Tier = 1.

Dưới đây là một số cuộc thảo luận liên quan trên Diễn đàn Hibernate được quan tâm:

  1. Can we update the discriminator column in Hibernate
  2. Table-per-Class Problem: Discriminator and Property
  3. Converting a persisted instance into a subclass
+0

Vâng, tôi nghĩ tôi sẽ cấu trúc lại cấu trúc phân cấp và có thể chọn cách tiếp cận thuộc tính đơn giản. Câu trả lời tuyệt vời, cảm ơn. –

1

Nếu bạn đang làm ngoại tuyến (ví dụ: trong tập lệnh nâng cấp DB), chỉ cần sử dụng SQL và đảm bảo tính nhất quán.

Nếu đây là điều bạn dự định sẽ xảy ra trong khi ứng dụng đang chạy, tôi nghĩ yêu cầu của bạn sai, giống như giữ cùng một địa chỉ con trỏ cho một đối tượng khác là sai.

Nếu bạn lưu ID và sử dụng ID đó để truy cập lại khách hàng (ví dụ: trong URL), hãy xem xét tạo trường mới chứa mã thông báo cho khóa này sẽ là khóa doanh nghiệp. Vì nó không phải là ID, thật dễ dàng để tạo một cá thể thực thể mới và sao chép mã thông báo (có thể bạn sẽ cần phải xóa mã thông báo khỏi mã thông báo cũ).

5

Bạn đang làm điều gì đó sai.

Điều bạn đang cố gắng làm là thay đổi loại đối tượng. Bạn không thể làm điều đó trong .NET hoặc trong Java. Điều đó đơn giản là không có ý nghĩa. Một đối tượng là chính xác một loại cụ thể, và loại bê tông của nó không thể thay đổi từ thời điểm đối tượng được tạo ra cho đến khi đối tượng bị phá hủy (mặc dù ma thuật đen). Để thực hiện những gì bạn đang cố gắng làm, nhưng với thứ bậc lớp mà bạn đã đặt ra, bạn sẽ phải hủy đối tượng khách hàng mà bạn muốn biến thành đối tượng khách hàng cấp một, tạo đối tượng khách hàng cấp một mới, và sao chép tất cả các thuộc tính có liên quan từ đối tượng khách hàng sang đối tượng khách hàng cấp một. Đó là cách bạn làm điều đó với các đối tượng, trong các ngôn ngữ hướng đối tượng, với hệ thống phân cấp lớp của bạn.

Rõ ràng, cấu trúc phân cấp lớp bạn không hoạt động cho bạn. Bạn không phá hủy khách hàng trong cuộc sống thực khi họ trở thành khách hàng cấp một! Vì vậy, không làm điều đó với các đối tượng hoặc. Thay vào đó, hãy tìm ra một hệ thống phân cấp lớp có ý nghĩa, với các kịch bản bạn cần thực hiện. Các tình huống sử dụng của bạn bao gồm:

  • Khách hàng trước đây không phải là trạng thái cấp một trở thành trạng thái cấp một.

Điều đó có nghĩa là bạn cần phân cấp lớp có thể chụp chính xác kịch bản này. Như một gợi ý, bạn nên ưu tiên thành phần trên thừa kế. Điều đó có nghĩa là bạn nên có một thuộc tính có tên là IsTierOne hoặc thuộc tính có tên là DiscountStrategy, v.v., tùy thuộc vào những gì hoạt động tốt nhất.

Toàn bộ mục đích của NHibernate (và Hibernate cho Java) là làm cho cơ sở dữ liệu ẩn đi. Để cho phép bạn làm việc với các đối tượng một cách tự nhiên, với cơ sở dữ liệu một cách kỳ diệu ở đằng sau hậu trường để làm cho các đối tượng của bạn bền bỉ. NHibernate sẽ cho phép bạn làm việc với cơ sở dữ liệu nguyên bản, nhưng đó không phải là loại kịch bản mà NHibernate được xây dựng cho.

+0

Tôi thích câu trả lời này, nhưng cảm thấy David đã được nói tốt hơn một chút vì vậy tôi chấp nhận của mình. Tuy nhiên, nhiều người cảm ơn vì câu trả lời hay - tôi đã lên tiếng bình chọn. –

2

Đây là REALLY muộn, nhưng có thể được sử dụng cho người tiếp theo tìm cách để làm điều gì đó tương tự:

Trong khi câu trả lời khác là chính xác mà bạn không nên thay đổi bộ phân biệt trong hầu hết các trường hợp, bạn có thể làm điều đó hoàn toàn trong phạm vi của NH (không có bản địa SQL), với một số sử dụng thông minh của các thuộc tính ánh xạ. Dưới đây là các ý chính của nó bằng cách sử FluentNH:

public enum CustomerType //not sure it's needed 
{ 
    Customer, 
    TierOneCustomer 
} 

public class Customer 
{ 
    //You should be able to use the Type name instead, 
    //but I know this enum-based approach works 
    public virtual CustomerType Type 
    { 
     get {return CustomerType.Customer;} 
     set {} //small code smell; setter exists, no error, but it doesn't do anything. 
    } 
    ... 
} 

public class TierOneCustomer:Customer 
{ 
    public override CustomerType Type {get {return CustomerType.TierOneCustomer;} set{}} 
    ... 
} 

public class CustomerMap:ClassMap<Customer> 
{ 
    public CustomerMap() 
    { 
     ... 
     DiscriminateSubClassesOnColumn<string>("CustomerType"); 
     DiscriminatorValue(CustomerType.Customer.ToString()); 
     //here's the magic; make the discriminator updatable 
     //"Not.Insert()" is required to prevent the discriminator column 
     //showing up twice in an insert statement 
     Map(x => x.Type).Column("CustomerType").Update().Not.Insert(); 
    } 
} 

public class TierOneCustomerMap:SubclassMap<TierOneCustomer> 
{ 
    public CustomerMap() 
    { 
     //same idea, different discriminator value 
     ... 
     DiscriminatorValue(CustomerType.TierOneCustomer.ToString()); 
     ... 
    } 
} 

Kết quả cuối cùng là giá trị phân biệt được quy định cho chèn, và sử dụng để xác định loại instantiated trên thu hồi, nhưng sau đó nếu một bản ghi của một subtype khác nhau với cùng Id được lưu (như thể bản ghi được nhân bản hoặc không bị ràng buộc từ giao diện người dùng sang kiểu mới), giá trị phân biệt đối xử được cập nhật trên bản ghi hiện có với ID đó làm thuộc tính đối tượng, để các lần truy xuất tương lai của loại đó là đối tượng mới. Bộ setter được yêu cầu trên các thuộc tính vì AFAIK NHibernate không thể nói rằng một thuộc tính là chỉ đọc (và do đó "chỉ ghi" cho DB); trong thế giới của NHibernate, nếu bạn viết một cái gì đó cho DB, tại sao bạn không muốn nó trở lại?

Gần đây tôi đã sử dụng mẫu này để cho phép người dùng thay đổi loại cơ bản của "chuyến tham quan", thực tế là một bộ quy tắc điều chỉnh lịch biểu của chuyến tham quan "kỹ thuật số" thực tế thiết bị tại chỗ để đảm bảo tất cả hoạt động đúng cách). Trong khi tất cả chúng đều là "lịch trình du lịch" và cần phải được thu thập trong danh sách/hàng đợi, v.v., các loại lịch biểu khác nhau yêu cầu dữ liệu rất khác nhau và xử lý rất khác nhau, kêu gọi cấu trúc dữ liệu tương tự như OP. Do đó, tôi hoàn toàn hiểu được mong muốn của OP để đối xử với một TierOneCustomer theo một cách khác biệt đáng kể trong khi giảm thiểu hiệu ứng ở lớp dữ liệu, vì vậy, ở đây ya đi.

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