通过EF获取数据库内容并传递给客户端,数据库名称叫topviewxp。
一.在WCFServiceLib中添加文件夹TopviewxpSevices,右键添加ADO.NET Entity Data Model
二.创建一个服务类TopviewxpService
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
namespace WCFServiceLib.TopviewxpSevices
{
[ServiceContract]
public interface ITopviewxpService
{
[OperationContract]
IList<T_MachineRoomInfo> GetMachineRoomInfos();
[OperationContract]
T_MachineRoomInfo GetMachineRoomInfo();
}
/// <summary>
/// Topviewxp数据库
/// </summary>
public class TopviewxpService: ITopviewxpService
{
public IList<T_MachineRoomInfo> GetMachineRoomInfos()
{
try
{
topviewxpEntities entities = new topviewxpEntities();
IList<T_MachineRoomInfo> list=entities.T_MachineRoomInfo.ToList();
return list;
}
catch (Exception ex)
{
return null;
}
}
public T_MachineRoomInfo GetMachineRoomInfo()
{
try
{
topviewxpEntities entities = new topviewxpEntities();
T_MachineRoomInfo roomInfo = entities.T_MachineRoomInfo.First();
return roomInfo;
}
catch (Exception ex)
{
return null;
}
}
}
}
3.在服务端测试获取数据
private void BtnEFTest_OnClick(object sender, RoutedEventArgs e)
{
try
{
TopviewxpService service=new TopviewxpService();
T_MachineRoomInfo room=service.GetMachineRoomInfo();
TbResult.Text += string.Format("{0}\n", room.RoomName);
}
catch (Exception ex)
{
TbResult.Text = ex.ToString();
}
}
结果有问题:No connection string named 'topviewxpEntities' could be found in the application config file.
发现是因为我是在WCFServiceLib(类库)中添加的Entity Data Model,相应的修改的是类库的App.config文件,将WCFServiceLib的App.Config中的connectionStrings部分拷贝到WCFServer的app.config中。
原本WCFServiceLib是用于在不同界面(控制台、WinForm、WPF)中公用服务端代码建的类库。
服务端能获取EF对象并显示了,但是客户端无法获取。
客户端显示
System.ServiceModel.CommunicationException: The server did not provide a meaningful reply; this might be caused by a contract mismatch, a premature session shutdown or an internal server error.
at System.Runtime.AsyncResult.End[TAsyncResult](IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.End(SendAsyncResult result)
at System.ServiceModel.Channels.ServiceChannel.EndCall(String action, Object[] outs, IAsyncResult result)
at System.ServiceModel.Channels.ServiceChannelProxy.TaskCreator.<>c__DisplayClass1_0.<CreateGenericTask>b__0(IAsyncResult asyncResult)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at UWPForWCF.MainPage.<BtnEFTest2_Click>d__22.MoveNext()
下面处理EF对象传输问题
我在获取数据后面添加了序列化的代码测试,发现无法序列化。
/// <summary>
/// 将对象序列化为XML数据
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static byte[] SerializeToXml<T>(T obj)
{
MemoryStream stream = new MemoryStream();
XmlSerializer xs = new XmlSerializer(typeof(T));
xs.Serialize(stream, obj);
byte[] data = stream.ToArray();
stream.Close();
return data;
}
结果:
不应是类型
System.Data.Entity.DynamicProxies.T_MachineRoomInfo_BB69D0679F9BBBF2965453DA19B14F1269078F3876B3BDE0ECA20F940998D97B。使用 XmlInclude 或 SoapInclude 特性静态指定非已知的类型。
是代理类导致的,这里的roomInfo实际上不是T_MachineRoomInfo类型,而是他的代理类。
因为T_MachineRoomInfo中有个到T_MachineRoomInfo_2D表的关联,这种情况EF是会用代理类的。
具体原理以后再研究。
public partial class T_MachineRoomInfo
{
public string RoomID { get; set; }
public string RoomName { get; set; }
public string PID { get; set; }
public string ImageName { get; set; }
public string ImageText { get; set; }
public string Remark { get; set; }
public Nullable<int> Cli { get; set; }
public Nullable<int> OptimumPercent { get; set; }
public Nullable<long> Obligate3 { get; set; }
public Nullable<long> Obligate4 { get; set; }
public string Obligate5 { get; set; }
public virtual T_MachineRoomInfo_2D T_MachineRoomInfo_2D { get; set; }
}
处理办法
参考:
https://www.cnblogs.com/Gyoung/p/3153875.html
1.是不用代理类:Configuration.ProxyCreationEnabled = false;
public partial class topviewxpEntities : DbContext
{
public topviewxpEntities()
: base("name=topviewxpEntities")
{
Configuration.ProxyCreationEnabled = false;
}
2.用DataContractSerializer和ProxyDataContractResolver反序列化
public T_MachineRoomInfo GetMachineRoomInfo()
{
try
{
topviewxpEntities entities = new topviewxpEntities();
T_MachineRoomInfo roomInfo = entities.T_MachineRoomInfo.First();
var serializer = new DataContractSerializer(typeof(T_MachineRoomInfo), new DataContractSerializerSettings()
{
DataContractResolver = new ProxyDataContractResolver()
});
using (var stream = new MemoryStream())
{
// 反序列化
serializer.WriteObject(stream, roomInfo);
stream.Seek(0, SeekOrigin.Begin);
T_MachineRoomInfo roomInfoNew = (T_MachineRoomInfo)serializer.ReadObject(stream);
roomInfo = roomInfoNew;
}
byte[] bytes= SerializerHelper.SerializeToXml(roomInfo);
return roomInfo;
}
catch (Exception ex)
{
return null;
}
}
不过这个的前提是要用[DataContract][DataMember]修改类
[DataContract]
public partial class T_MachineRoomInfo
{
[DataMember]
public string RoomID { get; set; }
[DataMember]
public string RoomName { get; set; }
[DataMember]
public string PID { get; set; }
[DataMember]
public string ImageName { get; set; }
[DataMember]
public string ImageText { get; set; }
[DataMember]
public string Remark { get; set; }
[DataMember]
public Nullable<int> Cli { get; set; }
[DataMember]
public Nullable<int> OptimumPercent { get; set; }
[DataMember]
public Nullable<long> Obligate3 { get; set; }
[DataMember]
public Nullable<long> Obligate4 { get; set; }
[DataMember]
public string Obligate5 { get; set; }
public virtual T_MachineRoomInfo_2D T_MachineRoomInfo_2D { get; set; }
}
当然WCF时肯定是要的,不过如果只是序列化的话有可能没有[DataContract][DataMember]。
另外,没有添加[DataMember]的属性会是空的。
单个对象多一次序列化和反序列化应该是没什么影响的。
顺便,这时如果去掉[DataContract],出现异常:
“System.Data.Entity.DynamicProxies.T_MachineRoomInfo_2D_FBF0DBE7B626DCACDA7B945BCFD342203A44A0D95BFC7A70572CCA6D6F2AC928”类型的对象图包含循环,如果禁用引用跟踪,择无法对其进行序列化。
这里的循环考虑是因为T_MachineRoomInfo_2D中也有对T_MachineRoomInfo的引用。
稍微整理一下提取一个T CloneByDataContract<T>(T obj)函数
public T_MachineRoomInfo GetMachineRoomInfo()
{
try
{
topviewxpEntities entities = new topviewxpEntities();
T_MachineRoomInfo roomInfo = entities.T_MachineRoomInfo.First();
roomInfo = CloneByDataContract(roomInfo);
byte[] bytes= SerializerHelper.SerializeToXml(roomInfo);
return roomInfo;
}
catch (Exception ex)
{
return null;
}
}
public static T CloneByDataContract<T>(T obj)
{
var serializer = new DataContractSerializer(typeof(T), new DataContractSerializerSettings()
{
DataContractResolver = new ProxyDataContractResolver()
});
using (var stream = new MemoryStream())
{
// 反序列化
serializer.WriteObject(stream, obj);
stream.Seek(0, SeekOrigin.Begin);
T objNew = (T)serializer.ReadObject(stream);
return objNew;
}
}
上面实际上用了一个数据契约解析器ProxyDataContractResolver
这个是EF自带的
具体源码是:
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);
}
}
}
将代理类对象按原来的类进行序列化
3.修改契约的数据解析器
3.1 这里通过特性安装自定义的解析器
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)
{
}
}
修改服务契约
[ServiceContract]
public interface ITopviewxpService
{
[ApplyProxyDataContractResolver]
[OperationContract]
IList<T_MachineRoomInfo> GetMachineRoomInfos();
[ApplyProxyDataContractResolver]
[OperationContract]
T_MachineRoomInfo GetMachineRoomInfo();
}
这样客户端那边就能正常获取了
在https://www.cnblogs.com/Gyoung/p/3153875.html里说这个是“加在服务契约上面,标识通过这个服务返回的方法都要进行反序列化”,但是我认为这一步并不是进行反序列化,而是修改序列化的目标类型,不用代理类而用原类型序列化给客户端。
3.2 修改服务宿主的所有服务契约的数据解析器(参考《WCF服务编程》中的数据解析器部分)
public static class ServiceHostHelper
{
public static void SetProxyDataContractResolver(this ServiceHost host)
{
SetDataContractResolver<ProxyDataContractResolver>(host);
}
public static void SetDataContractResolver<T>(this ServiceHost host) where T : DataContractResolver,new ()
{
foreach (ServiceEndpoint endpoint in host.Description.Endpoints)
{
foreach (OperationDescription operation in endpoint.Contract.Operations)
{
DataContractSerializerOperationBehavior behavior =
operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
behavior.DataContractResolver = new T();
}
}
}
}
在启动宿主前调用SetProxyDataContractResolver,就行了。
为了防止修改掉其他自身设置的数据解析器,稍微改一下。
if (behavior.DataContractResolver == null)
{
behavior.DataContractResolver = new T();
}
嘛,这样使用的前提一般是自己很确定这个服务里面的操作契约返回的数据都要用
ProxyDataContractResolver解析。
4.新建一个新对象(原创)
前面的处理是在WCF加EF的环境下服务端传客户端时使用的,如果只是EF对象序列化或者说当前并不是WCF环境(不能使用ProxyDataContractResolver和DataContractSerializer)。
可以考虑,通过新建一个T_MachineRoomInfo对象,将代理类的属性相应的值传给新对象,返回新对象可以。
public T_MachineRoomInfo GetMachineRoomInfo()
{
try
{
topviewxpEntities entities = new topviewxpEntities();
T_MachineRoomInfo roomInfo = entities.T_MachineRoomInfo.First();
roomInfo = roomInfo.CloneByDataContract();
T_MachineRoomInfo roomInfoNew = new T_MachineRoomInfo();
roomInfoNew.RoomID = roomInfo.RoomID;
roomInfoNew.RoomName = roomInfo.RoomName;
//其他属性
roomInfo = roomInfoNew;
byte[] bytes = SerializerHelper.SerializeToXml(roomInfo);
return roomInfo;
}
catch (Exception ex)
{
return null;
}
}
提取通用函数
public T_MachineRoomInfo GetMachineRoomInfo()
{
try
{
topviewxpEntities entities = new topviewxpEntities();
T_MachineRoomInfo roomInfo = entities.T_MachineRoomInfo.First();
roomInfo = CloneObject(roomInfo);
byte[] bytes = SerializerHelper.SerializeToXml(roomInfo);
return roomInfo;
}
catch (Exception ex)
{
return null;
}
}
public static T CloneObject<T>(T obj) where T :new()
{
T objNew = new T();
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
try
{
object value = property.GetValue(obj);
property.SetValue(objNew, value);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
return objNew;
}
相应的数组的处理方法
public IList<T_MachineRoomInfo> GetMachineRoomInfos()
{
try
{
topviewxpEntities entities = new topviewxpEntities();
IList<T_MachineRoomInfo> list = entities.T_MachineRoomInfo.ToList();
list = CloneObjectList(list);
return list;
}
catch (Exception ex)
{
return null;
}
}
public static IList<T> CloneObjectList<T>(IList<T> list) where T : new()
{
IList<T> listNew = new List<T>();
foreach (T item in list)
{
T itemNew = CloneObject(item);
listNew.Add(itemNew);
}
return listNew;
}
这个和前面第二种方法的思路是一样的,他是将代理对象反序列化成原类型对象,这里是new一个原对象出来。
这种方面没有数据解析器方便,只能需要的类型一个一个添加。
1.2.4都是不用代理类的功能,那样就是在服务端也是无法用获取关联数据表的信息的。
总的来说还是用第3种方式比较好
四.在客户端添加引用
测试代码
public TopviewxpServiceClient GetTopviewxpServiceClient()
{
string ip = CbIpList.SelectedItem.ToString();
return ServiceClientFactory.GetTopviewxpServiceClient(ip);
}
private async void BtnEFTest_Click(object sender, RoutedEventArgs e)
{
try
{
TopviewxpServiceClient client = GetTopviewxpServiceClient();
T_MachineRoomInfo room = await client.GetMachineRoomInfoAsync();
TBResult.Text += room.ObjectToString();
}
catch (Exception ex)
{
TBResult.Text = ex.ToString();
}
}
private async void BtnEFTest2_Click(object sender, RoutedEventArgs e)
{
try
{
TopviewxpServiceClient client = GetTopviewxpServiceClient();
IList<T_MachineRoomInfo> rooms = await client.GetMachineRoomInfosAsync();
foreach (var item in rooms)
{
TBResult.Text += string.Format("{0}:\n",item.RoomName);
TBResult.Text += item.ObjectToString(";") + "\n";
}
}
catch (Exception ex)
{
TBResult.Text = ex.ToString();
}
}
public static TopviewxpServiceClient GetTopviewxpServiceClient(string ip)
{
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
binding.MaxReceivedMessageSize = int.MaxValue;
EndpointAddress endpointAddress = new EndpointAddress("net.tcp://" + ip + ":8001/TopviewxpService");
TopviewxpServiceClient currentClient = new TopviewxpServiceClient(binding, endpointAddress);
return currentClient;
}
public static string ObjectToString(this object obj, string interval = "\n")
{
string txt = "";
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
try
{
string name = property.Name;
object value = property.GetValue(obj);
txt += string.Format("{0}={1}{2}", name, value, interval);
}
catch (Exception ex)
{
txt += ex.ToString();
break;
}
}
return txt;
}
五.结果
可以
六.改进
1.循环引用
[DataContract] [DataMember]这两个特性,现在是要手动添加到EF生成的类中的。
不添加DataContract的话,操作契约中的类的会自动添加上DataContract,所有属性都会添加上DataMember
关联表的属性也会被加上
public virtual T_MachineRoomInfo_2D T_MachineRoomInfo_2D { get; set; }
关联表中又有对本对象的引用,会导致序列化失败的。
用前面的CloneByDataContract测试序列化的话会抛出异常:类型的对象图包含循环,如果禁用引用跟踪,择无法对其进行序列化
手动设置DataContract和DataMember
[DataContract]
public partial class T_MachineRoomInfo
{
[DataMember]
public string RoomID { get; set; }
[DataMember]
public string RoomName { get; set; }
[DataMember]
public string PID { get; set; }
[DataMember]
public string ImageName { get; set; }
[DataMember]
public string ImageText { get; set; }
[DataMember]
public string Remark { get; set; }
[DataMember]
public Nullable<int> Cli { get; set; }
[DataMember]
public Nullable<int> OptimumPercent { get; set; }
[DataMember]
public Nullable<long> Obligate3 { get; set; }
[DataMember]
public Nullable<long> Obligate4 { get; set; }
[DataMember]
public string Obligate5 { get; set; }
[DataMember]
public virtual T_MachineRoomInfo_2D T_MachineRoomInfo_2D { get; set; }
}
[DataContract]
public partial class T_MachineRoomInfo_2D
{
[DataMember]
public string RoomID { get; set; }
[DataMember]
public string RoomName { get; set; }
[DataMember]
public string ImageText { get; set; }
[DataMember]
public Nullable<int> OptimumPercent { get; set; }
[DataMember]
public Nullable<long> Obligate3 { get; set; }
[DataMember]
public Nullable<long> Obligate4 { get; set; }
[DataMember]
public string Obligate5 { get; set; }
//[DataMember]
public virtual T_MachineRoomInfo T_MachineRoomInfo { get; set; }
}
这样就可以了。
服务端获取的T_MachineRoomInfo数据也会包括T_MachineRoomInfo_2D属性的内容。
2.自动添加特性
参考:http://blog.csdn.net/yangxinyue315/article/details/44600671
修改Model.edmx文件下面的Model.tt文件的内容
2.1 添加DataContract
2.2 添加using System.Runtime.Serialization;
2.3 添加DataMember
Property下面的NavigationProperty不用修改,这样就不会导致循环了。需要将关联对象传递过去的话自己添加DataMember。
3.正如参考网址中后面评论提到的,应该再加一层简单的领域数据模型,确实也有。具体加到项目中时也不会直接使用TopviewxpSevice的。