65.9K
CodeProject 正在变化。 阅读更多。
Home

DataObjectBase

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.66/5 (20投票s)

2010 年 4 月 28 日

CPOL

8分钟阅读

viewsIcon

39455

downloadIcon

608

DataObjectBase -> 数据对象的新 Object 类!

目录

  1. 引言
  2. 背景
  3. Using the Code
  4. 关注点
  5. 历史

引言

每个开发人员都面临的一个问题是数据持久化。大多数编写的软件都需要能够将对象保存到磁盘,或将其(二进制或 XML)序列化以用于 .NET 远程处理或 Web 服务。 .NET Framework 支持许多接口来实现此目的

但是,当数据对象的属性更改时,您也希望收到通知,现在您正在编写这类软件,并决定同时实现数据验证。需要以下 .NET 接口

发生的情况是,您需要编写大量(冗余)代码来支持所有数据类的所有这些接口。那时您还没有考虑过二进制序列化对象的版本控制,这些对象确实很难处理。此外,在(反)序列化对象时,您会发现自己编写了大量实际上执行相同操作且难以维护的自定义(重复)代码。

如何声明类

该类需要特殊声明。声明看起来代码量很大,但包中包含一个代码片段“dataobject”,可用于轻松创建新对象。

/// <summary>
/// MyFirstObject Data object class which fully supports serialization, 
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary>
[Serializable]
public class MyFirstObject : DataObjectBase
{
    /// <summary>
    /// Initializes a new object from scratch.
    /// </summary>
    public MyFirstObject()
        : base() { }

    /// <summary>
    /// Initializes a new object based on .
    /// </summary>
    ///  that contains the information.
    /// .
    public MyFirstObject(SerializationInfo info, StreamingContext context)
        : base(info, context) { }
} 

如何声明属性

没有属性的数据类就像没有轮子的自行车。您可以坐在上面,但永远无法骑行。因此,声明属性非常重要。但是,由于类使用特殊的(反)序列化属性,因此需要特殊声明。

要声明属性,请使用包含的代码片段“propdata”。

/// <summary>
/// Gets or sets the value of MyFirstProperty.
/// </summary>
public string MyFirstProperty
{
	get { return GetValue(MyFirstPropertyProperty); }
	set { SetValue(MyFirstPropertyProperty, value); }
}

/// <summary>
/// Register the property so it is known in the class.
/// </summary>
public readonly PropertyData MyFirstPropertyProperty = 
	RegisterProperty("MyFirstProperty", typeof(string), string.Empty); 

如您所见,为属性创建了一个特殊包装器,它使用基类的 GetValueSetValue 方法。属性定义中最重要部分是对 RegisterProperty 的调用。第一个参数是属性的名称,第二个参数是属性的类型,第三个参数是默认值,以防属性尚不可用或无法反序列化。

背景

DataObjectBase 类是一个泛型基类,可用于所有数据类。

  1. 完全可序列化
    现在将对象轻松存储在磁盘上或将其序列化为内存(二进制或 XML)非常容易。数据对象开箱即支持此功能,并自动处理(反)序列化。
  2. 支持属性更改通知
    该类支持 INotifyPropertyChanged 通知,因此该类可以轻松地用于 WPF 和 MVC 应用程序,以便向用户显示错误消息。
  3. 向后兼容性
    以二进制方式序列化对象时,很难维护正确的版本。当您向二进制类添加新属性或更改命名空间时,将无法再加载对象。数据对象基类负责解决此问题并支持向后兼容性。
  4. 错误检查
    该类实现了 IDataErrorInfo,因此可以验证数据对象并检查错误。这样,就不需要在数据类外部编写自定义验证代码。
  5. 备份和恢复
    该类实现了 IEditableObject 接口,这使得创建对象状态成为可能。然后可以编辑所有属性,最后可以应用或取消更改。

为了支持所有这些开箱即用的功能,该类实现了以下接口

接口 原因
ISerializable 要求使对象可序列化。
IXmlSerializable 要求使用自定义 XML 序列化器使对象可序列化。
INotifyPropertyChanging 要求通知其他类对象内部即将发生的属性更改。
INotifyPropertyChanged 要求通知其他类对象内部发生的属性更改。
IDataErrorInfo 要求通知其他类对象中的数据错误。在 WPF 中显示错误非常有用,可以自动在用户界面中显示错误。
IDataWarningInfo 自定义接口,类似于 IDataErrorInfo,以支持数据对象中的警告。
IClonable 要求支持数据对象的克隆。
IComparer 要求将对象与其它对象进行比较。
IEditableObject 要求在数据对象中创建状态。因此,在用户开始编辑对象之前,可以创建一个状态。然后,可以确认或取消更改。

Using the Code

下面列出的所有示例都作为代码包含在本文章附带的可下载包中。大多数示例都有实际应用示例,可以启动以显示其确实有效。 :)

示例 01 – 简单对象

此示例演示如何创建可用作“普通”CLR 对象的类。

/// <summary>
/// SimpleObject Data object class which fully supports serialization, 
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary>
[Serializable]
public class SimpleObject : DataObjectBase
{
    #region Variables
    #endregion

    #region Constructor & destructor
    /// <summary>
    /// Initializes a new object from scratch.
    /// </summary>
    public SimpleObject()
        : base() { }

    /// <summary>
    /// Initializes a new object based on .
    /// </summary>
    ///  that contains the information.
    /// .
    public SimpleObject(SerializationInfo info, StreamingContext context)
        : base(info, context) { }
    #endregion

    #region Properties
    /// </summary>
    /// Gets or sets the simple property.
    /// </summary>
    public string SimpleProperty
    {
        get { return GetValue(SimplePropertyProperty); }
        set { SetValue(SimplePropertyProperty, value); }
    }

    /// <summary>
    /// Register the property so it is known in the class.
    /// </summary>
    public readonly PropertyData SimplePropertyProperty = 
	RegisterProperty("SimpleProperty", typeof(string), string.Empty);
    #endregion

    #region Methods
    #endregion
}

示例 02 – 错误验证

此示例演示如何在对象中实现验证。验证将阻止存储无效对象(无效对象无法序列化)。此示例显示了一个使用 WPF 的对象,该对象可以实时向最终用户显示对象中的当前错误。

对象具有 2 种验证结果。可能存在阻止对象序列化的错误,也可能存在仅为警告的警告,因此对象仍可序列化。

要启用验证,您必须至少重写以下方法之一

/// <summary>
/// Validates the fields.
/// </summary>
protected override void ValidateFields()
{
    // Validate for warnings
    if (string.IsNullOrEmpty(Recommended)) SetFieldWarning(RecommendedProperty, 
	"The recommended property is missing!");

    // Validate for errors
    if (string.IsNullOrEmpty(Required)) SetFieldError
	(RequiredProperty, "The required property is missing!");
}

/// <summary>
/// Validates the business rules.
/// </summary>
protected override void ValidateBusinessRules()
{
    // Check for fields
    if (string.IsNullOrEmpty(Recommended) && string.IsNullOrEmpty(Required))
    {
        SetBusinessRuleError("A combination of field data is not valid, 
	make sure to enter at least one field!");
    }
}

WPF 中的验证将产生以下输出

示例 03 – 序列化

此示例演示如何使用 SaveLoad 方法将对象序列化/反序列化到/从磁盘。对象本身也可以直接序列化/反序列化到内存,但这在示例中未显示。

要保存对象,请调用对象本身的 Save 方法。

myObject.Save("MyFile.dob", SerializationMode.Binary);

要加载对象,请调用 static Load 方法来加载类。

SerializableObject myObject = SerializableObject.Load
				("MyFile.dob", SerializationMode.Binary);

示例 04 – 嵌套对象

该类完全能够嵌套。这意味着您可以使用基于 DataObjectBase 的对象,其中可以包含也基于 DataObjectBase 的其他类。这使得创建父/子对象成为可能,而整个树都是完全可序列化的。这里没有特别有趣的内容,如果需要更多关于此主题的信息,请查看示例。

示例 05 – 向后兼容性

此示例演示如何将“旧的”(标准 .NET)数据类(使用自定义二进制序列化)轻松转换为 DataObjectBase ,以便甚至为所有现有类使用 DataObjectBase

声明一个新的 DataObjectBase 类(记住“dataobject”代码片段)。如果新类位于新程序集中,或者具有新名称或命名空间,请使用 RedirectType 属性让 DataObjectBase 知道当它找到旧类型时,它应该将该类型反序列化为新类型。

然后,默认情况下,DataObjectBase 类将尝试反序列化旧对象。如果失败,它将回退到属性声明提供的默认值。但是,也可以重写 GetDataFromSerializationInfo 方法。

/// <summary>
/// Retrieves the actual data from the serialization info.
/// </summary>
/// .
/// <remarks>
/// This method should only be implemented if backwards 
/// compatibility should be implemented for
/// a class that did not previously implement the DataObjectBase class.
/// </remarks>
protected override void GetDataFromSerializationInfo(SerializationInfo info)
{
    // Check if deserialization succeeded
    if (DeserializationSucceeded) return;

    // Deserialization did not succeed for any reason, so retrieve the values manually
    // Luckily there is a helper class (SerializationHelper) 
    // that eases the deserialization of "old" style objects
    FirstName = SerializationHelper.GetString(info, 
		"FirstName", FirstNameProperty.GetDefaultValue());
    LastName = SerializationHelper.GetString(info, 
		"LastName", LastNameProperty.GetDefaultValue());
}

示例 06 - 恢复和应用

此示例演示如何为对象创建状态。当对象处于编辑模式时,这非常有用。在显示窗口之前,会创建一个新状态。然后,当用户单击“确定”按钮时,新状态将被应用。当用户单击“取消”按钮时,将通过恢复到窗口打开时创建的状态来清除更改。

要创建状态,请使用以下代码:

myObject.BeginEdit(); 

要应用自上次创建状态以来对对象所做的所有更改,请使用以下代码:

myObject.EndEdit();

要恢复自上次创建状态以来对对象所做的所有更改,请使用以下代码:

myObject.CancelEdit(); 

关注点

这一切在后台是如何工作的?好吧,这个想法非常基础和简单,它的实现方式并非因为对所有功能和接口的支持。DataObjectBase 类包含一个已注册属性的字典,以及属性的当前值。然后,可以使用 GetValueSetValue 方法在字典中获取或设置值。

最大的问题(实际上,这是一个挑战,而不是问题)是自动反序列化所有数据,特别是在程序集版本号不同或类型重命名的情况下。在反序列化二进制对象时,.NET Framework 非常非常严格,即使类或程序集发生最轻微的更改,也会完全破坏用户数据的反序列化。

DataObjectClass 通过 2 种方式解决了这个问题。首先,它检查 RedirectType 属性,以查看是否应将任何类型重定向到新类型。然后,它初始化自定义 SerializationBinder 来重定向所有类型。这解决了类型重命名问题,也可以处理类型的版本控制。

但是,仍然存在一些问题。在反序列化时,您永远不知道何时您的对象(及其子对象)已完全反序列化。为了解决这个问题,还实现了 OnDeserialized 属性以及 IDeserializationCallback 接口。仅使用其中一个无法解决问题,但同时使用两者可以解决问题。

历史

2010/05/18 - 2.1.0

  • IComparer<T> 接口添加了实现。
  • IXmlSerializable 接口添加了实现。
  • INotifyPropertyChaning 接口添加了实现。
  • 添加了接口(IDataObjectBase IDataObjectBase<T>)以支持模拟并将非泛型对象传递给方法。
  • 添加了 Bytes KeyName 属性。
  • 添加了 OnCreating OnCreated 方法。
  • AlwaysNotifyOnPropertyChanged 现在在 Initialize 方法中设置,因此在反序列化对象时也会将其设置为默认值。
  • 类现在必须实现空构造函数(XML 序列化需要)。
  • 在将集合作为属性写入时,不再将 XML 命名空间写入 XML 文件。
  • 将二进制格式化程序的创建移至单个方法。
  • 改进了单元测试,以检查使用 XML 进行序列化时默认值是否正确替换。
  • 修复了由于 IsInitialized 的静态状态导致派生类的属性未正确注册的 bug。
  • 修复了非公共属性无法正确(反)序列化的 bug。

2010/04/29 - 2.0.0

  • 第一个稳定版本,包含实际示例。
© . All rights reserved.