如何为 2 个共享存储在另一个表中的数据的实体设置 NHibernate 映射

本文关键字:数据 实体 设置 映射 NHibernate 另一个 共享 存储 | 更新日期: 2023-09-27 18:34:01

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

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

附加信息:

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

如何在 NHibernate 中映射这一点?

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

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

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

  • 使用继承映射映射(每个子类一个表(映射它;
  • 在用户和管理器的映射中与人员联接(无继承映射(;
  • 在用户和管理器映射中与人员的 HasOne 映射(没有联接和继承映射(;

如何为 2 个共享存储在另一个表中的数据的实体设置 NHibernate 映射

毫无疑问,您已经发现,像这样映射继承很容易:人员 -> 用户,或人员 -> 经理,或人员 -> 经理 -> 用户(或者,人员 -> 经理 -> 用户(。

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; }
}

项目:模型.B

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

项目:模型.impl

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

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);
                         });
    }
}

项目:模型.测试.性能

[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);
    }
}

如果要将人员限制为特定角色的一个实例,只需放置一个唯一的索引并使用 Equals 方法进行混乱,以检查Id是否相同或RoleName是否相同。

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

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);

您还可以看到其他程序集可以定义其他角色。管理器与"模型"位于不同的程序集中。

因此,您可以看到这将完成您想要的一切以及更多,尽管它使用了不同的方法。