3

在创建我的第一个 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));
        }
    }
4

1 回答 1

2

您的错误消息非常清楚地解释了这一点:

System.Data.SqlClient.SqlException:无法将值 NULL 插入列“FloorPlanName”、表“Orchard.dbo.CommunityWebsiteSolutions_ApartmentPartRecord”;列不允许空值。插入失败。

出现您的问题是因为:

  1. 您在 Record 类中使用可空类型,例如stringint?类型,这意味着您希望允许空值。
  2. 但是,您在数据库迁移中指定要禁止空值。
  3. 当 C# 实例化您的 Record 类时,它使用默认值初始化字段,对于可为空的类型为 null。

您可以执行以下操作之一:

  1. 使您的数据库列可以为空(删除NotNull
  2. 使您的 Record 类使用不可为空的类型(例如,int而不是int?)。请注意,这不是引用类型(例如string.
  3. 通过为类提供构造函数,为 Record 类的字段提供非空默认值。这可以说是不好的做法,因为您将在基类中调用虚拟属性,但在 NHibernate 中似乎没问题。
  4. 通过为您的部分提供一个处理程序,为您的 Record 类的字段提供非空默认值,该OnInitializing处理程序将放置在您的 Handler 类中。

更新

您评论说您希望这些字段由驱动程序类TryUpdateModelEditor函数中填写。这最终确实会发生,但实际发生的事件顺序是这样的(您可以在 的CreatePOST方法中看到这一点Orchard.Core.Contents.Controllers.AdminController):

  1. ContentManager.New()使用内容类型 ID 在内存中创建内容项。此步骤需要OnInitializing在处理程序中定义的内容类型的适当内容部分。
  2. ContentManager.Create()与草稿模式中的内容项目。这一步实际上尝试将项目持久化到数据库一次。
  3. ContentManager.UpdateEditor(). 这是实际调用Editor内容类型的适当驱动程序的调用。
  4. 如果有任何失败,请检查ModelState并回滚事务。

如果您在标记为 的列中有 NULL 值,则第 2 步将失败NotNull,因为此时这些字段具有默认值。对于这些列,您必须在第 2 步之前使用OnInitializing或使用 Record 部分上的构造函数来填充它们。

换句话说,TryUpdateModel在您的驱动程序中,实际上是直接将更改应用到已经Created 并且现在附加到 NHibernate 会话的实体。

于 2013-12-19T20:31:57.010 回答