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

来源:互联网 发布:adobe id是什么软件 编辑:程序博客网 时间:2024/06/05 20:56

http://www.cnblogs.com/Gildor/archive/2011/04/20/2022013.html

我的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 fileWriteHeader(fileManager);BeginNamespace(namespaceName, code);WriteCustomObservableCollection();EndNamespace(namespaceName);// Emit Entity Typesforeach (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表示下的奇怪格式。严格来说,这不算什么问题,因为既然微软选择了这种格式,那么全世界的人都在碰到这个问题,相应的客户端解决方案也有很多。比如:

  • http://archive.cnblogs.com/a/1444633/
  • http://stackoverflow.com/questions/206384/how-to-format-json-date

如果一定要从服务端解决这个问题的话,最根本的方法是改用自定义的序列化器。而比较简单的方法是为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格式的问题。
0 0