WCF中因序列化问题引起的异常和错误。

这段时间一直在忙着赶项目,由DAL+WCF+WinForm几层组成。其中数据库使用的MySQL+MySQL.Data.dll Driver。不过在使用中,经常碰到如下错误:

接收对 http://localhost/***.svc 的 HTTP 响应时发生错误。这可能是由于服务终结点绑定未使用 HTTP 协议造成的。这还可能是由于服务器中止了 HTTP 请求上下文(可能由于服务关闭)所致。有关详细信息,请参见服务器日志。

有时也会出现如下错误

基础连接已经关闭: 连接被意外关闭。

经过仔细核对发现,每当发生MySql.Data.MySqlClient.MySqlException异常时,就会报这个错误。网上找了半天也没有找到解决方案,后来使用Trace Viewer跟踪才发现是MySqlException序列化时出现了问题:

System.ServiceModel.CommunicationException: 尝试对参数 http://tempuri.org/ 进行序列化时出错: result。InnerException 消息是“不应为数据协定名称为“MySqlException:http://schemas.datacontract.org/2004/07/MySql.Data.MySqlClient”的类型“MySql.Data.MySqlClient.MySqlException”。请考虑使用 DataContractResolver,或将任何未知类型以静态方式添加到已知类型的列表。例如,可以使用 KnownTypeAttribute 特性,或者将未知类型添加到传递给 DataContractSerializer 的已知类型列表。”。有关详细信息,请参见 InnerException。  System.Runtime.Serialization.SerializationException:.......

其中result是一个存储操作结果的类实例,它横跨DAL、WCF、WinForm三层,用以存储一个完整操作中发现的异常及信息。里面对可能发生的异常做了包装,它的结构如下(其它属性及构造函数等没有列出):

[DataContract]
public class Result
{
    private bool success;
 
    [DataMember]
    public boll Success
    {
        get {return this.success;}
        set {this.success = value;}
    }
 
    private Exception exception;
    
    [DataMember]
    publice Exception Exception
    {
        get {return this.exception;}
        set {this.exception = value;}
    }
}

当DAL层中涉及SQL语句的执行发生错误时,result.Exception类型实际为MySqlException。虽然在Result类定义时已经声明[DataContract],不过序列化时仍然出错,头疼!查看MySqlException的定义如下:

[Serializable]
public sealed class MySqlException : DbException
{
    public int Number { get; }
}

明明是已经声明了[Serializable]特性,而且查看MySQL.Data.dll 文档,发现其在0.7版本的时候,就已经支持序列化了,却仍然不可在WCF中序列化,只能再找原因。

根据错误的提示,在IService的契约声明接口中,加上了[ServiceKnownType(typeof(MySql.Data.MySqlClient.MySqlException))]声明,可到头来结果还是一样不起作用。但是如果发生的异常不是MySqlException类型时,就不会报序列化错误。

对比Exception和MySqlException的实现,发现虽然都声明了[Serializable]特性,但MySqlException却没有实现序列化和逆序列化的函数。问题很可能出现在这个地方。

不过由于没有MySQL.Data.dll 的源代码,无法让添加这两个函数,只得新建一个异常类CustomException存放在Result。其代码如下:

[Serializable]    
public class CustomException : ISerializable,Exception
{
    private string message;
 
    public override string Message
    {
        get
        {
            return this.message;
        }
    }
 
    private string stackTrace;
 
    public override string StackTrace
    {
        get
        {
            return this.stackTrace;
        }
    }
 
    public CustomException() { }
 
    public CustomException(string exceptionMessage, string exceptionStackTrace)
    {
        this.message = exceptionMessage;
        this.stackTrace = exceptionStackTrace;
    }
    /*
     以下序列化和逆序列化中,只保存了Message和StackTrace
     */
    protected CustomException(SerializationInfo info, StreamingContext context)
    {
        this.message = info.GetString("Message");
        this.stackTrace = info.GetString("StackTraceString");
    }
 
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Message", this.message);
        info.AddValue("StackTraceString", this.stackTrace);
    }
 
    public override string ToString()
    {
        if (string.IsNullOrEmpty(this.message)) 
            return string.Empty;
 
        return this.message.ToString();
    }
}

之所以override定义Message和StackTrace,是为能对它们俩重写,因为Exception类中,定义的StackTrace是只读的,Message也只能在构造函数中赋值。此外,此处只定义了这两个属性,是因为项目中只用到了Exception中的这两个属性,一般情况下,这两个属性也已经足够查找异常信息了。

当然最主要的地方,还是要实现一个构造函数,用来序列化,以及GetObjectData 方法设置序列化对象的替代值。

 

再次跟踪执行,当产生MySqlException时,Result对象可以正常从DAL->WCF->WinForm。OK啦!

 

总结:这个问题,的确是折磨了我好几天,由于是对WCF头一次在项目中使用,对它的了解也不多。如果不是看到使用Trace Viewer,还不会找到问题的本质所在。看来.NET下的异常提示,有时候也不一定就是问题的产生点,就像编译代码一样,少个逗号,提示的信息却是五花八门。

posted @ 2011-10-15 23:33  Ritchie(乞戈)  阅读(10546)  评论(4编辑  收藏  举报