NHibernate - mapping many-to-one and many-to-many lookup table relationships without using a second class

StackOverflow https://stackoverflow.com/questions/19727469

سؤال

The database I'm mapping contains Plan entities. Each Plan entity has a main Department (a many-to-one relationship) and a bag of possible additional Departments (a many-to-many relationship). Departments are stored in a lookup table that has only 'ID' and 'NAME' columns. The 'PLAN' table has a 'DEPARTMENT_ID' foreign key column that stores an id from the 'DEPARTMENT' table to reference the main Department. Relationships between a Plan and additional Departments are stored in a cross-reference table that has the following columns: 'PLAN_ID' and 'DEPARTMENT_ID'.

Now, I've successfully mapped this structure using a Plan class and a Department class:

public class Plan
{
    public virtual int Id { get; set; }

    public virtual Department ExecutorDepartment { get; set; }

    public virtual IList<Department> AdditionalExecutorDepartments { get; set; }
}

public class Department
{
    public virtual int Id { get; set; }

    public virtual string Name { get; set; }
}

<class name="Plan" table="PLAN">
  <id name="Id" unsaved-value="0">
    <column name="ID" not-null="true" />
    <generator class="native"/>
  </id>
  <many-to-one name="ExecutorDepartment" class="Department" column="DEPARTMENT_ID"/>
  <bag name="AdditionalExecutorDepartments" table="PLAN_DEPARTMENT">
    <key column="PLAN_ID" />
    <many-to-many class="Department" column="DEPARTMENT_ID"></many-to-many>
  </bag>
</class>
<class name="Department" table="DEPARTMENT">
  <id name="Id" unsaved-value="0">
    <column name="ID" not-null="true" />
    <generator class="native"/>
  </id>
  <property name="Name" column="NAME" type="string"/>
</class>

However, I'm looking for a way to remove the Department class (which is completely useless as far as application logic is concerned), and have the Plan class store department names as strings, like this:

public class Plan
{
    public virtual int Id { get; set; }

    public virtual string ExecutorDepartment { get; set; }

    public virtual IList<string> AdditionalExecutorDepartments { get; set; }
}

Is there any way to do it? Just to mention, I'm not going to update the data in the database, and I can't change the database structure.

P.S. I tried using the <Join> element to get the main department, but apparently it can only join on the primary key (see https://nhibernate.jira.com/browse/NH-1452).

هل كانت مفيدة؟

المحلول

Most likely, the Department class is not entirely useless. What does the following code mean?

plan.ExecutorDepartment = "Hyoomun Reesorsis";
plan.ExecutorDepartment = "Human Resources";

You can't tell if we're just fixing the spelling or if we're assigning it to a different department.

Is it ever possible for the name of a department to change? If so, does that change need to be reflected in all Plans that belong to that department? If that's the case, then from a Domain Driven Design perspective, a Department is an entity, not a value object. If it's an entity, then you should continue to map those relationships with many-to-one and many-to-many. This makes your code more descriptive. To fix the spelling, you say...

plan.ExecutorDepartment.Name = "Human Resources";

... or to assign the plan to a new department, you say:

plan.ExecutorDepartment = theOtherHrDepartmentThatActuallyCaresAboutSpelling;

This version of the code is much clearer about what it is trying to do.

NHibernate is a tool that allows you to work with your relational database in an object-oriented, domain-driven fashion. It works best when you try to follow DDD rules. What you are asking to do doesn't make sense from a DDD perspective, because Department does have an identity, so NHibernate doesn't support pretending that it doesn't have identity. There is no mapping for what you want to do. If you need to model it this way, you have a few options.

A. Add properties to dig in and get the strings for you.

public virtual string ExecutorDepartmentName
{
    get { return ExecutorDepartment != null ? ExecutorDepartment.Name : null; }
}

public virtual IEnumerable<string> AdditionalExecutorDepartmentNames
{
    get { return AdditionalExecutorDepartments.Select(x => x.Name); }
}

B. Change the database schema, removing the Department table, and replacing any Department_id columns with DepartmentName. Do this if department really is supposed to be a value object, not an entity. After changing the schema, your mappings would need to change from many-to-one and bag many-to-many, to property and bag element.

C. If what you want is similar to option "A", but you also want to be able to change departments without messing with the Department_id - you can get something like that if you change Department's primary key to be the string name (provided that it is unique and not null). This will allow you to use session.Load to get a Department instance from the string name without hitting the database.

So, all of this probably isn't the silver bullet "here's this amazing mapping you didn't know about" that you were hoping for, but these are all of the realistic options I could think of, and you should be able to find something that meets your needs within this list.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top