昨天我们一直在做准备工作,最终表单数据需要从数据库里提取,并保存到数据库,今天接着介绍如何做提取、保存和验证。
四、提取并显示信息
在EditForm我们定义一个InfoId属性,用于接收在列表页面打开编辑窗体时传递主键,然后编辑窗体通过主键查询实体,最终填充到映射好的控件上。
/// <summary> /// 获取或设置信息ID,根据此ID查询实体并填充在窗体上。 /// </summary> public string InfoId { get; set; }
在窗体的Load事件中,判断InfoId是否为空,如果不空则查询出实体对象,将数据填充到各控件。
private void EditForm_Load(object sender, EventArgs e) { if (!DesignMode) { LoadInfo(); } } /// <summary> /// 读取实体信息,显示在窗体上。 /// </summary> protected virtual void LoadInfo() { if (EntityType == null || EntityPropertyExtend == null) { Helper.ShowError("没有绑定 EntityType 或 EntityPropertyExtend。"); return; } if (!string.IsNullOrEmpty(InfoId)) { using (var persister = new EntityPersister(EntityType)) { var entity = persister.First(InfoId) as IEntity; if (entity == null) { return; } FillEntityValues(entity); } } } /// <summary> /// 将实体的属性值填充到窗体上。 /// </summary> /// <param name="entity"></param> protected virtual void FillEntityValues(IEntity entity) { foreach (var kvp in EntityPropertyExtend.GetProperties()) { var value = entity.GetValue(kvp.Value); if (value == null || value.IsEmpty) { continue; } var converter = ControlEntityMapHelper.GetMapper(kvp.Key.GetType()); if (converter != null) { converter.SetValue(kvp.Key, value.GetStorageValue()); } } }
五、数据验证并保存
Fireasy.Data.Entity基于System.ComponentModel.DataAnnotations实现了内部的验证机制。在持久化对象Create和Update之前,会调用ValidationUnity对实体对象进行验证。
在窗体上,我们还是使用了ErrorProvider组件来显示验证提示信息。
private void btnSave_Click(object sender, EventArgs e) { SaveData(); } /// <summary> /// 保存表单数据到数据库。 /// </summary> /// <param name="createNew">保存后是否新建信息。</param> /// <returns></returns> protected virtual IEntity SaveData(bool createNew = false) { if (EntityType == null || EntityPropertyExtend == null) { Helper.ShowError("没有绑定 EntityType 或 EntityPropertyExtend。"); return null; } //清理验证错误的提示 errorProvider1.Clear(); try { using (var persister = new EntityPersister(EntityType)) { var entity = (string.IsNullOrEmpty(InfoId) ? persister.NewEntity() : persister.First(InfoId)) as IEntity; if (entity == null) { return null; } ReadEntityValues(entity); var infoId = string.Empty; if (entity.EntityState == EntityState.Attached) { infoId = SetPrimaryProperty(entity); } persister.Save(entity); if (entity.EntityState == EntityState.Attached) { InfoId = infoId; } Helper.ShowInformation("数据保存成功。"); return entity; } } catch (EntityInvalidateException exp) { ShowPropertyInvalidateMessages(exp); } catch (Exception exp) { var log = LoggerFactory.CreateLogger(); if (log != null) { log.Error("[" + EntityType + "]保存数据失败", exp); } Helper.ShowError("数据保存失败。", exp); } return null; }
ReadEntityValues方法用于从窗体控件中读取数据,然后写入到实体对象中。
/// <summary> /// 读取窗口上的控件值,写给实体属性。 /// </summary> /// <param name="entity"></param> protected virtual void ReadEntityValues(IEntity entity) { foreach (var kvp in EntityPropertyExtend.GetProperties()) { var converter = ControlEntityMapHelper.GetMapper(kvp.Key.GetType()); if (converter != null) { var value = converter.GetValue(kvp.Key); var property = PropertyUnity.GetProperty(EntityType, kvp.Value); entity.SetValue(kvp.Value, PropertyValue.New(value, property.Type)); } } }
PropertyValue是Fireasy.Data.Entity中的类,可以参考Fireasy.Data.Entity组件。
SetPrimaryProperty用于手动设置主键的值。
/// <summary> /// 设置主键的值,并返回主键属性。 /// </summary> /// <param name="entity"></param> /// <returns></returns> private string SetPrimaryProperty(IEntity entity) { var keyValue = Guid.NewGuid().ToString("N"); var accessor = entity as IEntityPropertyAccessor; var pk = PropertyUnity.GetPrimaryProperties(EntityType).FirstOrDefault(); accessor.SetValue(pk, keyValue); return keyValue; }
如果实体验证失败,会抛出一个EntityInvalidateException类型的异常信息,该对象包括验证失败的各个属性及对应的错误信息,以及在实体类型上定义的全局验证特性,即不是单一的属性值验证,而是业务逻辑验证。
/// <summary> /// 显示验证失败的信息。 /// </summary> /// <param name="exp"></param> private void ShowPropertyInvalidateMessages(EntityInvalidateException exp) { var sb = new StringBuilder(); foreach (var property in exp.PropertyErrors) { //查找有没有验证失败属性相关联的控件 var map = EntityPropertyExtend.GetProperties().Where(s => s.Value == property.Key.Name).FirstOrDefault(); if (map.Key == null) { sb.AppendLine(string.Join("\n", property.Value.ToArray())); continue; } //在控件上显示验证失败的信息 errorProvider1.SetError(map.Key, string.Join("\n", property.Value.ToArray())); } if (sb.Length > 0) { Helper.ShowError("填写的数据不完整,还包括以下这些信息:\n" + sb.ToString()); } }
六、运行实例
运行程序,查看前面我们定义的ProductEdit窗体。
我们从列表中双击一条数据进行编辑,Book的相关数据自动填充到了控件中。
我们把个别的文本清空,或是输入较长的字符串,这时将保存失败,并在窗体上显示红色的图标。
七、验证类的定义
最后,将Book的验证类贴上来,以便更直观。
//如果要启用实体验证,请使用以下特性,并在 BookMetadata 中定义验证特性。 [MetadataType(typeof(BookMetadata))] public partial class Book { } public class BookMetadata { /// <summary> /// 属性 Id 的验证特性。 /// </summary> [Required] [StringLength(32)] public object Id { get; set; } /// <summary> /// 属性 ISBN 的验证特性。 /// </summary> [StringLength(10)] public object ISBN { get; set; } /// <summary> /// 属性 BarCode 的验证特性。 /// </summary> [Required] [StringLength(13)] public object BarCode { get; set; } /// <summary> /// 属性 PyCode 的验证特性。 /// </summary> [StringLength(20)] public object PyCode { get; set; } /// <summary> /// 属性 Name 的验证特性。 /// </summary> [Required] [StringLength(20)] public object Name { get; set; } /// <summary> /// 属性 Price 的验证特性。 /// </summary> [Required] public object Price { get; set; } /// <summary> /// 属性 Agio 的验证特性。 /// </summary> [Required] public object Agio { get; set; } /// <summary> /// 属性 Unit 的验证特性。 /// </summary> [Required] [StringLength(4)] public object Unit { get; set; } /// <summary> /// 属性 SizeNo 的验证特性。 /// </summary> [StringLength(10)] public object SizeNo { get; set; } /// <summary> /// 属性 MediaNo 的验证特性。 /// </summary> [StringLength(10)] public object MediaNo { get; set; } /// <summary> /// 属性 VolumesOfBar 的验证特性。 /// </summary> public object VolumesOfBar { get; set; } /// <summary> /// 属性 BarsOfBag 的验证特性。 /// </summary> public object BarsOfBag { get; set; } /// <summary> /// 属性 Emphasis 的验证特性。 /// </summary> public object Emphasis { get; set; } /// <summary> /// 属性 TypeCode 的验证特性。 /// </summary> [StringLength(10)] public object TypeCode { get; set; } /// <summary> /// 属性 PublisherId 的验证特性。 /// </summary> [StringLength(32)] public object PublisherId { get; set; } /// <summary> /// 属性 PublishDate 的验证特性。 /// </summary> public object PublishDate { get; set; } /// <summary> /// 属性 UpperLimit 的验证特性。 /// </summary> [Required] public object UpperLimit { get; set; } /// <summary> /// 属性 LowerLimit 的验证特性。 /// </summary> [Required] public object LowerLimit { get; set; } /// <summary> /// 属性 State 的验证特性。 /// </summary> public object State { get; set; } /// <summary> /// 属性 Remark 的验证特性。 /// </summary> [StringLength(200)] public object Remark { get; set; } /// <summary> /// 属性 MarkColor 的验证特性。 /// </summary> [StringLength(6)] public object MarkColor { get; set; } /// <summary> /// 属性 DelFlag 的验证特性。 /// </summary> public object DelFlag { get; set; } }