使用Fluent NHibernate进行映射时对接口进行编程

时间:2009-05-10 15:32:27

标签: c# nhibernate fluent-nhibernate nhibernate-mapping

我已被提交并已开始学习Fluent NHibernate(之前没有NHibernate经验)。在我的项目中,我正在编程接口以减少耦合等。这意味着几乎所有“一切”都指的是接口而不是具体类型(IMessage而不是Message)。这背后的想法是通过模拟依赖关系来帮助使其更易于测试。

但是,(流利的)当我尝试映射到接口而不是具体类时,NHibernate不喜欢它。问题很简单 - 根据Fluent Wiki,定义我的类的ID字段是明智的

int Id { get; private set; }

获取典型的自动生成主键。但是,这仅适用于具体类 - 我无法在接口上指定访问级别,同一行必须是

int Id { get; set; }

我认为否定在具体类中将setter设为私有(这个想法是只有NHibernate应该将ID设置为由DB分配)。

就目前而言,我想我只会公开制定者,并试图避免写入它的诱惑..但是,是否有人知道什么是“适当的”,最佳实践方式来创造一个适当的只有NHibernate可以写入的主键字段,但仍然只编程到接口?

已更新

根据我在mookid和James Gregory的两个答案后的理解,我可能走错了路 - 我不应该有理由让每个实体拥有一个接口,就像我现在一样。这一切都很好。我想我的问题就变成了 - 是否没有理由对任何实体的接口进行100%编程?如果甚至只有一种情况可以证明这是合理的,那么可以用(流利的)NHibernate做到这一点吗?

我问,因为我不知道,不要批评。谢谢你的回复。 :)

5 个答案:

答案 0 :(得分:12)

我意识到这是一种转移,而不是你问题的答案(尽管我认为mookid已经涵盖了这一点)。

您应该真正评估您的域实体上的接口是否实际上提供了任何有价值的东西;很难找到你真正需要这样做的情况。

例如:如果IMessage依赖于Message而不依赖于{{1}},那么它们(几乎)无疑会共享相同的签名时,如何依赖它?你不应该嘲笑一个实体,因为它很少有足够的行为要求被嘲笑。

答案 1 :(得分:10)

您可以调整界面以仅包含getter:

public interface ISomeEntity
{
    int Id { get; }
}

您的具体类仍然可以实现一个setter,并且由于您正在编程接口,因此您不会“偶然”调用setter。

如果你想要禁止设置id,即使你持有对具体实例的引用,你也可以避免实现setter,然后让NHibernate访问字段而不是属性 - 这是正确的,NHibernate可以使用一些漂亮的东西反射技巧直接设置你的id字段而不是调用属性。然后你可以像这样映射id:

Id(e => e.Id).Access.AsCamelCaseField();

在这种情况下,您的Id属性必须由相应的id字段支持。有更多的命名约定,例如如果您更喜欢下划线作为私有字段前缀。

答案 2 :(得分:10)

我有完全相同的问题。 不幸的是,我有一个使用实体接口的正当理由;实体模型将以不同的方式实现,并为每个客户提供不同的映射。

整个模型需要是只读的,因此接口的风格为:

public interface IAccount
{
    long AccountId { get; }
    IHouse House { get; }
}

public interface IHouse
{
    long HouseId { get; }
    HouseStatus Status { get; }
    IList<IAccount> Accounts { get; }
}

具体实现然后使用内部setter实现这些:

public class Account: IAccount
{
    public virtual long AccountId { get; internal set; }
    public virtual IHouse House { get; internal set; }
}

public class House: IHouse
{
    public virtual long HouseId { get; internal set; }
    public virtual HouseStatus Status { get; internal set; }
    public virtual IList<IAccount> Accounts { get; internal set; }
}

我已经走上了映射到具体类的路线。在创建返回接口并需要转换为具体实现的关系之前,一切都很好。

HasMany(x => x.Accounts)

可以成为

HasMany<Account>(x => x.Accounts)

没有等效的'cast'
References(x => x.House)

映射到接口(整个解决方案)会引发上面提到的问题,因为Id必须存在于最顶层的类中才能进行设置,并且需要在接口上设置setter。

public sealed class AccountMap : ClassMap<IAccount>
{
    public PokerPlayerMap()
    {
        Id(x => x.AccountId, "account_id");

        DiscriminateSubClassesOnColumn("Type").SubClass<Account>(s =>  
        {
            References(x => x.House);       
        });
    }
}

目前,我唯一的解决方案是将setter添加到所有接口Id字段。遗憾的是,Id不能存在于子类中,或者从接口中强制转换类型。

答案 3 :(得分:8)

使用union-subclass的

UPDATE:不支持fluent-nhibernate提供的fluent接口。您必须使用常规的hbm映射文件并添加它。

我也是用流利的NHibernate尝试这样做的。我不认为映射接口应该是一个问题。您希望使用继承策略,特别是table-per-concrete-class strategy

基本上,您为基类创建了一个映射定义(在本例中是您的接口),并指定NHibernate如何使用union-subclass处理实现者。

因此,例如,这应该允许您进行多态关联:

<class name="IAccountManager"
                abstract="true"
                table="IAccountManager">

        <id name="Id">
                <generator class="hilo"/>
        </id>

        <union-subclass
                table="DefaultAccountManager"
                name="DefaultAccountManager">
                <property name="FirstName"/>
        </union-subclass>

        <union-subclass
                table="AnotherAccountManagerImplementation"
                name="AnotherAccountManagerImplementation">
                <property name="FirstName"/>
        </union-subclass>
        ...
</class> 

注意所有具体实施者的Id是如何相同的。 NHibernate需要这个。此外,IAccountManager表实际上并不存在。

你也可以尝试利用NHibernate的隐式多态性(在每个混凝土类表的策略下面记录) - 但它有很多限制。

答案 4 :(得分:4)

看起来我没有足够的声誉来评论其他人的答案,因此我将不得不自己做出这个答案。

引用现在有一个泛型重载,允许Gecko在他的答案中寻找的演员。