代码改变世界

WCF 使用 LINQ To SQL 数据序列化的终极解决办法!!!!

2012-07-23 20:15  duzher  阅读(727)  评论(0编辑  收藏  举报

WCF服务器端如果使用了LINQ to SQL实体化数据的话,由于序列化问题数据不能正常返回数据

找到了很多个文章,分别介绍了几个办法:

1、 老外的,需要在每个接口上取数据前先使用DataLoadOptions

Returning LINQ to SQL Entities from a WCF Service

http://www.codeexperiment.com/post/Returning-LINQ-to-SQL-Entities-From-WCF.aspx

2、 中国人的,需要对每个含有子表的表做以下两种操作都可以:

a) 第一种操作:以族库表中的Users表为例,对自动生产的Roles 属性,添加[DataMemeber] 标记,这个方法看似简单,但是一旦数据库发生变化,需要重新生成映射的话,改动很痛苦

b) 第二种操作:按照文章说的,单写一个名称相同的Partial类,将Roles属性提出来单独封装,这个方法看似屏蔽了数据库改动的风险,但是其实改动起来更麻烦

http://www.cnblogs.com/viter/archive/2008/07/27/1252655.html

3、 另一个老外的,以咱们的项目为例,除了序列化单向设置外,直接到LINQ to SQL的生成的代码上【DB.designer.cs】中,将所有的类都加上【DataContract】标记,并且给外键关联的字段加上【DataMember】属性,这样的方法更简单了,虽然也好,但是还是不能避免数据库发生变化的情况

http://www.aspfree.com/c/a/Windows-Scripting/Designing-WCF-DataContract-Classes-Using-the-LINQ-to-SQL-Designer/

4、 另一个老外的,这篇文章基本是告诉了答案了。

http://stackoverflow.com/questions/1351099/serializing-linq2sql-over-wcf-bug-or-misunderstanding

Go to the properties of your LinqToSqlGenerator and set the Serialization Mode to Unidirectional

This will cause the tool to add the DataMember attribute to the required properties and you will see that the StateId will not be a DataMember since it will be automatically set when the State Property is set while deserializing. 上面的话的意思是,只要改成单项序列号,自动会吧该设置DataContract、DataMember的地方全部设置完,如果真这样,实在太爽了!我用VS2012试了一下,确实做到了,你们用VS2010试试。但是似乎还是有问题,下面这个老外说明的很详细

5、 这个老外遇到的问题时,采用前面几个问题的综合解决办法还是遇到了order.Customer属性出不来,说明单向序列号还是有问题的,并且这个老外也没解决问题。

http://weblogs.asp.net/zeeshanhirani/archive/2008/07/14/entity-refs-not-getting-serialized-with-wcf-service.aspx

6、 现在这个老外更牛,几乎解决了问题了:

a) 不设置单向序列化,而是直接到LINQ to SQL的设计视图上,选中关联关系的箭头线,设置子表访问权限为Internal,就可以了。这个解决办法恐怕是所有解决办法里最爽的了。但是问题在于不能序列号二进制的数据,比如图片数据

b) 为了解决上述问题,可以设置单向序列号,但是同时导致了更严重的问题:【If you specify a non-public flag (Internal, Protected or Private) for the relationship (or any field for that matter), the DataMember property is not generated.】,意思是刚才设置那堆Internal属性的键值,都不会被标记成DataMember……

http://www.west-wind.com/weblog/posts/2007/Sep/02/LINQ-to-SQL-and-Serialization

这个老外总结的很到位:

All of this also got me to thinking about these object relationships. In code having an Projects property is useful because you can apply LINQ filter expressions against it. But when you persist via serialization you don't get that flexibility. So if you were to serialize you would get ALL of the child entities serialized which is probably not at all what you want. For example, in my simple time tracking app I'm using as my playground I have a customer entity with individual entries. I really don't want to serialize ALL entries. I MIGHT want to serialize a handful of new entries, but certainly not ALL of them once the application has been running for a few months.

The problem now is that we have a logical AND a physical relationship in this scenario. As non parameterized view the Projects property doesn't make any sense in a Customer object. However, a Entries property on an Invoice object might make perfect sense. In some cases it makes perfect sense to expose the relationship directly.But if you decide not to express the relationship explicitly between say Customer and Projects you loose some of the cool functionality that LINQ provides by automatically understanding relationships.

I'm not sure there's a solution to this. I think this same problem also exists with the ADO.NET Entity Framework (which uses a very similar approach although it's even more implicit about how relationships are defined by explicitly removing key fields).

Maybe some of the folks who've been doing OR/M development for some time with other tools like nHibernate can chime in. This is a thorny problem.

It seems to me that LINQ to SQL could actually solve these issues quite easily by providing a few more properties to set on entities and relationships. Such as a Serialized property on relations, and explicit serialization attributes on the entities. But even as it is you can bend LINQ to SQL to do as you need it seems.

7、 目前为止看到的现在的LINQ to SQL只有【Unidirectional单向】,可是下面这个网址说有个牛人实现了【 bidirectional双向】序列化

http://stackoverflow.com/questions/2077688/is-there-a-simple-linq-to-sql-generator-with-bi-directional-serialization-attrib

点击:Thishttp://l2st4.codeplex.com/ 】 turned out to be the solution to my problem. I had just resigned myself to start researching my own generator when I stumbled upon that. It has a bidirectional serialization option and it works great! Here's a link to the author's bog, which contains a great video example of how to get started.

关于双向序列化,可以看看这里:

http://social.msdn.microsoft.com/search/en-us?query=bidirectional+

http://l2st4.codeplex.com/ 请到这里下载下代码,研究一下,我刚下载了一下,这个应该似乎可能大概也许差不多是终极解决办法!

8、第七种办法的续集:这个网址【http://stackoverflow.com/questions/2468471/bidirectional-serialization-with-linq 】也说了双向序列化的实现方式

http://www.codeproject.com/Articles/33743/Using-LINQ-to-SQL-in-N-Tier-Architectures

这个文章写了这个问题的终极解决办法:

Using the (IsReference=true) Attribute in DataContract Serialization

In .NET Framework 3.5 SP1, a new enhancement was done to mark a DataContract with the (IsRference=true) attribute. This will tell the serializer (DataContractSerializer) that this contract is a reference, so that it will deal with it as a reference, and in this case, it will be able to detect the cyclic dependencies.

The problem now is that no one updated the LINQ to SQL designer to add a new mode for “Bi-Directional” serialization. Um… this is a real problem. I believe they will fix it in the next release of Visual Studio, but for now, there is another solution from my hero damieng at CodePlex: http://www.codeplex.com/l2st4.

To use the template in your project:

  1. Download it from the above link.
  2. Include the template in your project
  3. Rename the tt file to the same name as your DBML file.
  4. Open the tt file, and set SerializeDataContractSP1 = true. As we discussed before, this will generate bi-directionally serializable objects.
  5. Click the tt file and choose “Run Custom Tool”.
  6. You must change the compilation option for the original designer.cs file. Set the build action to None, instead of Compile, to avoid having duplicate generated classes with the same name.

最重要的:上面这个工具还支持【Concurrency Checks】,自己去看上面的文章详细的介绍吧。

使用这个工具的【注意事项】:

1、如下图所示:DB.dbml、DB.tt、L2ST4.ttinclude 三个文件必须要在同一级目录下

image

2、执行了第6步后,编译,会发现一个错误,如下图所示:

image

遇到以上错误,最笨地解决办法是使用下面这句话替换报错的这句话:

base(global::System.Configuration.ConfigurationManager.ConnectionStrings["YourConnectionString"].ConnectionString, mappingSource)

产生这个错误的原因是在这里:

#region Construction
<#if (data.ConnectSettingsObject != null) {#>
        public <#=data.ContextName#>() :
            base(global::<#=data.ConnectSettingsObject#>.Default.<#=data.ConnectSettingsProperty#>, mappingSource)
        {
            OnCreated();
        }

修改一下这句话:

        #region Construction
<#if (data.ConnectSettingsObject != null) {#>
        public <#=data.ContextName#>() :
            base(global::<#=data.ConnectSettingsObject#>["<#=data.ConnectSettingsProperty#>"].ConnectionString, mappingSource)
        {
            OnCreated();
        }

 

时间关系,只找到了这些办法,但是每个办法否足矣解决咱们的问题,只是根据自己的情况择优录用下即可,但是最后的终极办法,现阶段值得所有人去采用。

另外,关于延迟加载(Lazy Loading)和预加载(Pre-Loading):

上面的技术中提到了使用DataLoadOptions解决序列化的问题,这个东西是一个非常激动人心的功能“延迟加载Lazy Loading”,采用这个办法实现解决序列号问题也是不错的选择。详细说明可以看这里:

http://ericphan.info/blog/2008/3/19/linq-to-sql-with-wcf-lazy-loading-and-caveats.html

The question is how do you then pass a LINQ entity and its child collections across WCF?

This is where the DataLoadOptions class comes in handy. This class allows you to specify exactly what child collections to load along with your entity.

好处:Without this you would have to make two service calls - one to get the parent object and another to get the child collection.

坏处:Because LINQ-to-SQL serialization is unidirectional, you cannot use the DataLoadOptions class to load a parent object. e.g. If I have an Order I can’t use the DataLoadOptions to load the Customer as well. This is done to prevent cyclic relationships.

http://www.codeproject.com/Articles/33743/Using-LINQ-to-SQL-in-N-Tier-Architectures

Using Load Options to Pre-load Child/Related Objects

In LINQ, classes are loaded from the DB only when needed. However, in a disconnected environment, you might need to pre-load the child objects and send them all to the client at once. You need to save multiple trips to the server, and load all that you need in one trip.

To achieve this, you need to use the load options with the data context. The following code is an example:

clip_image001Collapse | Copy Code

DataClasses1DataContext dc1 = new DataClasses1DataContext(); 
 
DataLoadOptions dlo = new DataLoadOptions(); 
dlo.LoadWith<Material>(m => m.Category); 
dlo.LoadWith<Material>(m => m.Prices); 
dc1.LoadOptions = dlo; 
return dc1.Materials.ToList();