EntityFramework5.0与WCF使用遇到的问题

在使用EntityFramework5.0,并将其作为WCF放在服务端的时候遇到了如下问题:

1. EF在客户端与服务端之间传输问题

由于将EF放在服务端,所以类必须具有DataContract属性。

服务契约定义了远程访问对象和可供调用的方法,数据契约则是服务端和客户端之间要传送的自定义数据类型。

只有声明为DataContract的类型的对象可以被传送,且只有成员属性会被传递,成员方法不会被传递。WCF对声明为DataContract的类型提供更加细节的控制,可以把一个成员排除在序列化范围以外,也就是说,客户端程序不会获得被排除在外的成员的任何信息,包括定义和数据。默认情况下,所有的成员属性都被排除在外,因此需要把每一个要传送的成员声明为DataMember,如下所示。

1
2
3
4
5
6
7
8
[DataContract]
public partial class base_user
{
[DataMember]
public string user_code { get; set; }
[DataMember]
public string user_num { get; set; }
}

那么如何给EF生成的所有类加入这一属性?在EntityFramework5.0中使用T4模板生成类,同时也能很方便的进行批量修改:

1.进入实体类的T4模板

2.添加引用,修改UsingDirectives

1
2
3
4
5
6
7
8
9
10
11
12
public string UsingDirectives(bool inHeader, bool includeCollections = true)
{
return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
? string.Format(
CultureInfo.InvariantCulture,
"{0}using System;{1}{3}" +
"{2}",
inHeader ? Environment.NewLine : "",
includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
inHeader ? "" : Environment.NewLine, Environment.NewLine + "using System.Runtime.Serialization;")
: "";
}

3.添加数据契约属性

1
2
3
4
5
6
7
8
9
10
public string EntityClassOpening(EntityType entity)
{
return string.Format(
CultureInfo.InvariantCulture,
"{4}{0} {1}partial class {2}{3}",
Accessibility.ForType(entity),
_code.SpaceAfter(_code.AbstractOption(entity)),
_code.Escape(entity),
_code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)),"[DataContract]" + Environment.NewLine);
}

4.添加[DataMember]属性

1
2
3
4
5
6
7
8
9
10
11
public string Property(EdmProperty edmProperty)
{
return string.Format(
CultureInfo.InvariantCulture,
"{5}\t{0} {1} {2} { { {3}get;{4}set; }}",
Accessibility.ForProperty(edmProperty),
_typeMapper.GetTypeName(edmProperty.TypeUsage),
_code.Escape(edmProperty),
_code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
_code.SpaceAfter(Accessibility.ForSetter(edmProperty)),"[DataMember]" + Environment.NewLine);
}

2. EF引起WCF停止的问题

在调试中发现数据底层处理正常,但将数据封装成实体类并传送给客户端的时候报错,服务异常停止。这是由于代理类Proxy Types产生的影响,在程序运行过程中可以在EF的操作结果后设置断点,会看到封装的类型并不是实体类,举例来说,EF中有实体类Person,但在操作结果封装的时候并不是用Person,而是用了它的一个代理类,如Person123。如果我们给EF建立了实体类关联,如Person的关联子类Job,那么在序列化的过程中,代理类Person123没法找到Job类,因为代理类没有设置关联,所以产生了错误。解决这个问题的方法禁用实体的代理类。

1.进入实体的Context模板

2.修改实体模型初始化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public <#=code.Escape(container)#>()
: base("name=<#=container.Name#>")
{
<#
if (!loader.IsLazyLoadingEnabled(container))
{
#>
this.Configuration.LazyLoadingEnabled = false;
<#
}
#>
this.Configuration.ProxyCreationEnabled = false;
}

3. EF使用INotifyChangedProperty接口的问题

在EF5.0中使用T4模板从数据库生成实体模型并不会使用INotifyChangedProperty接口,所以如果需要,可以修改模板,方法和问题1一样:

1.添加引用,修改UsingDirectives

1
2
3
4
5
6
7
8
9
10
11
12
public string UsingDirectives(bool inHeader, bool includeCollections = true)
{
return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
? string.Format(
CultureInfo.InvariantCulture,
"{0}using System;{1}{3}{4}" +
"{2}",
inHeader ? Environment.NewLine : "",
includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
inHeader ? "" : Environment.NewLine, Environment.NewLine + "using System.Runtime.Serialization;", Environment.NewLine +"using System.ComponentModel;")
: "";
}

2.添加INotifyChanged接口,修改EntityClassOpening

1
2
3
4
5
6
7
8
9
10
public string EntityClassOpening(EntityType entity)
{
return string.Format(
CultureInfo.InvariantCulture,
"{4}{0} {1}partial class {2}{3}:INotifyPropertyChanged",
Accessibility.ForType(entity),
_code.SpaceAfter(_code.AbstractOption(entity)),
_code.Escape(entity),
_code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)),"[DataContract]" + Environment.NewLine);
}

3.实现INotifyChanged接口,找到codeStringGenerator.EntityClassOpening,在下面添加

1
2
3
4
5
6
7
8
9
10
11
<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
<#=codeStringGenerator.EntityClassOpening(entity)#>
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if(PropertyChanged!= null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}

4.重写访问控制器,修改Property

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public string Property(EdmProperty edmProperty)
{
return
"[DataMember]" + Environment.NewLine +
"\tprivate " + _typeMapper.GetTypeName(edmProperty.TypeUsage) + " _" + _code.Escape(edmProperty) + ";" + Environment.NewLine +
"\tpublic " + _typeMapper.GetTypeName(edmProperty.TypeUsage) + " " + _code.Escape(edmProperty) + Environment.NewLine +
"\t{" + Environment.NewLine +
"\t\tget"+ Environment.NewLine +
"\t\t{"+ Environment.NewLine +
"\t\t\treturn "+ "_" + _code.Escape(edmProperty) + ";" + Environment.NewLine +
"\t\t}"+ Environment.NewLine +
"\t\tset"+ Environment.NewLine +
"\t\t{"+ Environment.NewLine +
"\t\t\t_" + _code.Escape(edmProperty) + "=value;" + Environment.NewLine +
"\t\t\tOnPropertyChanged(\""+_code.Escape(edmProperty) + "\");"+Environment.NewLine +
"\t\t}"+Environment.NewLine +
"\t}";

}

4. EF禁用代理模式后不能在服务端使用导航属性的问题

在问题2中,使用了this.Configuration.ProxyCreationEnabled = false; 禁用EF代理模式,但是造成的后果是不能在服务端使用实体的导航属性,获取的所有导航属性均为null.如果需要在服务端使用导航属性,可以将代理模式开启this.Configuration.ProxyCreationEnabled = true; 同时需要解决问题2,可以在服务端返回客户端的接口上加上自定义的属性ApplyProxyDataContractResolverAttribute:

1
2
3
[OperationContract]
[ApplyProxyDataContractResolverAttribute]
TestModel GetData(string fileid);

ApplyProxyDataContractResolverAttribute的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class ProxyDataContractResolver : DataContractResolver
{
private XsdDataContractExporter _exporter = new XsdDataContractExporter();

public override Type ResolveName(string typeName, string typeNamespace, Type declaredType,
DataContractResolver knownTypeResolver)
{
return knownTypeResolver.ResolveName(
typeName, typeNamespace, declaredType, null);
}

public override bool TryResolveType(Type dataContractType, Type declaredType,
DataContractResolver knownTypeResolver,
out XmlDictionaryString typeName,
out XmlDictionaryString typeNamespace)
{

Type nonProxyType = ObjectContext.GetObjectType(dataContractType);
if (nonProxyType != dataContractType)
{
// Type was a proxy type, so map the name to the non-proxy name
XmlQualifiedName qualifiedName = _exporter.GetSchemaTypeName(nonProxyType);
XmlDictionary dictionary = new XmlDictionary(2);
typeName = new XmlDictionaryString(dictionary,
qualifiedName.Name, 0);
typeNamespace = new XmlDictionaryString(dictionary,
qualifiedName.Namespace, 1);
return true;
}
else
{
// Type was not a proxy type, so do the default
return knownTypeResolver.TryResolveType(
dataContractType,
declaredType,
null,
out typeName,
out typeNamespace);
}
}


}

/// <summary>
/// 用于标识要对返回到客户端的实体类(实体类的代理类)进行转化的接口
/// </summary>
public class ApplyProxyDataContractResolverAttribute : Attribute, IOperationBehavior
{
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}

public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
{
DataContractSerializerOperationBehavior
dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
}

public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
{
DataContractSerializerOperationBehavior
dataContractSerializerOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
}
public void Validate(OperationDescription description)
{
}
}

EntityFramework5.0与WCF使用遇到的问题

https://wurang.net/ef5_wcf/

作者

Wu Rang

发布于

2014-10-23

更新于

2021-12-06

许可协议

评论