我的WCF4 Rest Service及Entity Framework with POCO之旅(四)——定制Entity

本文将focus几个结合使用WCF REST和Entity Framework with POCO的常见问题。

Entity Type和Property名称的大小写

按照RESTful的习惯,XML或者JSON格式的数据的node名称开头字母一般使用小写,比如,下面是一段Google Buzz API的RESTful返回信息:

<entry xmlns="http://www.w3.org/2005/Atom" xmlns:activity="http://activitystrea.ms/spec/1.0">

  <id>tag:google.com,2010:buzz:z12puk22ajfyzsz</id>

  <author>

    <name>Ted Taco</name>

    <uri>http://www.google.com/profiles/ted</uri>

  </author>

  <published>2010-04-23T11:03:34.342Z</published>

  <updated>2010-04-23T11:03:34.342Z</updated>

  <activity:object>

    <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>

    <content type="html">Hey, this is my first Buzz Post!</content>

  <activity:object>

  <link rel="alternate" type="text/html" href="http://www.google.com/buzz/ted/Hey-this-is-my-first-Buzz-Post" />

  <!-- more data -->

</entry>

要让我们的Service返回数据node也使用小写字母开头,我们需要给Entity加上[DataContract]和[DataMember] attributes。(当然也可以修改WCF的behavior让它使用我们提供的的serializer但是现在框架既然还能满足需求,我还不想那么做。)
手动加attribute显然不是一个好主意,而且可以想见一旦模型代码重新生成,我们的修改就都没了。我们需要修改生成Entity的T4模板(Model.tt和Model.Context.tt)。
T4模板和其他的一些代码生成器的模板很相似,和现在的ASP.NET也有点相似。主要特点就是在模板内容(对于模板来说类似于纯文本)中嵌入C#代码。
由于T4模板的可读性不是很好,而VS原生又不支持T4的高亮和intellisense,开始编辑之前,最好装一个支持T4高亮的插件。我用的是Visual T4,虽然反应比较慢还有一些bug,不过是免费的,基本也够用了。

我们先看一下现在的Model.tt的全貌:

<#@ template language="C#" debug="false" hostspecific="true"#>

<#@ include file="EF.Utility.CS.ttinclude"#><#@

 output extension=".cs"#><#



CodeGenerationTools code = new CodeGenerationTools(this);

MetadataLoader loader = new MetadataLoader(this);

CodeRegion region = new CodeRegion(this, 1);

MetadataTools ef = new MetadataTools(this);



string inputFile = @"WcfRestServiceDemo.edmx";

EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile);

string namespaceName = code.VsNamespaceSuggestion();



EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this);



// Write out support code to primary template output file

WriteHeader(fileManager);

BeginNamespace(namespaceName, code);

WriteCustomObservableCollection();

EndNamespace(namespaceName);



// Emit Entity Types

foreach (EntityType entity in ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name))

{

    fileManager.StartNewFile(entity.Name + ".cs");

    BeginNamespace(namespaceName, code);

    bool entityHasNullableFKs = entity.NavigationProperties.Any(np => np.GetDependentProperties().Any(p=>ef.IsNullable(p)));

#>

<#=Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#><#=code.StringBefore(" : ", code.Escape(entity.BaseType))#>

{

<#

    region.Begin("Primitive Properties");



    foreach (EdmProperty edmProperty in entity.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == entity))

    {

        bool isForeignKey = entity.NavigationProperties.Any(np=>np.GetDependentProperties().Contains(edmProperty));

        bool isDefaultValueDefinedInModel = (edmProperty.DefaultValue != null);

        bool generateAutomaticProperty = false;



#>



    <#=PropertyVirtualModifier(Accessibility.ForProperty(edmProperty))#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#>

    {

<#

        if (isForeignKey)

        {

#>

        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get { return <#=code.FieldName(edmProperty)#>; }

        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set

        {

<#

            if (entityHasNullableFKs)

            {

#>

            try

            {

                _settingFK = true;

<#

                PushIndent(CodeRegion.GetIndent(1));

            }

            if (((PrimitiveType)edmProperty.TypeUsage.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.Binary)

            {

#>

            if (!StructuralComparisons.StructuralEqualityComparer.Equals(<#=code.FieldName(edmProperty)#>, value))

<#

            }

            else

            {

#>

            if (<#=code.FieldName(edmProperty)#> != value)

<#

            }

#>

            {

<#

            foreach (var np in entity.NavigationProperties.Where(np=>np.GetDependentProperties().Contains(edmProperty)))

            {

                EdmProperty principalProperty = ef.GetCorrespondingPrincipalProperty(np, edmProperty);

                if (((PrimitiveType)principalProperty.TypeUsage.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.Binary)

                {

#>

                if ((<#=code.Escape(np)#> != null) && !StructuralComparisons.StructuralEqualityComparer.Equals(<#=code.Escape(np)#>.<#=code.Escape(principalProperty)#>, value))

<#

                }

                else

                {

#>

                if (<#=code.Escape(np)#> != null && <#=code.Escape(np)#>.<#=code.Escape(principalProperty)#> != value)

<#

                }

#>

                {

<#

                if (!(np.GetDependentProperties().Where(p=>ef.IsNullable(p)).Any() &&

                      np.GetDependentProperties().Count() > 1))

                {

#>

                    <#=code.Escape(np)#> = null;

<#

                }

                else

                {

#>

                    var previousValue = <#=code.FieldName(np)#>;

                    <#=code.FieldName(np)#> = null;

                    Fixup<#=np.Name#>(previousValue, skipKeys: true);

<#

                }

#>

                }

<#

            }

#>

                <#=code.FieldName(edmProperty)#> = value;

            }

<#

            if (entityHasNullableFKs)

            {

                PopIndent();

#>

            }

            finally

            {

                _settingFK = false;

            }

<#

            }

#>

        }

<#

        }

        else if (isDefaultValueDefinedInModel)

        {

#>

        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get { return <#=code.FieldName(edmProperty)#>; }

        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set { <#=code.FieldName(edmProperty)#> = value; }

<#

        }

        else

        {

            generateAutomaticProperty = true;

#>

        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get;

        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set;

<#

        }

#>

    }

<#

        if (!generateAutomaticProperty)

        {

#>

    private <#=code.Escape(edmProperty.TypeUsage)#> <#=code.FieldName(edmProperty)#><#=code.StringBefore(" = ", code.CreateLiteral(edmProperty.DefaultValue))#>;

<#

        }

    }

    region.End();



    region.Begin("Complex Properties");



    foreach(EdmProperty edmProperty in entity.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == entity))

    {

#>



    <#=PropertyVirtualModifier(Accessibility.ForProperty(edmProperty))#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#>

    {

        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get { return <#=code.FieldName(edmProperty)#>; }

        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set { <#=code.FieldName(edmProperty)#> = value; }

    }

    private <#=code.Escape(edmProperty.TypeUsage)#> <#=code.FieldName(edmProperty)#> = new <#=code.Escape(edmProperty.TypeUsage)#>();

<#

    }



    region.End();



    ////////

    //////// Write Navigation properties -------------------------------------------------------------------------------------------

    ////////



    region.Begin("Navigation Properties");



    foreach (NavigationProperty navProperty in entity.NavigationProperties.Where(np => np.DeclaringType == entity))

    {

        NavigationProperty inverse = ef.Inverse(navProperty);

        if (inverse != null &&  !IsReadWriteAccessibleProperty(inverse))

        {

            inverse = null;

        }

#>



<#

        if (navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)

        {

#>

    <#=PropertyVirtualModifier(Accessibility.ForReadOnlyProperty(navProperty))#> ICollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>> <#=code.Escape(navProperty)#>

    {

        get

        {

            if (<#=code.FieldName(navProperty)#> == null)

            {

<#

                if (inverse != null || ((AssociationType)navProperty.RelationshipType).IsForeignKey)

                {

#>

                var newCollection = new FixupCollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>>();

                newCollection.CollectionChanged += Fixup<#=navProperty.Name#>;

                <#=code.FieldName(navProperty)#> = newCollection;

<#

                }

                else

                {

#>

                <#=code.FieldName(navProperty)#> = new FixupCollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>>();

<#

                }

#>

            }

            return <#=code.FieldName(navProperty)#>;

        }

        set

        {

<#

            if (inverse != null || ((AssociationType)navProperty.RelationshipType).IsForeignKey)

            {

#>

            if (!ReferenceEquals(<#=code.FieldName(navProperty)#>, value))

            {

                var previousValue = <#=code.FieldName(navProperty)#> as FixupCollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>>;

                if (previousValue != null)

                {

                    previousValue.CollectionChanged -= Fixup<#=navProperty.Name#>;

                }

                <#=code.FieldName(navProperty)#> = value;

                var newValue = value as FixupCollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>>;

                if (newValue != null)

                {

                    newValue.CollectionChanged += Fixup<#=navProperty.Name#>;

                }

            }

<#

            }

            else

            {

#>

            <#=code.FieldName(navProperty)#> = value;

<#

            }

#>

        }

    }

    private ICollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>> <#=code.FieldName(navProperty)#>;

<#

        }

        else

        {

#>

    <#=PropertyVirtualModifier(Accessibility.ForProperty(navProperty))#> <#=code.Escape(navProperty.ToEndMember.GetEntityType())#> <#=code.Escape(navProperty)#>

    {

<#

            if (inverse != null || ((AssociationType)navProperty.RelationshipType).IsForeignKey)

            {

#>

        <#=code.SpaceAfter(Accessibility.ForGetter(navProperty))#>get { return <#=code.FieldName(navProperty)#>; }

        <#=code.SpaceAfter(Accessibility.ForSetter(navProperty))#>set

        {

            if (!ReferenceEquals(<#=code.FieldName(navProperty)#>, value))

            {

                var previousValue = <#=code.FieldName(navProperty)#>;

                <#=code.FieldName(navProperty)#> = value;

                Fixup<#=navProperty.Name#>(previousValue);

            }

        }

    }

    private <#=code.Escape(navProperty.ToEndMember.GetEntityType())#> <#=code.FieldName(navProperty)#>;

<#

            }

            else

            {

#>

        <#=code.SpaceAfter(Accessibility.ForGetter(navProperty))#>get;

        <#=code.SpaceAfter(Accessibility.ForSetter(navProperty))#>set;

    }

<#

            }

        }

    }

    region.End();



    region.Begin("Association Fixup");



    if (entityHasNullableFKs)

    {

#>



    private bool _settingFK = false;

<#

    }

    foreach (NavigationProperty navProperty in entity.NavigationProperties.Where(np => np.DeclaringType == entity))

    {

        NavigationProperty inverse = ef.Inverse(navProperty);



        if (inverse != null && !IsReadWriteAccessibleProperty(inverse))

        {

            inverse = null;

        }



        if ( (inverse != null || ((AssociationType)navProperty.RelationshipType).IsForeignKey) &&

             (navProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many) )

        {

            var skipKeysArgument = (navProperty.GetDependentProperties().Where(p=>ef.IsNullable(p)).Any() && navProperty.GetDependentProperties().Count() > 1)

                ? ", bool skipKeys = false"

                : String.Empty;

#>



    private void Fixup<#=navProperty.Name#>(<#=code.Escape(navProperty.ToEndMember.GetEntityType())#> previousValue<#= skipKeysArgument #>)

    {

<#

        if (inverse != null)

        {

            if (inverse.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)

            {

#>

        if (previousValue != null && previousValue.<#=code.Escape(inverse)#>.Contains(this))

        {

            previousValue.<#=code.Escape(inverse)#>.Remove(this);

        }

<#

            }

            else

            {

#>

        if (previousValue != null && ReferenceEquals(previousValue.<#=code.Escape(inverse)#>, this))

        {

            previousValue.<#=code.Escape(inverse)#> = null;

        }

<#

            }



            if (inverse.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)

            {

#>



        if (<#=code.Escape(navProperty)#> != null)

        {

            if (!<#=code.Escape(navProperty)#>.<#=code.Escape(inverse)#>.Contains(this))

            {

                <#=code.Escape(navProperty)#>.<#=code.Escape(inverse)#>.Add(this);

            }

<#

                foreach (var dependentProperty in navProperty.GetDependentProperties())

                {

                    EdmProperty principalProperty = ef.GetCorrespondingPrincipalProperty(navProperty, dependentProperty);

                    if (((PrimitiveType)principalProperty.TypeUsage.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.Binary)

                    {

#>

            if (!StructuralComparisons.StructuralEqualityComparer.Equals(<#=code.Escape(dependentProperty)#>, <#=code.Escape(navProperty)#>.<#=code.Escape(principalProperty)#>))

<#

                    }

                    else

                    {

#>

            if (<#=code.Escape(dependentProperty)#> != <#=code.Escape(navProperty)#>.<#=code.Escape(principalProperty)#>)

<#

                    }

#>

            {

                <#=code.Escape(dependentProperty)#> = <#=code.Escape(navProperty)#>.<#=code.Escape(principalProperty)#>;

            }

<#

                }

#>

        }

<#

                if (navProperty.GetDependentProperties().Where(p=>ef.IsNullable(p)).Any())

                {

                    if (navProperty.GetDependentProperties().Count() > 1)

                    {

#>

        else if (!_settingFK && !skipKeys)

<#

                    }

                    else

                    {

#>

        else if (!_settingFK)

<#

                    }

#>

        {

<#

                    foreach (var dependentProperty in navProperty.GetDependentProperties().Where(p => ef.IsNullable(p)))

                    {

#>

            <#=code.Escape(dependentProperty)#> = null;

<#

                    }

#>

        }

<#

                }

            }

            else

            {

#>



        if (<#=code.Escape(navProperty)#> != null)

        {

            <#=code.Escape(navProperty)#>.<#=code.Escape(inverse)#> = this;

<#

                foreach (var dependentProperty in navProperty.GetDependentProperties())

                {

                    EdmProperty principalProperty = ef.GetCorrespondingPrincipalProperty(navProperty, dependentProperty);

                    if (((PrimitiveType)principalProperty.TypeUsage.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.Binary)

                    {

#>

            if (!StructuralComparisons.StructuralEqualityComparer.Equals(<#=code.Escape(dependentProperty)#>, <#=code.Escape(navProperty)#>.<#=code.Escape(principalProperty)#>))

<#

                    }

                    else

                    {

#>

            if (<#=code.Escape(dependentProperty)#> != <#=code.Escape(navProperty)#>.<#=code.Escape(principalProperty)#>)

<#

                    }

#>

            {

                <#=code.Escape(dependentProperty)#> = <#=code.Escape(navProperty)#>.<#=code.Escape(principalProperty)#>;

            }

<#

                }

#>

        }

<#

            }

        }

        else

        {

            if (navProperty.GetDependentProperties().Any())

            {

#>

        if (<#=code.Escape(navProperty)#> != null)

        {

<#

                foreach (var dependentProperty in navProperty.GetDependentProperties())

                {

                    EdmProperty principalProperty = ef.GetCorrespondingPrincipalProperty(navProperty, dependentProperty);

                    if (((PrimitiveType)principalProperty.TypeUsage.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.Binary)

                    {

#>

            if (!StructuralComparisons.StructuralEqualityComparer.Equals(<#=code.Escape(dependentProperty)#>, <#=code.Escape(navProperty)#>.<#=code.Escape(principalProperty)#>))

<#

                    }

                    else

                    {

#>

            if (<#=code.Escape(dependentProperty)#> != <#=code.Escape(navProperty)#>.<#=code.Escape(principalProperty)#>)

<#

                    }

#>

            {

                <#=code.Escape(dependentProperty)#> = <#=code.Escape(navProperty)#>.<#=code.Escape(principalProperty)#>;

            }

<#

                }

#>

        }

<#

                if (navProperty.GetDependentProperties().Where(p => ef.IsNullable(p)).Any())

                {

                    if (navProperty.GetDependentProperties().Count() > 1)

                    {

#>

        else if (!_settingFK && !skipKeys)

<#

                    }

                    else

                    {

#>

        else if (!_settingFK)

<#

                    }

#>

        {

<#

                    foreach (var dependentProperty in navProperty.GetDependentProperties().Where(p => ef.IsNullable(p)))

                    {

#>

            <#=code.Escape(dependentProperty)#> = null;

<#

                    }

#>

        }

<#

                }

            }

            else if (((AssociationType)navProperty.RelationshipType).IsForeignKey)

            {

#>

        if (<#=code.Escape(navProperty)#> != null)

        {

<#

                foreach (var fromProperty in ef.GetPrincipalProperties(navProperty))

                {

#>

            <#=code.Escape(navProperty)#>.<#=code.Escape(ef.GetCorrespondingDependentProperty(navProperty, fromProperty))#> = <#=code.Escape(fromProperty)#>;

<#

                }

#>

        }

<#

            }

        }

#>

    }

<#

        }

    }



    foreach (NavigationProperty navProperty in entity.NavigationProperties.Where(np => np.DeclaringType == entity))

    {

        NavigationProperty inverse = ef.Inverse(navProperty);



        if (inverse != null && !IsReadWriteAccessibleProperty(inverse))

        {

            inverse = null;

        }



        if ( (inverse != null || ((AssociationType)navProperty.RelationshipType).IsForeignKey) &&

             (navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many) )

        {

#>



    private void Fixup<#=navProperty.Name#>(object sender, NotifyCollectionChangedEventArgs e)

    {

        if (e.NewItems != null)

        {

            foreach (<#=code.Escape(navProperty.ToEndMember.GetEntityType())#> item in e.NewItems)

            {

<#

                if (inverse != null)

                {

                    if (inverse.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many)

                    {

#>

                item.<#=code.Escape(inverse)#> = this;

<#

                    }

                    else

                    {

#>

                if (!item.<#=code.Escape(inverse)#>.Contains(this))

                {

                    item.<#=code.Escape(inverse)#>.Add(this);

                }

<#

                    }

                }

                else if (((AssociationType)navProperty.RelationshipType).IsForeignKey)

                {

                    foreach (var fromProperty in ef.GetPrincipalProperties(navProperty))

                    {

#>

                item.<#=code.Escape(ef.GetCorrespondingDependentProperty(navProperty, fromProperty))#> = <#=code.Escape(fromProperty)#>;

<#

                    }

                }

#>

            }

        }



        if (e.OldItems != null)

        {

            foreach (<#=code.Escape(navProperty.ToEndMember.GetEntityType())#> item in e.OldItems)

            {

<#

                if (inverse != null)

                {

                    if (inverse.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many)

                    {

#>

                if (ReferenceEquals(item.<#=code.Escape(inverse)#>, this))

                {

                    item.<#=code.Escape(inverse)#> = null;

                }

<#

                    }

                    else

                    {

#>

                if (item.<#=code.Escape(inverse)#>.Contains(this))

                {

                    item.<#=code.Escape(inverse)#>.Remove(this);

                }

<#

                    }

                }

                else if (((AssociationType)navProperty.RelationshipType).IsForeignKey)

                {

                    foreach (var fromProperty in ef.GetPrincipalProperties(navProperty))

                    {

                        var p = ef.GetCorrespondingDependentProperty(navProperty, fromProperty);

                        if (ef.IsNullable(p.TypeUsage))

                        {

#>

                item.<#=code.Escape(p)#> = null;

<#

                        }

                    }

                }

;#>

            }

        }

    }

<#

        }

    }



    region.End();

#>

}

<#

    EndNamespace(namespaceName);

}



foreach (ComplexType complex in ItemCollection.GetItems<ComplexType>().OrderBy(e => e.Name))

{

    fileManager.StartNewFile(complex.Name + ".cs");

    BeginNamespace(namespaceName, code);

#>

<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>

{

<#

    region.Begin("Primitive Properties");



    foreach(EdmProperty edmProperty in complex.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == complex))

    {

        bool isDefaultValueDefinedInModel = (edmProperty.DefaultValue != null);

#>



    <#=Accessibility.ForProperty(edmProperty)#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#>

<#

        if (isDefaultValueDefinedInModel)

        {

#>

    {

        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get { return <#=code.FieldName(edmProperty)#>; }

        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set { <#=code.FieldName(edmProperty)#> = value; }

    }

    private <#=code.Escape(edmProperty.TypeUsage)#> <#=code.FieldName(edmProperty)#><#=code.StringBefore(" = ", code.CreateLiteral(edmProperty.DefaultValue))#>;

<#

        }

        else

        {

#>

    {

        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get;

        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set;

    }

<#

        }

    }



    region.End();



    region.Begin("Complex Properties");



    foreach(EdmProperty edmProperty in complex.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == complex))

    {

#>



    <#=Accessibility.ForProperty(edmProperty)#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#>

    {

        <#=code.SpaceAfter(Accessibility.ForGetter(edmProperty))#>get { return <#=code.FieldName(edmProperty)#>; }

        <#=code.SpaceAfter(Accessibility.ForSetter(edmProperty))#>set { <#=code.FieldName(edmProperty)#> = value; }

    }

    private <#=code.Escape(edmProperty.TypeUsage)#> <#=code.FieldName(edmProperty)#> = new <#=code.Escape(edmProperty.TypeUsage)#>();

<#

    }



    region.End();

#>

}

<#

    EndNamespace(namespaceName);

}



if (!VerifyTypesAreCaseInsensitiveUnique(ItemCollection))

{

    return "";

}



fileManager.Process();



#>

<#+

void WriteHeader(EntityFrameworkTemplateFileManager fileManager, params string[] extraUsings)

{

    fileManager.StartHeader();

#>

//------------------------------------------------------------------------------

// <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>

//------------------------------------------------------------------------------



using System;

using System.Collections;

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.Collections.Specialized;

<#=String.Join(String.Empty, extraUsings.Select(u => "using " + u + ";" + Environment.NewLine).ToArray())#>

<#+

    fileManager.EndBlock();

}



void BeginNamespace(string namespaceName, CodeGenerationTools code)

{

    CodeRegion region = new CodeRegion(this);

    if (!String.IsNullOrEmpty(namespaceName))

    {

#>

namespace <#=code.EscapeNamespace(namespaceName)#>

{

<#+

        PushIndent(CodeRegion.GetIndent(1));

    }

}





void EndNamespace(string namespaceName)

{

    if (!String.IsNullOrEmpty(namespaceName))

    {

        PopIndent();

#>

}

<#+

    }

}



bool IsReadWriteAccessibleProperty(EdmMember member)

{

    string setter = Accessibility.ForWriteOnlyProperty(member);

    string getter = Accessibility.ForReadOnlyProperty(member);



    return getter != "private" && getter != "protected" && setter != "private" && setter != "protected";

}



string PropertyVirtualModifier(string accessibility)

{

    return accessibility + (accessibility != "private" ? " virtual" : "");

}



void WriteCustomObservableCollection()

{

#>

// An System.Collections.ObjectModel.ObservableCollection that raises

// individual item removal notifications on clear and prevents adding duplicates.

public class FixupCollection<T> : ObservableCollection<T>

{

    protected override void ClearItems()

    {

        new List<T>(this).ForEach(t => Remove(t));

    }



    protected override void InsertItem(int index, T item)

    {

        if (!this.Contains(item))

        {

            base.InsertItem(index, item);

        }

    }

}

<#+

}



bool VerifyTypesAreCaseInsensitiveUnique(EdmItemCollection itemCollection)

{

    Dictionary<string, bool> alreadySeen = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);

    foreach(StructuralType type in itemCollection.GetItems<StructuralType>())

    {

        if (!(type is EntityType || type is ComplexType))

        {

            continue;

        }



        if (alreadySeen.ContainsKey(type.FullName))

        {

            Error(String.Format(CultureInfo.CurrentCulture, "This template does not support types that differ only by case, the types {0} are not supported", type.FullName));

            return false;

        }

        else

        {

            alreadySeen.Add(type.FullName, true);

        }



    }



    return true;

}

#>

看起来很长?其实主要内容并不复杂。我们所要关心的的Entity生成代码都在一个遍历所有entity的foreach中,对于每个entity,它依次枚举Primitive Properties - entity本身定义的基本类型字段,Complex Properties - 用户定义的复杂类型字段,以及Navigation Properties - entity的关系字段。

首先找到生成每个类定义的代码:

<#=Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#><#=code.StringBefore(" : ", code.Escape(entity.BaseType))#>

在上面加一行:

[DataContract(Name = "<#=char.ToLowerInvariant(entity.Name[0]) + entity.Name.Substring(1) #>")]

然后再找到生成Primitive Property的代码(其他的类似):

<#=PropertyVirtualModifier(Accessibility.ForProperty(edmProperty))#> <#=code.Escape(edmProperty.TypeUsage)#> <#=code.Escape(edmProperty)#>

在上面加一行:

[DataMember(Name = "<#=char.ToLowerInvariant(edmProperty.Name[0]) + edmProperty.Name.Substring(1) #>")]

由于DataContract在System.Runtime.Serialization命名空间下,我们还需要添加命名空间。在接近Model.tt末尾的地方找到有一堆命名空间定义,在末尾加上:

using System.Runtime.Serialization;

然后Save,这时代码会重新生成,我们再看一下这时的Microblog.cs:

//------------------------------------------------------------------------------

// <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>

//------------------------------------------------------------------------------



using System;

using System.Collections;

using System.Collections.Generic;

using System.Collections.ObjectModel;

using System.Collections.Specialized;

using System.Runtime.Serialization;



namespace WcfRestServiceDemo.Data

{

    [DataContract(Name = "microblog")]

    public partial class Microblog

    {

        #region Primitive Properties

        [DataMember(Name = "id")]

        public virtual int Id

        {

            get;

            set;

        }

        [DataMember(Name = "content")]

        public virtual string Content

        {

            get;

            set;

        }

        [DataMember(Name = "publishTime")]

        public virtual System.DateTime PublishTime

        {

            get;

            set;

        }



        #endregion

    }

}

这时如果测试一下服务,就可以看到不论是XML还是JSON的返回结果中的字段都已经变成小写字母开头了。

需要注意的是,不仅是返回,这时客户端发送请求时,对应字段也必须同样改成小写字母开头。


处理Null值属性

为了引入这个问题,我们将服务稍稍变得复杂一些。假设现在并且每条微博可以关联一张图片,用户只要在写微博同时,输入图片地址即可。那么修改Microblog模型,添加Picture属性,包含一个图片的URL。由于图片是可选的,所以还要将的Nullable设为true。

如下:

imageimage

由于修改了EntityModel,需要在两个.tt模板文件上右键选择Run Custom Tools来生成新的POCO entity.

然后我们看一下服务的返回:

<ArrayOfmicroblog xmlns="http://schemas.datacontract.org/2004/07/WcfRestServiceDemo.Data" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

  <microblog>

    <content>Microblog with no picture.</content>

    <id>1</id>

    <picture i:nil="true"/>

    <publishTime>2011-04-19T22:00:00</publishTime>

  </microblog>

  <microblog>

    <content>Microblog with picture</content>

    <id>2</id>

    <picture>http://somepicture/somepicture</picture>

    <publishTime>2011-04-19T22:00:00</publishTime>

  </microblog>

</ArrayOfmicroblog>

这里有两条microblog,一条有picture,一条没有。但是没有picture的那个也把picture字段给返回了,只是用i:nil=”true”来标识这是个null值。显然这不是理想方式,想象一下一个entity可能会有很多可空的属性,尤其是当一个entity与许多其他entity产生关联,引入了许多navigation properties时,传输这些空属性将严重影响可读性(虽然大多数情况下应该不是人来读)并对性能造成影响(传输、序列化和反序列化)。(当然这不是绝对的,nil值有其应用场景,请根据实际情况决定

幸好DataMember attribute还有一个EmitDefaultValue属性,可以指定是否要序列化默认值。用和刚才类似的方法,修改T4模板,修改Property定义上方的[DataMember]:

    [DataMember(Name = "<#=char.ToLowerInvariant(edmProperty.Name[0]) + edmProperty.Name.Substring(1) #>"<#

if(edmProperty.Nullable)

{

	#>, EmitDefaultValue = false<#

}

#>)]
首先判断了property是否可设为null,若可以才省略空值。否则对于一个int属性,值为0时它就不会被传输了,这一般是不太合适的。(确切的讲,这里的Nullable和数据类型没有关系,指的是在数据模型中的nullable,也就是是否为可空字段。)

再次访问服务:

<ArrayOfmicroblog xmlns="http://schemas.datacontract.org/2004/07/WcfRestServiceDemo.Data" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

  <microblog>

    <content>Microblog with no picture.</content>

    <id>1</id>

    <publishTime>2011-04-19T22:00:00</publishTime>

  </microblog>

  <microblog>

    <content>Microblog with picture</content>

    <id>2</id>

    <picture>http://somepicture/somepicture</picture>

    <publishTime>2011-04-19T22:00:00</publishTime>

  </microblog>

</ArrayOfmicroblog>

可以看到没有图片的那个microblog的picture属性已经被省略了。


JSON中的DateTime

在第二章末尾我们提到过DateTime属性在JSON表示下的奇怪格式。严格来说,这不算什么问题,因为既然微软选择了这种格式,那么全世界的人都在碰到这个问题,相应的客户端解决方案也有很多。比如:

如果一定要从服务端解决这个问题的话,最根本的方法是改用自定义的序列化器。而比较简单的方法是为entity写一个partial类,然后提供一个long格式的对应属性:

using System;

using System.Runtime.Serialization;



namespace WcfRestServiceDemo.Data

{

    partial class Microblog

    {

        private static readonly DateTime _baseTime =

            new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToLocalTime();



        [DataMember(Name = "publishTimeSec")]

        public long PublishTimeSeconds

        {

            get { return (long)(PublishTime - _baseTime).TotalMilliseconds; }

            set { PublishTime = _baseTime.AddMilliseconds(value); }

        }

    }

}

这时服务的返回就是:

[

 {

  "content":"Microblog with no picture.",

  "id":1,

  "publishTime":"\/Date(1303221600000+0800)\/",

  "publishTimeSec":1303221600000

 },

 {

  "content":"Microblog with picture",

  "id":2,

  "picture":"http:\/\/somepicture\/somepicture",

  "publishTime":"\/Date(1303221600000+0800)\/",

  "publishTimeSec":1303221600000

 }

]

当然更绝的做法是把这个替代属性的生成也交给T4模板来做。具体做法请参考我整理好后上传的代码


小结

本文介绍了:

  • 如何修改T4模板控制模型代码的生成,包括修改Type名称、属性名称的起始字母大小写和是否要序列化默认值的选项
  • 如何用替代属性解决JSON中DateTime格式的问题。

我的WCF4 REST Service及Entity Framework with POCO之旅系列

你可能感兴趣的:(framework)