Свободный Nhibernate: сопоставление единого объекта с требованиями интерфейса - PullRequest
2 голосов
/ 28 августа 2010

Добрый день.

Прежде чем я начну свое объяснение, я рассмотрел другие подобные вопросы, но тонкие различия (в основном в целях разработки) означают, что решения, представленные в этих ответах, не относятся ко мне.

Я пытаюсь создать «Базовую библиотеку доступа к данным» для использования с будущими проектами, чтобы мне не приходилось тратить время на написание одной и той же логики в нескольких проектах. Намерение состоит в том, чтобы просто импортировать эту библиотеку и мгновенно иметь возможность наследовать от IBaseDao и мгновенно получать стандартные возможности «CRUD» с самого начала.

Потому что (при кодировании этой базовой библиотеки) у меня нет возможности точно знать, какими будут мои бизнес-объекты (Customer, FileStructure, Cat, Dog, Order и т. Д.). Я определил интерфейс в своей базовой библиотеке, который должны выполнять все бизнес-объекты. реализовать, прежде чем они могут быть использованы с этой библиотекой. Такой интерфейс определяется следующим образом:

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', я получаю исключение несоответствия количества параметров. Я подозреваю, что причиной этой проблемы является то, что таблица 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, а затем сохранение в отдельной таблице с именем (если IsMockEntity имеет значение true) MockEntitiy the Идентификатор фиктивного объекта и его читаемое имя.

Я нахожу, что ClassMap довольно сложно понять в данный момент, так как я очень мало сделал с прошлой базовой стойкостью NHibernate. Любая помощь или ссылки на соответствующие материалы очень приветствуется.

Спасибо за ваше время.

Ответы [ 4 ]

0 голосов
/ 17 июня 2011

Создайте абстрактную реализацию 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);
}
0 голосов
/ 03 сентября 2010

Хорошо, я изменил код, чтобы отразить предложенные вами изменения, а также проделал еще несколько «копаний», чтобы попытаться решить эту проблему.Теперь у меня есть следующая структура базы данных:

ТАБЛИЦА Клиент
Идентификатор uniqueidentifier
Имя varchar (50)
День рождения datetime

TABLE Заказ
Id uniqueidentifier
CustomerId uniqueidentifier
Цена float

TABLEMockEntities
Id uniqueidentifier
EntityId uniqueidentifier
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();

    }
}

ClassMap for Order выглядит почти идентично, но для полноты картины я включу его в этот пост.

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: имя столбца недопустимо.[Имя узла (если есть) =, имя столбца = идентификатор клиента]

На всякий случай, если это кому-нибудь пригодится, трассировка стека из графического интерфейса NUnit:

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

И, наконец,SQL, который использует NHibernate:

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.Однако теперь кажется, что он ожидает столбец, который я нигде не определил, а именно «Идентификатор клиента».

Любой вклад с благодарностью, спасибо.

0 голосов
/ 03 сентября 2010
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);

        ...

Этот ответ был бы неполным, если бы я не указал, что числовые идентификаторы почти наверняка лучше, чем что-либо еще, поскольку они приводят к более быстрым запросам, лучшей индексации и являются принятым соглашением. Тем не менее, это должно привести вас в движение.

0 голосов
/ 29 августа 2010

Первое, что я бы сделал, это

  1. Установить список на IList; NHibernate должен иметь возможность внедрять свой собственный тип перечисляемого в объект, а не определенный в C # список.
  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, но я бы сначала изменил эти вещи, а затем посмотрел, что случилось. Если это все еще не работает, сообщите нам о появившемся новом исключении.

...