Linq 之WebService返回Linq对象解决方案

写在篇头:本文为原创,转贴请注明,如有雷同,纯属巧合。 顺祝各位大朋友和小朋友儿童节快乐! 

使用了一段Linq2SQL,作为数据传输对象,Linq确实能有效的提高业务逻辑的开发效率 J

在Linq对象的使用上,也有不少短板。比如级联修改、删除等操作的开发非常容易出错,而且难以修正,因不明其问题所在。此类问题,网上的解决解答有很多,不过还是需要DEV自己过滤和实践。有时间的话,我会记录下来实际开发中的案例及解决方案,本篇暂不涉及。

相信很多.Net的DEV已经习惯采用多层架构的设计开发,并且业务逻辑层(以下采用BL表示)的一种流行部署方式是使用WebService。同时如果Dev又使用了Linq2SQL的技术,以Linq对象作为数据传输的载体,通过WebService返回到Client,那么问题就来了:

Linq对象是对数据表的映射,并且能很好的处理表间的级联关系 – 内聚,其实是一种对象引用。但是这种引用是一种嵌套引用,即A中有B,B中有A。这样的话WebService返回Linq对象时(有级联数据的)会产生异常,因为WebService要序列化这个对象为string,而Linq的嵌套引用的特性将使序列化的string为无穷长。

案例描述

Linq2SQL:DicBO是主表,DicBOProperty是子表

201106011518258095.png

WebService方法:获取DicBO对象    

ContractedBlock.gif ExpandedBlockStart.gif View Code
 
   
[WebMethod]
public DicBO GetLinq( string boID, bool loadProperties)
{
DicBO res
= null ;

try
{
using (UIModelDataContext dc = new UIModelDataContext(CONN_STR))
{
if (dc.Connection.State != System.Data.ConnectionState.Open)
{
dc.Connection.Open();
}

dc.CommandTimeout
= 1200 ;
dc.DeferredLoadingEnabled
= false ; // 设定不延迟加载

// 是否加载级联的属性表数据
if (loadProperties)
{
dc.DeferredLoadingEnabled
= false ;

DataLoadOptions dlo
= new DataLoadOptions();
dlo.LoadWith
< DicBO > (b => b.DicBOProperties);
dc.LoadOptions
= dlo;

}

var query
= dc.GetTable < DicBO > ().Where(v => v.BOID == boID);

if ( 1 == query.Count())
{
res
= query.SingleOrDefault();
}
else if ( 1 < query.Count())
{
throw new Exception( " More than one record. " );
}
}
}
catch (Exception ex)
{
res
= null ;
}

return res;
}



OK,我们来运行一下这个WebService,参数loadProperties指示是否要加载子表。设定2个测试用例,如下

  1. 当loadProperties=false时,不加载,仅获取主表

    201106011518351532.png

    点击"调用",结果显示:

    201106011518368957.png

  2. 当loadProperties=true时,加载,获取主表及子表数据

    201106011518368019.png

    点击"调用",发生错误:

    System.InvalidOperationException: 生成 XML 文档时出错。 ---> System.InvalidOperationException: 序列化类型 Stone.Software.UI.ModelDesign.DicBO 的对象时检测到循环引用。

    在 System.Xml.Serialization.XmlSerializationWriter.WriteStartElement(String name, String ns, Object o, Boolean writePrefixed, XmlSerializerNamespaces xmlns)

    在 Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write25_DicBO(String n, String ns, DicBO o, Boolean isNullable, Boolean needType)

    在 Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write4_DicBOProperty(String n, String ns, DicBOProperty o, Boolean isNullable, Boolean needType)

    在 Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write25_DicBO(String n, String ns, DicBO o, Boolean isNullable, Boolean needType)

    在 Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriter1.Write26_DicBO(Object o)

    在 Microsoft.Xml.Serialization.GeneratedAssembly.DicBOSerializer.Serialize(Object objectToSerialize, XmlSerializationWriter writer)

    在 System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)

    --- 内部异常堆栈跟踪的结尾 ---

    在 System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)

    在 System.Xml.Serialization.XmlSerializer.Serialize(TextWriter textWriter, Object o, XmlSerializerNamespaces namespaces)

    在 System.Xml.Serialization.XmlSerializer.Serialize(TextWriter textWriter, Object o)

    在 System.Web.Services.Protocols.XmlReturnWriter.Write(HttpResponse response, Stream outputStream, Object returnValue)

    在 System.Web.Services.Protocols.HttpServerProtocol.WriteReturns(Object[] returnValues, Stream outputStream)

    在 System.Web.Services.Protocols.WebServiceHandler.WriteReturns(Object[] returnValues)

    在 System.Web.Services.Protocols.WebServiceHandler.Invoke()

这的确是个令人头疼的问题,上网搜索了不少,但都没有一个可以立竿见影的解答。

当然这不是Linq设计的Bug,也不是WebService设计的bug,而是这两种技术的特性不兼容造成的。如何解决这个棘手的问题呢???

劳动人民的智慧这时候又要启动了(嘿嘿……),解决方法分为3步

1> 如果你做过XML编程,别忘了.Net还提供了一项技术 – 序列化&反序列化 (详细的技术参考http://msdn.microsoft.com/zh-cn/library/e123c76w(v=VS.80).aspx,这里不做讲解

 现在,我们手动加工一下自动产生的Linq对象代码(偶称其为XLinq对象 J):  

ContractedBlock.gif ExpandedBlockStart.gif XLinq
 
   
[XmlTypeAttribute(TypeName = " DicBO " )]
[XmlRootAttribute(Namespace
= "" , IsNullable = false )]
[
global ::System.Data.Linq.Mapping.TableAttribute(Name = " dbo.Dic_BO " )]
public partial class DicBO : INotifyPropertyChanging, INotifyPropertyChanged
{

private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);

private string _BOID;

private string _Description;

private EntitySet < ParamBO > _ParamBOs;

private EntitySet < DicBOProperty > _DicBOProperties;

#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnBOIDChanging( string value);
partial void OnBOIDChanged();
partial void OnDescriptionChanging( string value);
partial void OnDescriptionChanged();
#endregion

public DicBO()
{
this ._ParamBOs = new EntitySet < ParamBO > ( new Action < ParamBO > ( this .attach_ParamBOs), new Action < ParamBO > ( this .detach_ParamBOs));
this ._DicBOProperties = new EntitySet < DicBOProperty > ( new Action < DicBOProperty > ( this .attach_DicBOProperties), new Action < DicBOProperty > ( this .detach_DicBOProperties));
OnCreated();
}

[XmlElementAttribute(
" BOID " )]
[
global ::System.Data.Linq.Mapping.ColumnAttribute(Storage = " _BOID " , DbType = " varchar(50) " , CanBeNull = false , IsPrimaryKey = true )]
public string BOID
{
get
{
return this ._BOID;
}
set
{
if (( this ._BOID != value))
{
this .OnBOIDChanging(value);
this .SendPropertyChanging();
this ._BOID = value;
this .SendPropertyChanged( " BOID " );
this .OnBOIDChanged();
}
}
}

[XmlElementAttribute(
" Description " )]
[
global ::System.Data.Linq.Mapping.ColumnAttribute(Storage = " _Description " , DbType = " varchar(200) " , CanBeNull = false )]
public string Description
{
get
{
return this ._Description;
}
set
{
if (( this ._Description != value))
{
this .OnDescriptionChanging(value);
this .SendPropertyChanging();
this ._Description = value;
this .SendPropertyChanged( " Description " );
this .OnDescriptionChanged();
}
}
}

[XmlArray(
" ParamBOs " )]
[
global ::System.Data.Linq.Mapping.AssociationAttribute(Name = " DicBO_ParamBO " , Storage = " _ParamBOs " , ThisKey = " BOID " , OtherKey = " BOID " )]
public EntitySet < ParamBO > ParamBOs
{
get
{
return this ._ParamBOs;
}
set
{
this ._ParamBOs.Assign(value);
}
}

[XmlArray(
" DicBOProperties " )]
[
global ::System.Data.Linq.Mapping.AssociationAttribute(Name = " DicBO_DicBOProperty " , Storage = " _DicBOProperties " , ThisKey = " BOID " , OtherKey = " BOID " )]
public EntitySet < DicBOProperty > DicBOProperties
{
get
{
return this ._DicBOProperties;
}
set
{
this ._DicBOProperties.Assign(value);
}
}

// 以下代码省略

}

[XmlTypeAttribute(TypeName
= " DicBOProperty " )]
[XmlRootAttribute(Namespace
= "" , IsNullable = false )]
[
global ::System.Data.Linq.Mapping.TableAttribute(Name = " dbo.Dic_BOProperty " )]
public partial class DicBOProperty : INotifyPropertyChanging, INotifyPropertyChanged
{

private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);

private string _BOPropertyID;

private string _BOPropertyName;

private string _BOPropertyType;

private int _BOPropertyLength;

private string _BOID;

private EntitySet < BDMappingDetail > _BDMappingDetails;

private EntityRef < DicBO > _DicBO;

#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnBOPropertyIDChanging( string value);
partial void OnBOPropertyIDChanged();
partial void OnBOPropertyNameChanging( string value);
partial void OnBOPropertyNameChanged();
partial void OnBOPropertyTypeChanging( string value);
partial void OnBOPropertyTypeChanged();
partial void OnBOPropertyLengthChanging( int value);
partial void OnBOPropertyLengthChanged();
partial void OnBOIDChanging( string value);
partial void OnBOIDChanged();
partial void OnBOPropertyValueChanging( string value);
partial void OnBOPropertyValueChanged();
#endregion

public DicBOProperty()
{
this ._BDMappingDetails = new EntitySet < BDMappingDetail > ( new Action < BDMappingDetail > ( this .attach_BDMappingDetails), new Action < BDMappingDetail > ( this .detach_BDMappingDetails));
this ._DicBO = default (EntityRef < DicBO > );
OnCreated();
}

[XmlElementAttribute(
" BOPropertyID " )]
[
global ::System.Data.Linq.Mapping.ColumnAttribute(Storage = " _BOPropertyID " , DbType = " varchar(50) " , CanBeNull = false , IsPrimaryKey = true )]
public string BOPropertyID
{
get
{
return this ._BOPropertyID;
}
set
{
if (( this ._BOPropertyID != value))
{
this .OnBOPropertyIDChanging(value);
this .SendPropertyChanging();
this ._BOPropertyID = value;
this .SendPropertyChanged( " BOPropertyID " );
this .OnBOPropertyIDChanged();
}
}
}

[XmlElementAttribute(
" BOPropertyName " )]
[
global ::System.Data.Linq.Mapping.ColumnAttribute(Storage = " _BOPropertyName " , DbType = " varchar(50) " , CanBeNull = false , UpdateCheck = UpdateCheck.Never)]
public string BOPropertyName
{
get
{
return this ._BOPropertyName;
}
set
{
if (( this ._BOPropertyName != value))
{
this .OnBOPropertyNameChanging(value);
this .SendPropertyChanging();
this ._BOPropertyName = value;
this .SendPropertyChanged( " BOPropertyName " );
this .OnBOPropertyNameChanged();
}
}
}

[XmlElementAttribute(
" BOPropertyType " )]
[
global ::System.Data.Linq.Mapping.ColumnAttribute(Storage = " _BOPropertyType " , DbType = " varchar(50) " , CanBeNull = false , UpdateCheck = UpdateCheck.Never)]
public string BOPropertyType
{
get
{
return this ._BOPropertyType;
}
set
{
if (( this ._BOPropertyType != value))
{
this .OnBOPropertyTypeChanging(value);
this .SendPropertyChanging();
this ._BOPropertyType = value;
this .SendPropertyChanged( " BOPropertyType " );
this .OnBOPropertyTypeChanged();
}
}
}

[XmlElementAttribute(
" BOPropertyLength " )]
[
global ::System.Data.Linq.Mapping.ColumnAttribute(Storage = " _BOPropertyLength " , DbType = " int " , UpdateCheck = UpdateCheck.Never)]
public int BOPropertyLength
{
get
{
return this ._BOPropertyLength;
}
set
{
if (( this ._BOPropertyLength != value))
{
this .OnBOPropertyLengthChanging(value);
this .SendPropertyChanging();
this ._BOPropertyLength = value;
this .SendPropertyChanged( " BOPropertyLength " );
this .OnBOPropertyLengthChanged();
}
}
}

[XmlElementAttribute(
" BOID " )]
[
global ::System.Data.Linq.Mapping.ColumnAttribute(Storage = " _BOID " , DbType = " varchar(50) " , CanBeNull = false , UpdateCheck = UpdateCheck.Never)]
public string BOID
{
get
{
return this ._BOID;
}
set
{
if (( this ._BOID != value))
{
if ( this ._DicBO.HasLoadedOrAssignedValue)
{
throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
}
this .OnBOIDChanging(value);
this .SendPropertyChanging();
this ._BOID = value;
this .SendPropertyChanged( " BOID " );
this .OnBOIDChanged();
}
}
}

[XmlArray(
" BDMappingDetails " )]
[
global ::System.Data.Linq.Mapping.AssociationAttribute(Name = " DicBOProperty_BDMappingDetail " , Storage = " _BDMappingDetails " , ThisKey = " BOPropertyID " , OtherKey = " BOPropertyID " )]
public EntitySet < BDMappingDetail > BDMappingDetails
{
get
{
return this ._BDMappingDetails;
}
set
{
this ._BDMappingDetails.Assign(value);
}
}

[XmlIgnore]
[
global ::System.Data.Linq.Mapping.AssociationAttribute(Name = " DicBO_DicBOProperty " , Storage = " _DicBO " , ThisKey = " BOID " , OtherKey = " BOID " , IsForeignKey = true )]
public DicBO DicBO
{
get
{
return this ._DicBO.Entity;
}
set
{
DicBO previousValue
= this ._DicBO.Entity;
if (((previousValue != value)
|| ( this ._DicBO.HasLoadedOrAssignedValue == false )))
{
this .SendPropertyChanging();
if ((previousValue != null ))
{
this ._DicBO.Entity = null ;
previousValue.DicBOProperties.Remove(
this );
}
this ._DicBO.Entity = value;
if ((value != null ))
{
value.DicBOProperties.Add(
this );
this ._BOID = value.BOID;
}
else
{
this ._BOID = default ( string );
}
this .SendPropertyChanged( " DicBO " );
}
}
}

// 以下代码省略

}

重点看一下[Xml???Attribute]的部分,之所以添加XML的Attribute特性,是因为我要控制哪些数据需要序列化,哪些不用。原则如下

// 1. Linq类添加特性 - [XmlTypeAttribute(TypeName = "Linq class name")] & [XmlRootAttribute(Namespace = "", IsNullable = false)]

// 2. Linq类ColumnAttribute特性的属性添加特性 - [XmlElementAttribute("Property name")]

// 3. Linq类的子表对象(一般是EntitySet<T>类型的属性)添加特性 - [XmlArray("Property name")]

// 4. 外键属性添加特性 - [XmlIgnore]

 

2>  然后添加3个共用泛型方法去做Linq和XML的转换 3> 最后修改下WebService的GetLinq方法

ContractedBlock.gif ExpandedBlockStart.gif Common methods
 
   
public static string Linq2Xml < TLinq > (TLinq linq)
where TLinq : class
{
string res = null ;

using (StringWriter sw = new StringWriter())
{
XmlSerializer xs
= new XmlSerializer( typeof (TLinq));
xs.Serialize(sw, linq);
res
= sw.ToString();
}

return res;
}

public static TLinq Xml2Linq < TLinq > (Stream stream)
where TLinq : class
{
XmlSerializer xs
= new XmlSerializer( typeof (TLinq));
return (TLinq)xs.Deserialize(stream);
}

public static TLinq Xml2Linq < TLinq > ( string linqXML)
where TLinq : class
{
TLinq linq
= null ;

// Remove XML declare
if (linqXML.StartsWith( " <? " ))
{
int startIdx = linqXML.IndexOf( " ?> " );
linqXML
= linqXML.Substring(startIdx);
startIdx
= linqXML.IndexOf( " < " );
linqXML
= linqXML.Substring(startIdx);
}

using (MemoryStream ms = TextHelper.Instance.String2Stream(linqXML)) // 一个辅助方法做string to Stream,很简单,这里不提供代码
{
linq
= Xml2Linq < TLinq > (ms);
}

return linq;
}

返回值改为string类型:是由Linq对象转化而来的XML格式的string

ContractedBlock.gif ExpandedBlockStart.gif WebService.GetLinq
 
   
[WebMethod]
public string GetLinq( string boID, bool loadProperties)
{
string res = null ;

try
{
using (UIModelDataContext dc = new UIModelDataContext(CONN_STR))
{
DicBO bo
= null ;

if (dc.Connection.State != System.Data.ConnectionState.Open)
{
dc.Connection.Open();
}

dc.CommandTimeout
= 1200 ;
dc.DeferredLoadingEnabled
= false ;

if (loadProperties)
{
DataLoadOptions dlo
= new DataLoadOptions();
dlo.LoadWith
< DicBO > (b => b.DicBOProperties);
dc.LoadOptions
= dlo;
}

var query
= dc.GetTable < DicBO > ().Where(v => v.BOID == boID);

if ( 1 == query.Count())
{
bo
= query.SingleOrDefault();
}
else if ( 1 < query.Count())
{
throw new Exception( " More than one record. " );
}

res
= Common.Linq2Xml < DicBO > (bo);
}
}
catch (Exception ex)
{
res
= null ;
}

return res;
}

好了,大功告成,执行上面的2个测试用例

  1. 当loadProperties=false时,不加载,仅获取主表

    201106011518377397.png

    点击"调用",结果显示:

     

  2. 当loadProperties=true时,加载,获取主表及子表数据

    201106011518451064.png

    点击"调用",结果显示:

    20110601151847310.png

    (下面还有数据,省略了)

有兴趣的朋友们还可以做个客户端,接收到WebService的返回字符串,调用public static TLinq Xml2Linq<TLinq>()方法转化生成Linq对象。

 

一口气写了这么多,真累……

不过还是总结一下:自动产生的Linq类文件,是通过一个内置的T4模板生成的,希望Microsoft或有心的朋友们开发一套T4,能够生成支持序列化的XLinq对象 :P

MS.Net是一个非常优秀的开发平台,只要开动脑筋,应该木有解决不了的问题!



转载于:https://www.cnblogs.com/JamesL/archive/2011/06/01/2066086.html

LinqSamples 这些示例能够帮助您快速地了解 LINQ。对于具有一定 LINQ 经验的人员来说,它们还可以作为参考和指南。此外,还包括了几种实用工具。它们位于 LinqSamples 文件夹中。 •DynamicQuery: 在运行时创建 LINQ 查询的代码。 •LinqToNorthwind: 关于如何使用 LINQ To SQL 查询数据库的基本示例。 •LinqToXmlDataBinding: 将 LINQ to XML 代码绑定到 WPF 控件。 •ObjectDumper: 用于将 LINQ 查询的输出以文本模式写入屏幕的实用工具。 •PasteXmlAsLinq: 能够自动将 XML 转换为 LINQ to XML 的 Visual Studio 外接程序。 •QueryVisualizer: 使 LINQ to SQL 开发人员能够看到其查询的 SQL 代码,并能在网格中查看查询的结果。 •Reflector: 使用 LINQ 对使用反射 API 的代码中的对象进行正确的查询。 •RSS: 此示例可作为聚合若干 RSS 源的小型 Web 服务器。 •SampleQueries: 这是最重要的示例,其中包含了 500 多个关于如何在 LINQ to Objects、LINQ to SQL、LINQ to XML 和 LINQ to DataSet 中使用各个查询运算符的例子。 •SimpleLambdas: 几个关于如何编写和使用 lambda 表达式的示例。 •SimpleLinqToObjects: 简单的 LINQ 示例。为您显示使用 LINQ to Objects 创建内存对象的简单查询是多么简单。 •SimpleLinqToXml: 开始使用 LINQ to XML。 •WebServiceLinqProvider(TerraService): 用于 TerraServer-USA Web 服务的自定义 LINQ 提供程序和客户端。 •Whitepapers: 以下白皮书以 Word 格式存储在此目录中: ◦LINQ 项目概述 ◦LINQ to SQL 概述(适用于 C# 和 VB) ◦标准查询运算符 ◦LINQ to XML 概述 •WinFormsDataBinding: 学习如何在 Windows 窗体中使用网格显示 LINQ 查询。它包括一个一对多查询示例。 •XQuery: 另一个简单的 LINQ to XML 示例查询。此示例显示编写 LINQ to XML 查询所需的最少代码。 LINQ 示例数据库连接问题 如果开发计算机上安装有 SQL Server Express,则这些示例应无需修改即可正常发挥作用。下面是一个 备用连接字符串,如果不使用 SQL Express,但有 SQL Server 可供使用,则可对该字符串进行修改,以用于自己的用途。只要 test 一词显示在示例连接字符串中,您就可能需要修改该单词。 string connString = "server=test;database=northwind;user id=test;password=test"; 对于某些 SQL Express 安装,您可能无权启动用户实例。如果在此情况下收到错误消息,请尝试从连接字符串中删除子句 “;user instance = true”。 确保要访问的 northwind 数据库的副本未标记为只读。如有必要,请浏览到尝试访问的 NORTHWIND.MDF 的副本,右击该文件,然后选择“属性”。清除 只读特性。 用户已报告,在某些运行速度较慢的计算机上,或在使用虚拟 PC 时,某些数据库应用程序在首次运行时出错。请尝试运行该示例两次或三次。 未将 LDF (日志)文件随示例中包含的 Northwind 数据库自定义副本一起提供。特意这样做的目的是为了使 LDF 与 MDF 文件不会失去同步。运行使用该数据库的示例时,或从 Visual Studio 中的服务器资源管理器连接到该数据库时,将会自动创建 LDF 文件。如果在未先创建日志文件的情况下使用 SQL Server Management Studio,则在连接到 MDF 文件时,可能会遇到问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值