Fluent Nhibernate:将单个实体与接口需求映射

发布于 2024-09-16 16:38:43 字数 2893 浏览 3 评论 0原文

下午好。

在开始解释之前,我已经查看了其他类似的问题,但细微的差异(主要是设计目的)意味着这些答案中提供的解决方案不适用于我。

我正在尝试创建一个“基础数据访问库”以供未来的项目使用,这样我就不必花时间在多个项目中编写相同的逻辑。目的是简单地导入这个库,并立即具有从 IBaseDao 继承的能力,并从一开始就立即具有标准的“CRUD”功能。

因为(在编码这个基础库时)我无法确切地知道我的业务对象是什么(客户、文件结构、猫、狗、订单等),我在我的基础库中定义了一个接口,所有业务对象都必须先实现该接口可以与该库一起使用。这样的接口定义如下:

public interface IEntity
{
    /// Indicates weather this entity is used as test
    /// data or if it is a real-world entity.
    bool IsMockObject { get; set; }

    /// This is not intended for use in a 'real' application
    /// and is only used in testing.
    string ReadableName { get; set; }

    /// The unique identifier for this object.
    Guid Id { get; set; }
}

因此,现在在“真实”应用程序中,我打算拥有类似于以下内容的内容:

public class Customer : IEntity
{
    public Customer()
    {

    }

    string name = "";
    public virtual String Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    private DateTime birthday = DateTime.Now;
    public virtual DateTime Birthday
    {
        get;
        set;
    }

    private List<Order> orders = new List<Order>();
    public virtual List<Order> Orders
    {
        get
        {
            return orders;
        }
        set
        {
            orders = value;
        }
    }

    #region IEntity Members

    private bool isMockObject;
    public virtual bool IsMockObject
    {
        get
        {
            return true;
        }
        set
        {
            isMockObject = value;
        }
    }

    private string readableName;
    public virtual string ReadableName
    {
        get
        {
            return readableName;
        }
        set
        {
            readableName = value;
        }
    }

    private Guid id;
    public virtual Guid Id
    {
        get
        {
            return id;
        }
        set
        {
            id = value;
        }
    }
    #endregion
}

现在,这就是问题出现的地方。在我的单元测试期间,当我尝试保存“客户”时,我收到参数计数不匹配异常。我怀疑此问题的原因是我的数据库中的 Customer 表不包含所有 IEntity 属性的列。但它确实包含一个 Id 列。到目前为止,我的 CustomerMap 看起来像这样:

public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Table("Customer");

        // From IEntity
        Id(x => x.Id);

        Map(x => x.Name);
        Map(x => x.Birthday);

        // From IEntity
        Map(x => x.IsMockObject);

        // From IEntity
        Map(x => x.ReadableName);
        HasMany(x => x.Orders);
    }
}

我真正希望 NHibernate 在保存时执行的操作是保存在 Customer 表中,以及 Customer 特有的所有属性以及 IEntity.Id 属性然后将模拟对象的 Id 及其可读名称保存在一个名为(如果 IsMockEntity 为 true)MockEntitiy 的单独表中。

我发现 ClassMap 目前很难理解,因为我对 NHibernate 过去的基本持久性做了很少的工作。任何帮助或相关材料的链接非常表示赞赏。

感谢您抽出时间。

Good afternoon.

Before I begin my explanation, I have had a look at other similar questions but the subtle differences (mainly in purpose of design) mean that the solutions provided in these answers to not apply to me.

I am attempting to create a 'Base data access library' for use with future projects so that I do not have to spend my time coding the same logic across multiple projects. The intention is to simply import this library and instantly have the ability to inherit from a IBaseDao and instantly have standard 'CRUD' abilities from the get-go.

Because (when coding this base library) I have no way of knowing exactly what my business objects will be (Customer, FileStructure, Cat, Dog, Order ect) I have defined an interface within my base library which all business objects must implement before they can be used with this library. Such an interface is defined as follows:

public interface IEntity
{
    /// Indicates weather this entity is used as test
    /// data or if it is a real-world entity.
    bool IsMockObject { get; set; }

    /// This is not intended for use in a 'real' application
    /// and is only used in testing.
    string ReadableName { get; set; }

    /// The unique identifier for this object.
    Guid Id { get; set; }
}

So now in the 'real' application I intend to have something similar to the following:

public class Customer : IEntity
{
    public Customer()
    {

    }

    string name = "";
    public virtual String Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }

    private DateTime birthday = DateTime.Now;
    public virtual DateTime Birthday
    {
        get;
        set;
    }

    private List<Order> orders = new List<Order>();
    public virtual List<Order> Orders
    {
        get
        {
            return orders;
        }
        set
        {
            orders = value;
        }
    }

    #region IEntity Members

    private bool isMockObject;
    public virtual bool IsMockObject
    {
        get
        {
            return true;
        }
        set
        {
            isMockObject = value;
        }
    }

    private string readableName;
    public virtual string ReadableName
    {
        get
        {
            return readableName;
        }
        set
        {
            readableName = value;
        }
    }

    private Guid id;
    public virtual Guid Id
    {
        get
        {
            return id;
        }
        set
        {
            id = value;
        }
    }
    #endregion
}

Now here is where the issue arises. During my unit tests, when i try and save a 'Customer' I get a parameter count mismatch exception. I suspect the cause of this issue is that the Customer table within my database does not contain columns for all the IEntity properties. It does however contain an Id column. So far, my CustomerMap looks like this:

public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Table("Customer");

        // From IEntity
        Id(x => x.Id);

        Map(x => x.Name);
        Map(x => x.Birthday);

        // From IEntity
        Map(x => x.IsMockObject);

        // From IEntity
        Map(x => x.ReadableName);
        HasMany(x => x.Orders);
    }
}

What I actually want NHibernate to do when saving is save in the Customer table, all the properties unique to Customer as well as the IEntity.Id property then save in a seperate table called (if IsMockEntity is true) MockEntitiy the Id of the mock object and its readable name.

I find ClassMap's pretty difficult to understand at the moment as I have done very little with NHibernate past basic persistence. Any help or links to relevant material greatly appreciated.

Thank you for your time.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

猫腻 2024-09-23 16:38:43

我要做的第一件事是

  1. 将列表设置为 IList; NHibernate 需要能够将自己的可枚举类型注入到对象中,而不是 C# 定义的 List。
  2. 确保 NHibernate 能够读取和写入您的所有属性,即不要在映射中设置默认值。如果您需要这样做,您可以在构造函数(或其他地方)中执行此操作。

_

public class Customer
{
    public Customer()
    {
        IsMockObject = true;
        Orders = new List<Order>();
    }

    public virtual Guid Id { get; set; }
    public virtual string Name { get; set; }
    public virtual DateTime Birthday { get; set; }  

    // Protected: the user can't modify this!
    public virtual bool IsMockObject { get; protected set; }

    public virtual string ReadableName { get; set; }

    // Needs to be an IList<T>, NOT a regular List<T>.
    public virtual IList<Order> Orders { get; set; }
}

_

我认为您的 ClassMap 没有任何问题,但我会先更改这些内容,然后看看发生了什么。如果仍然不起作用,请通知我们抛出的新异常。

The first thing I would do is

  1. Set the List to an IList; NHibernate needs to be able to inject its own type of enumerable to the object, not C#'s defined List.
  2. Make sure NHibernate is able to read and write to all of your properties, i.e. do not set default values in the mappings. If you need that, you could do it in the constructor (or somewhere else).

_

public class Customer
{
    public Customer()
    {
        IsMockObject = true;
        Orders = new List<Order>();
    }

    public virtual Guid Id { get; set; }
    public virtual string Name { get; set; }
    public virtual DateTime Birthday { get; set; }  

    // Protected: the user can't modify this!
    public virtual bool IsMockObject { get; protected set; }

    public virtual string ReadableName { get; set; }

    // Needs to be an IList<T>, NOT a regular List<T>.
    public virtual IList<Order> Orders { get; set; }
}

_

I don't think there's any problems with your ClassMap, but I'd change these things first and then see what's up. If it still doesn't work, do notify us of the new exception thrown.

独享拥抱 2024-09-23 16:38:43

好的,我已经修改了代码以反映您建议的更改,并且还进行了更多“挖掘”以尝试解决此问题。我现在有以下数据库结构:

TABLE Customer
Id 唯一标识符
名称 varchar(50)
生日日期

时间表顺序
Id 唯一标识符
客户 ID 唯一标识符
价格 float

TABLE MockEntities
Id 唯一标识符
EntityId 唯一标识符
ReadableName nvarchar(MAX)

以及以下 ClassMap。根据我从其他来源收集到的信息,我在这里试图实现的是某种连接。至于天气,它是外部的还是内部的……我不确定。

 public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Table("Customer");

        Id(x => x.Id);
        Map(x => x.Name);
        Map(x => x.Birthday);
        Join("MockEntities", me =>
            {
                Id<Guid>("Id");
                me.Map(xx => xx.ReadableName);
                me.Map(xx => xx.Id, "EntityId");
            });
        HasMany(x => x.Orders).Cascade.All().Not.LazyLoad();

    }
}

Order 的 ClassMap 看起来几乎相同,但为了完整起见,我将把它包含在这篇文章中。

public class OrderMap : ClassMap<Order>
{
    public OrderMap()
    {
        Table("Order");
        Id(x => x.Id);
        Map(x => x.Price);
        Map(x => x.CustomerId, "CustomerId");
        Join("MockEntities", me =>
            {
                Id<Guid>("Id");
                me.Map(xx => xx.ReadableName);
                me.Map(xx => xx.Id, "EntityId");
            });
    }
}

但是,现在,当我运行测试时,出现以下错误:

`System.Data.SqlServerCeException:列名无效。 [ 节点名称(如果有) = ,列名称 = 客户 id ]

以防万一它对任何人都有用,来自 NUnit GUI 的堆栈跟踪:

at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()
at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteNonQuery()
at NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd)
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Boolean[] notNull, Int32 j, SqlCommandInfo sql, Object obj, ISessionImplementor session)
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Object obj, ISessionImplementor session)
at NHibernate.Action.EntityInsertAction.Execute()
at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)
at NHibernate.Engine.ActionQueue.ExecuteActions()
at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at NHibernate.Transaction.AdoTransaction.Commit()
at MySolution.DataAccess.ConnectionProviders.NHibernateConnection.Create[TEntity](TEntity objectToCreate) in MySolution.DataAccess\ConnectionProviders\NHibernateConnection.cs:line 154
at MySolution.Testing.DataAccess.Connection.Testing_Connections.Function_NHibernateCreateNestedEntity()

最后,NHibernate 正在使用的 SQL:

NHibernate: INSERT INTO Customer (Name, Birthday, Id) VALUES (@p0, @p1, @p2);@p0 = 'David', @p1 = 03/09/2010 07:28:22, @p2 = 68fb7dd7-5379-430f-aa59-9de6007b2d56
NHibernate: INSERT INTO MockEntities (ReadableName, EntityId, Customer_id) VALUES (@p0, @p1, @p2);@p0 = 'Function_NHibernateCreateNestedEntity1', @p1 = 00000000-0000-0000-0000-000000000000, @p2 = 68fb7dd7-5379-430f-aa59-9de6007b2d56
07:28:28,851 ERROR [TestRunnerThread] AbstractBatcher [(null)]- Could not execute command: INSERT INTO MockEntities (ReadableName, EntityId, Customer_id) VALUES (@p0, @p1, @p2)
The column name is not valid. [ Node name (if any) = ,Column name = Customer_id ]

恐怕我有点超出了我的深度当谈到这个问题时。在我尝试让映射加入工作之前,系统很好地保存了 Customer 对象及其包含的 Order 集合。然而现在,它似乎需要一个我没有在任何地方定义的列,即“客户 ID”。

任何意见都非常感谢,谢谢。

Ok I have modified the code to reflect your suggested changes and have also done some more 'digging' to try and solve this issue. I now have the following database structure:

TABLE Customer
Id uniqueidentifier
Name varchar(50)
Birthday datetime

TABLE Order
Id uniqueidentifier
CustomerId uniqueidentifier
Price float

TABLE MockEntities
Id uniqueidentifier
EntityId uniqueidentifier
ReadableName nvarchar(MAX)

and thus, the following ClassMap's. From what I have managed to gather from other sources, what I am trying to achieve here is a join of some kind. As to weathers it is an outer or inner...im not sure.

 public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Table("Customer");

        Id(x => x.Id);
        Map(x => x.Name);
        Map(x => x.Birthday);
        Join("MockEntities", me =>
            {
                Id<Guid>("Id");
                me.Map(xx => xx.ReadableName);
                me.Map(xx => xx.Id, "EntityId");
            });
        HasMany(x => x.Orders).Cascade.All().Not.LazyLoad();

    }
}

The ClassMap for Order looks almost identical but for the sake of completeness I shall include it in this post.

public class OrderMap : ClassMap<Order>
{
    public OrderMap()
    {
        Table("Order");
        Id(x => x.Id);
        Map(x => x.Price);
        Map(x => x.CustomerId, "CustomerId");
        Join("MockEntities", me =>
            {
                Id<Guid>("Id");
                me.Map(xx => xx.ReadableName);
                me.Map(xx => xx.Id, "EntityId");
            });
    }
}

However, now, when I run my tests I am given the following error:

`System.Data.SqlServerCeException: The column name is not valid. [ Node name (if any) = ,Column name = Customer id ]

Just in case it is useful to anyone, a stack trace from the NUnit GUI:

at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()
at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options)
at System.Data.SqlServerCe.SqlCeCommand.ExecuteNonQuery()
at NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd)
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Boolean[] notNull, Int32 j, SqlCommandInfo sql, Object obj, ISessionImplementor session)
at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Object obj, ISessionImplementor session)
at NHibernate.Action.EntityInsertAction.Execute()
at NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
at NHibernate.Engine.ActionQueue.ExecuteActions(IList list)
at NHibernate.Engine.ActionQueue.ExecuteActions()
at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event)
at NHibernate.Impl.SessionImpl.Flush()
at NHibernate.Transaction.AdoTransaction.Commit()
at MySolution.DataAccess.ConnectionProviders.NHibernateConnection.Create[TEntity](TEntity objectToCreate) in MySolution.DataAccess\ConnectionProviders\NHibernateConnection.cs:line 154
at MySolution.Testing.DataAccess.Connection.Testing_Connections.Function_NHibernateCreateNestedEntity()

And finally, the SQL that NHibernate is using:

NHibernate: INSERT INTO Customer (Name, Birthday, Id) VALUES (@p0, @p1, @p2);@p0 = 'David', @p1 = 03/09/2010 07:28:22, @p2 = 68fb7dd7-5379-430f-aa59-9de6007b2d56
NHibernate: INSERT INTO MockEntities (ReadableName, EntityId, Customer_id) VALUES (@p0, @p1, @p2);@p0 = 'Function_NHibernateCreateNestedEntity1', @p1 = 00000000-0000-0000-0000-000000000000, @p2 = 68fb7dd7-5379-430f-aa59-9de6007b2d56
07:28:28,851 ERROR [TestRunnerThread] AbstractBatcher [(null)]- Could not execute command: INSERT INTO MockEntities (ReadableName, EntityId, Customer_id) VALUES (@p0, @p1, @p2)
The column name is not valid. [ Node name (if any) = ,Column name = Customer_id ]

Im afraid im somewhat out of my depth when it comes to this issue. Previous to me trying to get this joining in the mappings working, the system was saving a Customer object and its contained Order collection very well. Now however, it seems as if it is expecting a column I have not defined anywhere, namely "Customer id".

Any input greatly appreciated, thank you.

清引 2024-09-23 16:38:43
public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Table("Customer");

        Id(x => x.Id);

        ...

此时,您正在定义正在爆炸的 Customer.Id 字段,因此您需要检查一些事项。

首先,您需要确保它存在。 (在你的情况下,我认为可能是这样)

其次,看看你的SQL,我们有一个问题......

@p2 = 68fb7dd7-5379-430f-aa59-9de6007b2d56

看起来这是一个字符串,但它没有用单引号括起来。

@p2 = '68fb7dd7-5379-430f-aa59-9de6007b2d56'

这可能是因为 NHibernate 错误地认为我们正在处理一个整数,我认为你可以通过像这样调整你的 CustomerMap 来解决这个问题。

public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Table("Customer");

        Id<Guid>(x => x.Id);

        ...

如果我没有指出数字 id 几乎肯定比其他任何东西都好,那么这个答案就不完整,因为它们会导致更快的查询、更好的索引,并且是一种可接受的约定。然而,这应该会让你的事情再次进展。

public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Table("Customer");

        Id(x => x.Id);

        ...

At this point, you are defining the Customer.Id field that is blowing up, so you need to check a few things.

Firstly, you need to make sure it exists. (In your case I think it probably does)

Secondly, looking at your SQL we have a problem...

@p2 = 68fb7dd7-5379-430f-aa59-9de6007b2d56

It looks like this is a string, but it isn't wrapped in single quotes.

@p2 = '68fb7dd7-5379-430f-aa59-9de6007b2d56'

This is probably because NHibernate is under the mistaken impression that we are dealing with an integer, which I think you can fix by adjusting your CustomerMap like this.

public class CustomerMap : ClassMap<Customer>
{
    public CustomerMap()
    {
        Table("Customer");

        Id<Guid>(x => x.Id);

        ...

This answer wouldn't be complete if I didn't point out that numeric ids are almost certainly better than anything else, as they result in faster queries, better indexing and are an accepted convention. However, this should get things moving again for you.

仅此而已 2024-09-23 16:38:43

创建 IEntityBase (EntityBase) 的抽象实现,然后让 Customer 继承 EntityBase(因此实现 IEntityBase )。然后为 EntityBase 创建一个映射,并为 Customer 创建一个子类映射。然后,您应该有一个 EntityBase 表和一个 Customer 表,其中的 EntityBase_Id 列与 EntityBase 表相关。您会发现 EntityBase 需要另一个属性(以便可以通过插入新行并设置另一个属性在数据库中生成 Id),因此我通常添加一个 Created 时间戳或类似的属性。

public interface IEntityBase
{
    int Id { get; set; }
    DateTime? Created { get; set; }
}

public abstract class EntityBase : IEntityBase
{
    public int Id { get; set; }
    public DateTime? Created { get; set ;}
}

public class Customer : EntityBase
{
    public string Name { get; set; }
}

public class EntityBaseMap : ClassMap<EntityBase>
{
    Id(x => x.Id);
    Map(x => x.Created);
}

public class CustomerMap : SubclassMap<Customer>
{
    Map(x => x.Name);
}

Create an abstract implementation of IEntityBase (EntityBase) then have Customer inherit EntityBase (and therefore implement IEntityBase). Then create a map for EntityBase and a subclass map for Customer. You should then have an EntityBase table and a Customer table with a column EntityBase_Id that relates to the EntityBase table. You will find that EntityBase will need another property (so that the Id can be generated in the database by inserting a new row and setting the another property) so I usually add a Created timestamp or similar.

public interface IEntityBase
{
    int Id { get; set; }
    DateTime? Created { get; set; }
}

public abstract class EntityBase : IEntityBase
{
    public int Id { get; set; }
    public DateTime? Created { get; set ;}
}

public class Customer : EntityBase
{
    public string Name { get; set; }
}

public class EntityBaseMap : ClassMap<EntityBase>
{
    Id(x => x.Id);
    Map(x => x.Created);
}

public class CustomerMap : SubclassMap<Customer>
{
    Map(x => x.Name);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文