使用DataContractSerializer和MetaDataTypeAttribute问题自动生成POCO序列化

时间:2011-06-08 08:52:10

标签: .net entity-framework xml-serialization datacontractserializer

正如标题所说,我在序列化自动生成的POCO对象时遇到了一些问题。但首先是一些背景信息:

我已按照本指南使用EF 4.0和ADO.Net POCO实体生成器创建了数据访问层:http://blogs.msdn.com/b/adonet/archive/2010/01/25/walkthrough-poco-template-for-the-entity-framework.aspx

我现在有2个类库,一个使用EF模型,第二个使用T4自动生成的POCO实体。

目前我正在开展另一个项目,我想使用我的DAL类库。我必须检索一些对象并将它们序列化为XML。首先我尝试了XmlSerializer,但后来我发现它有圆周参考的问题。我使用XmlIgnore修复了这个问题,但后来我遇到序列化问题:

Public Overridable Property NwlGroup As ICollection(Of NwlGroup) 

因为XmlSerializer不支持接口。

其次,我在自动生成的实体Poco类文件中尝试了DataContractSerializer和[DataContract]和[DataMember]属性。这工作,但自然我不得不清理自动生成的文件中的更改,因此我想使用MetaDataType属性。我创建了这样的额外文件:

Imports System.Runtime.Serialization
Imports System.ComponentModel.DataAnnotations

<MetadataType(GetType(NewsletterCustomerMetadata))>
Partial Public Class NewsletterCustomer
End Class

<DataContract()
Public Class NewsletterCustomerMetadata

    <DataMember(Name:="emailaddress", IsRequired:=True)>
    Public Overridable Property Emailaddress As String

    <DataMember(Name:="name")>
    Public Overridable Property Name As String

    <DataMember()>
    Public Overridable Property NwlGroup As ICollection(Of NwlGroup)
End Class

自动生成的文件:

'------------------------------------------------------------------------------
' <auto-generated>
'     This code was generated from a template.
'
'     Changes to this file may cause incorrect behavior and will be lost if
'     the code is regenerated.
' </auto-generated>
'------------------------------------------------------------------------------
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized
Imports System.Runtime.Serialization



Public Class NewsletterCustomer
#Region "Primitive Properties"

    Public Overridable Property ID As Integer

    Public Overridable Property Emailaddress As String

    Public Overridable Property Name As String

...

#Region "Navigation Properties"
    Public Overridable Property NwlGroup As ICollection(Of NwlGroup)
        Get
            If _nwlGroup Is Nothing Then
                Dim newCollection As New FixupCollection(Of NwlGroup)
                AddHandler newCollection.CollectionChanged, AddressOf FixupNwlGroup
                _nwlGroup = newCollection
            End If
            Return _nwlGroup
        End Get
        Set(ByVal value As ICollection(Of NwlGroup))
            If _nwlGroup IsNot value Then
                Dim previousValue As FixupCollection(Of NwlGroup) = TryCast(_nwlGroup, FixupCollection(Of NwlGroup))
                If previousValue IsNot Nothing Then
                    RemoveHandler previousValue.CollectionChanged, AddressOf FixupNwlGroup
                End If
                _nwlGroup = value
                Dim newValue As FixupCollection(Of NwlGroup) = TryCast(value, FixupCollection(Of NwlGroup))
                If newValue IsNot Nothing Then
                    AddHandler newValue.CollectionChanged, AddressOf FixupNwlGroup
                End If
            End If
        End Set
    End Property
    Private _nwlGroup As ICollection(Of NwlGroup)

...
End Class

然后我尝试将其序列化为xml

    Dim ctx = New ModelEntities(_connectionString)
       ctx.ContextOptions.ProxyCreationEnabled = False
       ctx.ContextOptions.LazyLoadingEnabled = False

    Dim customers = From c In ctx.NwlCustomer
                    Select c
                   Where c.SiID = 99

    Dim filename As String = "C:\test.txt"
    Dim result As NewsletterCustomer = customers.ToList.FirstOrDefault
    Dim writer As New FileStream(filename, FileMode.Create)
    Dim ser As New DataContractSerializer(GetType(NewsletterCustomer))
    ser.WriteObject(writer, customers.ToList.FirstOrDefault)
    writer.Close()

这给了我NewsletterCustomer xml,其中所有的读/写属性都是序列化的,就像没有指定DataContract时那样。如果我将DataContract属性从NewsletterCustomerMetadata移动到NewsletterCustomer,那么我只会在没有DataMember属性的情况下指定DataContract时获得根节点。

看起来DataContractSerializer不适用于MetaDataType数据注释。

我的问题是:

  1. 如何将POCO类序列化为CUSTOM XML?
  2. 如何将[DataContract]和[DataMember]属性添加到自动生成的POCO类中?
  3. 将自动生成的POCO类序列化为XML的最佳方法是什么?

3 个答案:

答案 0 :(得分:2)

  

首先我尝试了XmlSerializer,但后来我发现它有圆周参考的问题。

嗯,是的:xml是树格式 - 它不会喜欢循环引用。 DataContractSerializer不允许任何喜欢对xml具有相同级别的控制权,所以我的建议是:在这种情况下坚持XmlSerializer,然后 删除您的循环引用 (通常在父属性上有少量[XmlIgnore]。)

否则:实施IXmlSerializable,但请注意,这几乎不提供任何元数据,这是一个非常痛苦的做法。

答案 1 :(得分:1)

DataContractSerializer不读取外部元数据类型的属性。并非.NET框架的每个功能都与API的其余部分一起使用,特别是较新的功能通常不适用于旧的功能,这正是这种情况。

最好的方法是使用IXmlSerializable中的自定义序列化或@Marc建议的DTO。如果是DataContractSerializer,您还可以使用IDataContractSurrogateXmlSerializer的非常高级的方案是overriding XML serialization

也可以让T4模板为您生成属性,但这是非常先进的技术,因为它需要两个步骤:

  • 手动修改EDMX文件(作为XML)并将结构注释添加到CSDL部分(定义实体的部分)。结构注释是自定义XML元素。反向过程Example of using structural annotation(控制SQL生成)。
  • 修改T4模板以加载自定义结构注释并在类生成中使用它们。

答案 2 :(得分:1)

您的问题已得到解答,但我在此处添加了我的解决方法,以帮助那些高度依赖XML序列化的人。当然,循环引用并不适合XML,但令人惊讶的是,这完全让人们不再使用XML。

我使用Code First并从EDMX生成POCO类。我自定义T4模板,使每个实体类都有ToXmlElement和FromXmlElement。

这些功能有能力:

  • 完全弃掉循环引用。
  • 包含预定义数量的循环依赖关系。

这是代码。不要担心晦涩的名字。图示的重点是,这是所有生成的代码,它处理本机类型,复杂类型,外键和子对象(多端)。最后,它不会与EF对你的POCO类强加的FixUpCollection冲突。

public System.Xml.XmlElement ToXmlElement (System.Xml.XmlDocument document)
{
    return (this.ToXmlElement(document, 3, new System.Collections.Generic.List<object>()));
}

public System.Xml.XmlElement ToXmlElement (System.Xml.XmlDocument document, int level)
{
    return (this.ToXmlElement(document, level, new System.Collections.Generic.List<object>()));
}

public System.Xml.XmlElement ToXmlElement (System.Xml.XmlDocument document, int level, System.Collections.Generic.List<object> collection)
{
    System.Xml.XmlElement element = null;

    collection.Add(this);

    element = document.CreateElement(System.Data.Objects.ObjectContext.GetObjectType(this.GetType()).Name);

    // Native Types.
    element.Attributes.Append(document, "Id", this.Id.ToString());
    element.Attributes.Append(document, "Assessment_StudentId", this.Assessment_StudentId.ToString());

    // Complex Types.

    // Foreign Keys.
    if (!collection.Contains(this.Assessment_Student) && level > 0) { element.AppendChild(this.Assessment_Student.ToXmlElement(document, level - 1, collection)); }
    if (!collection.Contains(this.PackageServer) && level > 0) { element.AppendChild(this.PackageServer.ToXmlElement(document, level - 1, collection)); }
    if (!collection.Contains(this.PackageClient) && level > 0) { element.AppendChild(this.PackageClient.ToXmlElement(document, level - 1, collection)); }

    // Child Objects.
    foreach (Core.SessionTasks _SessionTasks in this.SessionTasks)
    {
        if (!collection.Contains(_SessionTasks) && level > 0)
        {
            collection.Add(_SessionTasks);
            element.AppendChild(_SessionTasks.ToXmlElement(document, level - 1, collection));
        }
    }

    return (element);
}

public bool FromXmlElement (System.Xml.XmlElement element)
{
    bool result = true;

    //this.InitializeData();

    // Native Types.
    this.Id = int.Parse(element.Attributes ["Id"].Value);
    this.Assessment_StudentId = int.Parse(element.Attributes ["Assessment_StudentId"].Value);

    // Complex Types.

    // Foreign Keys.
    Core.Assessment_Student __Assessment_Student = new Core.Assessment_Student();
    if (element ["Assessment_Student"] != null)
    {
        __Assessment_Student.FromXmlElement(element ["Assessment_Student"]);
        this.Assessment_Student = __Assessment_Student;
    }

    Core.PackageServer __PackageServer = new Core.PackageServer();
    if (element ["PackageServer"] != null)
    {
        __PackageServer.FromXmlElement(element ["PackageServer"]);
        this.PackageServer = __PackageServer;
    }

    Core.PackageClient __PackageClient = new Core.PackageClient();
    if (element ["PackageClient"] != null)
    {
        __PackageClient.FromXmlElement(element ["PackageClient"]);
        this.PackageClient = __PackageClient;
    }

    // Child Objects.
    this.SessionTasks.FromXmlElement(element ["SessionTasks"]);

    return (result);
}