1

次のクラス階層が与えられます。

クラス図http://img535.imageshack.us/img535/4802/personusermanager.jpg

追加情報:

  • Personクラスは抽象的ではありません。
  • 人は、ユーザー、マネージャー、またはIPersonインターフェースを実装する他の何かである可能性があります。
  • Personクラスは、その子クラスに関する知識を持ってはなりません。
  • 子クラスは別のアセンブリに存在する可能性があります。
  • また、個人がユーザーであり、管理者である可能性もあります。その場合、UserRepositoryは指定されたPersonIdのUserオブジェクトを返す必要があり、ManagerRepositoryは同じPersonIdのManagerを返す必要があります。
  • また、PersonRepositoryを介してIPersonインターフェイスを実装するすべてのオブジェクトのPerson(ベース)部分を取得できる必要があります。

これをNHibernate内でどのようにマッピングできますか?

FluentNHibernate1.2とNHibernate3.1を使用しています。

現在の状況では、各クラスに独自のテーブルがあります。つまり、Personテーブル、Userテーブル、およびManagerテーブルがあります。

私はすでに次のオプションを試しましたが成功しませんでした:

  • これを継承マッピング(サブクラスごとに1つのテーブル)でマッピングします。
  • UserとManagerのマッピングでのPersonとの結合(継承マッピングなし)。
  • ユーザーとマネージャーのマッピングにPersonを含むHasOneマッピング(結合と継承のマッピングなし)。
4

1 に答える 1

2

間違いなく発見したように、継承を次のようにマップするのは簡単です:Person-> User、またはPerson-> Manager、またはPerson-> Manager-> User(または、Person-> Manager-> User)。

NHibernateでは、サブクラスへの昇格/サブクラスからの降格は許可されていません。昇格または降格するには、ネイティブSQLを実行する必要があります。

ただし、継承の最初の「マップ」に従った場合は、実行しようとしていることに対してサブクラスを使用することは、モデル化しようとしていることに対して不適切な解決策であるというエピファニーがあったはずです。そして、それは2つのサブクラスだけです!役割を追加するとどうなりますか?

あなたが持っているのは、役割が拡張可能な、任意の数の役割のメンバーになることができる人です。このソリューションを検討してください(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

時間を節約するために、両方のプロジェクトの流暢なマッピングを1つにまとめました。モデルとModels.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を特定のロールの1つのインスタンスに制限する場合は、一意のインデックスを設定し、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);

また、他のアセンブリが追加の役割を定義できることもわかります。Managerは、モデルとは異なるアセンブリにあります。

したがって、これは別のアプローチを使用しているという事実にもかかわらず、これがあなたが望むすべてに加えてさらに多くのことを行うことがわかります。

于 2012-05-14T17:43:43.987 に答える