Сохранение записи детали в базе данных

Работаю над созданием моего первого модуля Orchard, и у меня возникают проблемы с сохранением данных формы обратно в базу данных. У меня все зарегистрировано правильно, насколько я могу судить по множеству образцов, поэтому я должен упустить что-то незначительное.

Я могу отобразить форму «Квартира» в новом меню, проверка работает, но когда я полностью заполняю форму и нажимаю «Сохранить», я получаю:

Ваша квартира создана.

При проверке базы данных записи нет в таблице, и проверка журналов показывает:

2013-12-19 09:15:23,416 [19] NHibernate.Transaction.ITransactionFactory — сбой на этапе подготовки транзакции DTC NHibernate.Exceptions.GenericADOException: не удалось выполнить пакетную команду. [SQL: SQL недоступен] ---> System.Data .SqlClient.SqlException: невозможно вставить значение NULL в столбец "FloorPlanName" таблицы "Orchard.dbo.CommunityWebsiteSolutions_ApartmentPartRecord"; столбец не допускает пустых значений. ВСТАВИТЬ не удается.

Запуск SQL Profiler показывает вставку со всеми столбцами, для которых установлено значение NULL.

Миграции.cs

    SchemaBuilder.CreateTable(typeof(ApartmentPartRecord).Name, table => table
                .ContentPartRecord()
                .Column<string>("FloorPlanName", c => c.WithLength(25).NotNull())
                .Column<string>("FullAddress", c => c.WithLength(256).NotNull()))
                .Column<string>("ShortDescription", c => c.WithLength(150).NotNull())
                .Column("NumberOfBedrooms", DbType.Int32, c => c.NotNull())
                .Column("NumberOfBathrooms", DbType.Int32, c => c.NotNull())
                .Column("SquareFootage", DbType.Int32, c => c.NotNull())
                .Column("WhenAvailable", DbType.DateTime)
                .Column("RentAmount", DbType.Decimal)
                );

     ContentDefinitionManager.AlterPartDefinition(typeof (ApartmentPart).Name, part => part.Attachable());

КвартираЧасть

public class ApartmentPartRecord : ContentPartRecord {
    public virtual string FloorPlanName { get; set; }
    public virtual string ShortDescription { get; set; }
    public virtual string FullAddress { get; set; }
    public virtual int? NumberOfBedrooms { get; set; }
    public virtual int? NumberOfBathrooms { get; set; }
    public virtual int? SquareFootage { get; set; }
    public virtual DateTime? WhenAvailable { get; set; }
    public virtual decimal? RentAmount { get; set; }
}

public class ApartmentPart : ContentPart<ApartmentPartRecord> {
    [Required, StringLength(256)]
    [Display(Name = "Address / Unit Number")]
    public string FullAddress {
        get { return Record.FullAddress; }
        set { Record.FullAddress = value; }
    }

    [Required, StringLength(25)]
    [Display(Name = "Floor Plan")]
    public string FloorPlanName {
        get { return Record.FloorPlanName; }
        set { Record.FloorPlanName = value; }
    }

    [Required, StringLength(150)]
    [Display(Name = "Sales Description")]
    public string ShortDescription {
        get { return Record.ShortDescription; }
        set { Record.ShortDescription = value; }
    }

    [Required]
    [Display(Name = "Bedroom Count")]
    public int? NumberOfBedrooms {
        get { return Record.NumberOfBedrooms; }
        set { Record.NumberOfBedrooms = value; }
    }

    [Required]
    [Display(Name = "Bathroom Count")]
    public int? NumberOfBathrooms {
        get { return Record.NumberOfBathrooms; }
        set { Record.NumberOfBathrooms = value; }
    }

    [Required]
    [Display(Name = "Square Footage")]
    public int? SquareFootage {
        get { return Record.SquareFootage; }
        set { Record.SquareFootage = value; }
    }

    [Display(Name = "First Availability")]
    public DateTime? WhenAvailable {
        get { return Record.WhenAvailable; }
        set { Record.WhenAvailable = value; }
    }

    [Display(Name = "Rent Amount")]
    public decimal? RentAmount {
        get { return Record.RentAmount; }
        set { Record.RentAmount = value; }
    }
}

Водитель

public class ApartmentPartDriver : ContentPartDriver<ApartmentPart>
    {
        protected override string Prefix
        {
            get { return "Apartment"; }
        }

        //GET
        protected override DriverResult Editor(ApartmentPart part, dynamic shapeHelper)
        {
            return ContentShape("Parts_Apartment_Edit", 
                () => shapeHelper.EditorTemplate(
                    TemplateName: "Parts/Apartment", 
                    Model: part, 
                    Prefix: Prefix));
        }

        //POST
        protected override DriverResult Editor(ApartmentPart part, IUpdateModel updater, dynamic shapeHelper)
        {
            updater.TryUpdateModel(part, Prefix, null, null);
            return Editor(part, shapeHelper);
        }
    }

Обработчик

public class ApartmentPartHandler : ContentHandler {
        public ApartmentPartHandler(IRepository<ApartmentPartRecord> repository)
        {
            Filters.Add(StorageFilter.For(repository));
        }
    }

person Larry Ruckman    schedule 19.12.2013    source источник


Ответы (1)


Ваше сообщение об ошибке объясняет это довольно ясно:

System.Data.SqlClient.SqlException: невозможно вставить значение NULL в столбец «FloorPlanName» таблицы «Orchard.dbo.CommunityWebsiteSolutions_ApartmentPartRecord»; столбец не допускает пустых значений. ВСТАВИТЬ не удается.

Ваша проблема возникает, потому что:

  1. Вы используете типы, допускающие значение NULL, такие как типы string и int? в своем классе Record, что означает, что вы хотите разрешить значения NULL.
  2. Тем не менее, вы указываете в своей миграции БД, что хотите запретить нули.
  3. И когда C# создает экземпляр вашего класса Record, он инициализирует поля, используя значение по умолчанию, которое равно null для типов, допускающих значение NULL.

Вы можете сделать одно из следующих действий:

  1. Сделайте столбцы БД обнуляемыми (удалите NotNull)
  2. Заставьте класс Record использовать ненулевые типы (например, int вместо int?). Обратите внимание, что это не вариант для ссылочных типов, таких как string.
  3. Задайте ненулевые значения по умолчанию для полей вашего класса Record, предоставив классу конструктор. Возможно, это плохая практика, поскольку вы будете вызывать виртуальные свойства в базовом классе, но, похоже, это нормально в NHibernate.
  4. Задайте ненулевые значения по умолчанию для полей вашего класса Record, предоставив вашей части обработчик OnInitializing, который будет помещен в ваш класс Handler.

ОБНОВЛЕНИЕ

Вы отметили, что ожидаете заполнения полей функцией TryUpdateModel в функции Editor вашего класса драйвера. В конечном итоге это происходит, но фактическая последовательность событий такова (вы можете увидеть это в методе CreatePOST из Orchard.Core.Contents.Controllers.AdminController):

  1. ContentManager.New() с идентификатором типа контента, чтобы создать элемент контента в памяти. Этот шаг вызывает OnInitializing для соответствующих частей контента для типа контента, которые определены в обработчиках.
  2. ContentManager.Create() с элементом содержимого в режиме черновика. Этот шаг фактически пытается сохранить элемент в базе данных один раз.
  3. ContentManager.UpdateEditor(). Это вызов, который фактически вызывает Editor соответствующего драйвера для типа контента.
  4. Проверьте ModelState и откатите транзакцию, если что-то не удалось.

Шаг 2 завершится ошибкой, если в столбцах с пометкой NotNull есть значения NULL, потому что в этот момент поля имеют значения по умолчанию. Для этих столбцов вы должны заполнить их перед шагом 2 с помощью OnInitializing или с помощью конструктора в части записи.

Другими словами, TryUpdateModel в вашем драйвере фактически применяет изменения непосредственно к объекту, который уже был Created и теперь присоединен к сеансу NHibernate.

person Katsuyuki Ohmuro    schedule 19.12.2013
comment
Может быть, я немного тугодум, но когда форма публикуется и обрабатывается в функции редактора драйверов, updater.TryUpdateModel заполняет часть всеми значениями из POST. Через некоторое время после этого значения становятся NULL при сохранении. Я хотел бы сохранить типы с нулевым значением в классе записи, потому что у нас не всегда будет дата WhenAvailable и RentAmount, и мне не нужны значения в этих полях, если у нас их нет. Я попытаюсь удалить NotNull из определений столбцов, но это кажется неправильным. - person Larry Ruckman; 20.12.2013
comment
Вы не пометили WhenAvailable и RentAmount знаком NotNull, поэтому эти столбцы не вызовут проблем. Но как насчет других столбцов, таких как FloorPlanName и NumberOfBedrooms? Это типы с нулевым значением в вашей записи, так почему вы пометили их как NotNull в столбцах базы данных? - person Katsuyuki Ohmuro; 20.12.2013
comment
Я обновил свой ответ объяснением того, почему ваш подход к использованию TryUpdateModel не работает, а также другим способом предоставления значений по умолчанию для вашей части контента. - person Katsuyuki Ohmuro; 20.12.2013
comment
Спасибо за дополнительные пояснения. Я удалил NotNull() из своих миграций, и это работает. Странно, я не знал, когда он пошел вставлять пользовательскую часть в базу данных, что сначала он выполняет INSERT с идентификатором и остальными столбцами как NULL, а затем выполняет UPDATE с данными, введенными пользователем. - person Larry Ruckman; 21.12.2013