1

给定的是以下类层次结构:

类图 http://img535.imageshack.us/img535/4802/personusermanager.jpg

附加信息:

  • Person 类不是抽象的。
  • 一个人可以是用户、经理或其他实现 IPerson 接口的东西。
  • Person 类不得对其子类有任何了解。
  • 子类可以驻留在另一个程序集中。
  • 一个人也有可能是一个用户和一个经理。在这种情况下,UserRepository 必须为给定的 PersonId 返回一个 User 对象,ManagerRepository 必须为相同的 PersonId 返回一个 Manager。
  • 还必须可以通过 PersonRepository 获取实现 IPerson 接口的所有对象的 Person(基础)部分。

这如何在 NHibernate 中映射?

我们正在使用 FluentNHibernate 1.2 和 NHibernate 3.1。

在我们目前的情况下,每个类都有自己的表。所以我们有一个 Person 表、一个 User 表和一个 Manager 表。

我已经尝试了以下选项但没有成功:

  • 用继承映射映射它(每个子类一个表);
  • 在 User 和 Manager 的映射中加入 Person(无继承映射);
  • 在 User 和 Manager 映射中带有 Person 的 HasOne 映射(没有连接和继承映射);
4

1 回答 1

2

正如您无疑发现的那样,像这样映射继承很容易:Person -> User,或 Person -> Manager,或 Person -> Manager -> User(或者,Person -> Manager -> User)。

NHibernate 不允许您从子类升级/降级。您必须运行本机 SQL 才能升级或降级。

但是,如果您遵循我最初的继承“地图”,您应该有一个顿悟,即使用子类来做您正在尝试做的事情对于您要建模的事情是不合适的解决方案。这只是两个子类!当您添加更多角色时会发生什么?

你所拥有的是一个 Person,他可以是任意数量的角色的成员,其中角色是可扩展的。考虑这个解决方案(github上的来源:https ://github.com/HackedByChinese/NHibernateComposition ):

(假设我们有一个处理相等性的实体抽象类,其中具有相同 ID 的相同类型的对象被认为是相等的)

项目:模型

public class Person : Entity, IPerson
{
    public virtual string FirstName { get; set; }

    public virtual string LastName { get; set; }

    public virtual IList<Role> Roles { get; protected set; }

    public Person()
    {
        Roles = new List<Role>();
    }

    public virtual void AddRole(Role role)
    {
        if (Roles.Contains(role)) return;

        role.Person = this;

        Roles.Add(role);
    }

    public virtual void RemoveRole(Role role)
    {
        if (!Roles.Contains(role)) return;

        role.Person = null;

        Roles.Remove(role);
    }
}

public interface IPerson
{
    string FirstName { get; set; }

    string LastName { get; set; }

    Int32 Id { get; }
}

public abstract class Role : Entity
{
    public virtual Person Person { get; set; }

    public virtual string RoleName { get; protected set; }
}

public class User : Role
{
    public virtual string LoginName { get; set; }

    public virtual string Password { get; set; }
}

项目:Models.B

public class Manager : Role
{
    public virtual string Division { get; set; }

    public virtual string Status { get; set; }
}

项目:Models.Impl

我将两个项目的流利映射合二为一以节省时间。可以很容易地为模型和模型建立单独的映射程序集。B

public class PersonMap : ClassMap<Person>
{
    public PersonMap()
    {
        Id(c => c.Id)
            .GeneratedBy.HiLo("100");

        Map(c => c.FirstName);
        Map(c => c.LastName);

        HasMany(c => c.Roles)
            .Inverse()
            .Cascade.AllDeleteOrphan();
    }
}

public class RoleMap : ClassMap<Role>
{
    public RoleMap()
    {
        Id(c => c.Id)
            .GeneratedBy.HiLo("100");

        DiscriminateSubClassesOnColumn<string>("RoleName");

        References(c => c.Person);
    }
}

public class UserMap : SubclassMap<User>
{
    public UserMap()
    {
        DiscriminatorValue("User");

        Join("User", joined =>
                         {
                             joined.Map(c => c.LoginName);
                             joined.Map(c => c.Password);
                         });
    }
}

项目:Models.Impl.Tests

[TestFixture]
public class MappingTests
{
    private ISessionFactory _factory;

    #region Setup/Teardown for fixture

    [TestFixtureSetUp]
    public void SetUpFixture()
    {
        if (File.Exists("test.db")) File.Delete("test.db");

        _factory = Fluently.Configure()
            .Database(() => SQLiteConfiguration.Standard
                                .UsingFile("test.db")
                                .ShowSql()
                                .FormatSql())
            .Mappings(mappings => mappings.FluentMappings
                                      .AddFromAssemblyOf<PersonMap>())
            .ExposeConfiguration(config =>
                                     {
                                         var exporter = new SchemaExport(config);
                                         exporter.Execute(true, true, false);
                                     })
            .BuildSessionFactory();
    }

    [TestFixtureTearDown]
    public void TearDownFixture()
    {
        _factory.Close();
    }

    #endregion

    #region Setup/Teardown for each test

    [SetUp]
    public void SetUpTest()
    {
    }

    [TearDown]
    public void TearDownTest()
    {
    }

    #endregion

    [Test]
    public void Should_create_and_retrieve_Person()
    {
        var expected = new Person
        {
            FirstName = "Mike",
            LastName = "G"
        };

        using (var session = _factory.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            session.SaveOrUpdate(expected);

            tx.Commit();
        }

        expected.Id.Should().BeGreaterThan(0);

        using (var session = _factory.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            var actual = session.Get<Person>(expected.Id);

            actual.Should().NotBeNull();
            actual.ShouldHave().AllProperties().EqualTo(expected);
        }

    }

    [Test]
    public void Should_create_and_retrieve_Roles()
    {
        // Arrange
        var expected = new Person
                         {
                             FirstName = "Mike",
                             LastName = "G"
                         };

        var expectedManager = new Manager
                           {
                               Division = "One",
                               Status = "Active"
                           };
        var expectedUser = new User
                               {
                                   LoginName = "mikeg",
                                   Password = "test123"
                               };

        Person actual;

        // Act
        expected.AddRole(expectedManager);
        expected.AddRole(expectedUser);

        using (var session = _factory.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            session.SaveOrUpdate(expected);

            tx.Commit();
        }

        using (var session = _factory.OpenSession())
        using (var tx = session.BeginTransaction())
        {
            actual = session.Get<Person>(expected.Id);

            // ignore this; just forcing the Roles collection to be lazy loaded before I kill the session.
            actual.Roles.Count();
        }

        // Assert
        actual.Roles.OfType<Manager>().First().Should().Be(expectedManager);
        actual.Roles.OfType<Manager>().First().ShouldHave().AllProperties().But(c => c.Person).EqualTo(expectedManager);

        actual.Roles.OfType<User>().First().Should().Be(expectedUser);
        actual.Roles.OfType<User>().First().ShouldHave().AllProperties().But(c => c.Person).EqualTo(expectedUser);
    }
}

如果您想将 Person 限制为特定角色的一个实例,只需放置一个唯一索引并使用 Equals 方法来检查是否Id相同 ORRoleName是否相同。

您可以轻松获取或检查任何类型的用户角色:

if (person.Roles.OfType<User>().Any())
{
   var user = person.Roles.OfType<User>().FirstOrDefault();
} 

您还可以直接查询角色以查找其人员:

var peopleWhoAreManagersInDistrictOne = (from role in session.Query<Manager>()
                                         where role.District == "One"
                                         select role.Person);

您还可以看到其他程序集可以定义其他角色。经理与模型在不同的装配中。

所以,你可以看到它会做你想做的一切以及更多,尽管它使用了不同的方法。

于 2012-05-14T17:43:43.987 回答