Silverlight + WCF使用Linq to SQL以及ADO.NET Entity Data Model更新数据库子表方法

websco 2010-04-18 10:15:48
加精
前言:日前,Microsfot已发布了Silverlight 4.0 RC2,其所主导的WCF RIA Services也渐渐为更多的开发人员所认识并使用,分布式N层结构数据访问要为WCF RIA Serices所代替了么? 但这不是我想要讨论的问题,这里仅是针对Silverlight + WCF在使用Linq to SQL以及ADO.NET Entity Data Model下的更新数据库子表的方式作比较。

使用Linq to SQL或ADO.NET Entity Data Model(Entity Framework实体框架,以下简称EF)访问数据库的一般方式是用实体Entity映射数据表,形成一种最直观的封装,对数据表的增、删、更改、查询等操作都可以变成对Entity的操作(这里说的只是“可以”,许多情况下Entity还是不如TSQL或存储过程方便的)。而在关系型数据库中,主子表的结构是很常见的,如下图的TMaster、TDetail:


LINQ to SQL(.NET 3.0)、EF 1.0(.NET 3.5SP1)、EF 2.0(.NET 4.0)中对于这种主子表关系的表示有所不同:

LINQ to SQL:

l 子实体TDetail中含有MasterKey属性,对应主实体中的MasterKey;

l 在两个实体的设计图中均不显示出导航属性,但是在代码中存在的;

l 导航属性、在DataContext中的属性均以单数形式显示(即不以TMasters、TDetails形式显示),且没有地方能设置;

l 特别要注意的是DataContext的序列化模式,要想在WCF中传输LINQ to SQL实体,就得要将序列化模式设为“单向Unidirectional”;

l 为了控制并发共享冲突,要手去设置主键(一般是主键)的“时间戳”属性(对应的Attribute是IsVersion),而且对于非int型的字段,这里似乎还有Bug。在默认情况下,各个实体的各个与数据表字段对应的属性的时间戳均false,也就是使用并发控制。


EF 1.0:

l 子实体TDetail中没有MasterKey属性;

l 在两个实体的设计图中均显示出了导航属性,在代码中同样也存在的;

l 和LINQ to SQL一样导航属性、在ObjectContext中的属性均以单数形式显示,但前者可以在设计器中设置,后者则没有地方可以更改;

l 各个属性的“并发模式”默认为None,即不参与并发控制。


EF 2.0:

l 子实体TDetail中有MasterKey属性,改回和LINQ to SQL一样了;

l 在两个实体的设计图中均显示出了导航属性,在代码中同样也存在的;

l 导航属性、在ObjectContext中的属性均可选择以单数、复数的形式显示,建议使用复数形式;

l 各个属性的“并发模式”默认为None,即不参与并发控制;

l 整个ObjectContext增加了一个Lazy Loading Enabled,且默认为true,这个属性和LINQ to SQL中的延迟加载类似,并且下文会提到要注意的地方。

Silverlight + WCF + Entity更新数据库的方式一般是“调用WCF的GetXXX()方法将Entity传送至Silverlight端,在界面上将Entity修改之后,调用WCF的UpdateXXX()方法将Entity传回至WCF端进行操作”。在这种模式中,对于Silverlight端而言,Entity完全是离线的,和服务端没有关系,和数据库中的数据更是没有关系(因为要访问数据库就得先通过WCF),对于WCF端而言,Entity是以“Detached”的形态被传送回来的,处于一种未知的状态,只有先将其Attach,管理其状态,然后才有可能将其最新的值更新至数据库。


而对于“Update更新”一词,本来就是应当针对是数据的“新版本”与“旧版本”才有所谓更新的,那么如何产生两个版本?

l 一次性从WCF端传送两份相同的数据至Silverlight端,一个数据用于被更改,另一个则用于表示“原版本”;

l WCF端只传送一份数据至Silverlight端,Silverlight端自行复制一份,用作“原版本”;

l WCF端只传送一份数据至Silverlight端,Silverlight端在对该数据更改后传送回WCF端,WCF端再从数据库取出最新版本的数据,用作“原版本”。

显然第一种方式增加了网络传输量,并且在Silverlight端对实体进行复制一般也不会占用多少CPU资源,第一种方式应当不可取。而针对后二者的情况展开来就成了对并发控制的讨论了,个人偏好最后一种方法,但是第二种情况有时也会用得到,比如要在Silverlight端进行当前版本与原始版本的比较并显示出不同的地方。

前面说了这么多,这里才开始入正题。先看看Silverlight中的界面,很简单,主实体使用几个TextBox来呈现,子实体使用DataForm呈现:



并针对两种更新方式,提供了“以当前版本进行更新”的单实体方式、“先复制原始版本,再以两个版本进行更新”的双实体方式。

由于本文仅是演示在更新数据库子表(即子实体)上的代码,为了简单起见,直接在WCF服务中对实体进行操作,但是建议在实际业务应用上,应再增加一层业务class,在业务class中操作实体,WCF服务实例化业务class,并将实体作为参数传递给业务class。

更新子实体的关键在于如何让DataContext/ObjectContext知道哪些子实体是新增的、哪些是删除的、哪些是更改的,而这一步,有朋友说在LINQ to SQL中DataContext能自动进行判断,不过我试了好久都没有试成功,能自动判断固然好,但是手工进行干预也未尝不是好事。


先看看 LINQ to SQL:

[OperationContract]
public Models.TMaster GetEntity(string masterKey)
{
using (Models.TestDataClassesDataContext db = new Models.TestDataClassesDataContext())
{
db.DeferredLoadingEnabled = false;
System.Data.Linq.DataLoadOptions option = new System.Data.Linq.DataLoadOptions();
option.LoadWith<Models.TMaster>(m => m.TDetail);
db.LoadOptions = option;
return db.TMaster.FirstOrDefault(m => m.MasterKey == masterKey);
}
}

[OperationContract]
public int UpdateWithTwoEntities(Models.TMaster current, Models.TMaster original)
{
using (Models.TestDataClassesDataContext db = new Models.TestDataClassesDataContext())
{
//db.DeferredLoadingEnabled = false;
db.TMaster.Attach(current, original);
this.ApplyCurrentAndOriginalValues(db, current , original);
db.SubmitChanges();
return 1;
}
}
/// <summary>
/// 将主表标识为已更改,同时判断子表的新增、删除、更改。
/// 在CSDN论坛上,有朋友说只需要一步db.TMaster.Attach(current, original);就可以完成
/// 对子表的更新,可是试了好久好久都没有试出来,不管如何,就算真的可以由DataContext
/// 自己完成对子表的新增、删除、更改的判断,知道要如何手工的去判断也未尝不是件好事。
/// </summary>
/// <param name="db"></param>
/// <param name="current"></param>
/// <param name="original"></param>
void ApplyCurrentAndOriginalValues(Models.TestDataClassesDataContext db,
Models.TMaster current, Models.TMaster original)
{
// 已被删除的子表数据
var deleteds = original.TDetail.Where(ori => !current.TDetail
.Any(d => d.DetailKey == ori.DetailKey && d.MasterKey == ori.MasterKey));
db.TDetail.AttachAll(deleteds);
db.TDetail.DeleteAllOnSubmit(deleteds);

// 新增加的子表数据
var inserteds = current.TDetail.Where(cur => !original.TDetail
.Any(d => d.DetailKey == cur.DetailKey && d.MasterKey == cur.MasterKey));
db.TDetail.InsertAllOnSubmit(inserteds);

// 要更新的子表数据
var updateds = current.TDetail.Where(cur => original.TDetail
.Any(d => d.DetailKey == cur.DetailKey && d.MasterKey == cur.MasterKey));
foreach (Models.TDetail upd in updateds)
{
db.TDetail.Attach(upd, original.TDetail.First(d =>
d.DetailKey == upd.DetailKey && d.MasterKey == upd.MasterKey));
}
}

[OperationContract]
public int UpdateWithCurrentEntity(Models.TMaster current)
{
// 在 LINQ to SQL 中,没有 DataContext.Detach() 方法,要将一个已附加的实体 Detach,
// 就只有序列化后再反序列化(即Clone)得到新的实体。
// 不过,还有一种方法可以“绕道”,那就是在另一个不同的 DataContext 中去得到实体
return this.UpdateWithTwoEntities(current, this.GetEntity(current.MasterKey));
}


注意:
l 在 LINQ to SQL 中,没有 DataContext.Detach() 方法,要将一个已附加的实体 Detach,就只有序列化后再反序列化(即Clone)得到新的实体。不过,还有一种方法可以“绕道”,那就是在另一个不同的 DataContext 中去得到实体;
l Attach(entity,bool) 要求 entity 有时间戳。


在这里,看看默认情况下LINQ to SQL的并发控制所产生的SQL,以对子实体TDetail进行Update为例,打开SQL Server Profile,看到运行的SQL如下:

exec sp_executesql N'UPDATE [dbo].[TDetail]
SET [DetailName] = @p6
WHERE ([DetailKey] = @p0) AND ([MasterKey] = @p1) AND ([DetailName] = @p2) AND ([Fa] = @p3) AND ([Fb] = @p4) AND ([Fc] = @p5)'
,N'@p0 varchar(4),@p1 varchar(4),@p2 varchar(4),@p3 int,@p4 varchar(4),@p5 datetime,@p6 varchar(4)'
,@p0='D001',@p1='Key1',@p2='DN A',@p3=100,@p4='Fb 1',@p5='2010-08-08 00:00:00:000',@p6='DN B'

只是Update了一个DetailName字段,就要把全部的字段都作为Where条件,这是开放式并发的SQL。

修改子实体TDetail的主键DetailKey的时间戳为True,

修改后“自动生成的值”会自动变为True,由于是Varchar型,不是Int Identity,所以把它改回False:

然后再运行测试,得出的SQL为:

exec sp_executesql N'UPDATE [dbo].[TDetail]
SET [DetailName] = @p2
WHERE ([DetailKey] = @p0) AND ([DetailKey] = @p1)'
,N'@p0 varchar(4),@p1 varchar(4),@p2 varchar(4)',@p0='D001',@p1='D001',@p2='DN C'

仅有主键字段使用了开放式并发控制。

不过这里似乎有一个Bug,也可能是我设置得不对,在对TDetail进行插入时,虽然“自动生成的值”已经设为False,可是居然还是尝试去自动生成值,并出现异常


...全文
4368 76 打赏 收藏 转发到动态 举报
写回复
用AI写文章
76 条回复
切换为时间正序
请发表友善的回复…
发表回复
不啦草 2012-07-18
  • 打赏
  • 举报
回复
好东西 顶顶顶顶顶顶顶顶
night_edge 2011-09-01
  • 打赏
  • 举报
回复
看不懂啊,mark一下 等学到了 来看看 谢谢分享
chengin 2011-08-23
  • 打赏
  • 举报
回复
怎么使用ADO.NET Entity通过WCF对数据库多表查询啊?
royalsily 2010-11-11
  • 打赏
  • 举报
回复
写得真好~~ 谢谢~~
sansan6188 2010-10-21
  • 打赏
  • 举报
回复
谢谢 学习了,很有参考价值
a48775566 2010-09-20
  • 打赏
  • 举报
回复
感谢楼主分享..!
rainyhao 2010-06-01
  • 打赏
  • 举报
回复
挺的挺好的
huihuipeng 2010-05-18
  • 打赏
  • 举报
回复
值得学习的文章……
websco 2010-05-14
  • 打赏
  • 举报
回复

using (DbEntities db = new DbEntities())
{
var objs = db.TMaster;
var objs1 = objs.Where(c => c.MasterKey == "en");
var objs2 = objs1.Where(c => c.MasterName.StartsWith("E"));
objs2.ToArray();

db.TMaster.Where(c => c.MasterKey == "en").Where(c => c.MasterName.StartsWith("E")).ToArray();

db.TMaster.Where(c => c.MasterKey == "en" && c.MasterName.StartsWith("E")).ToArray();
}

经测试,前两种方法最终自动转化为最后一种写法:
1. 像 LINQ to SQL一样,延迟加载起了作用,只有调用 ToArray() 或 foreach 等操作时才执行Sql;
2. 很聪明的, Where().Where() 并没有生成嵌套的 Sql,而且直接把两个Where()中的条件合并到了一起。
websco 2010-05-14
  • 打赏
  • 举报
回复
[Quote=引用 63 楼 lkj7b226 的回复:]
问下楼主entityframework的动态条件查询
若有这样几个参数 A,B,C,D,E,....根据这个参数从实体 myEntity中查询数据:
var info=ObjectContext.myEntity;
if(A!=nul)
info.where(a=>a.name=A);
if(B!=null)
info.where(a=>a.age=b)
if(C!=null……
[/Quote]

楼主是想构建动态 SQL 吧,我的EF查询都是用得很简单的,动态SQL都是直接通过SQL解决的,因为我并不通过EF进行Search操作,Search操作还是由SQL来完成的。

有时间可以测试一下,当然这要借助于 SQL Profiler 工具了,
测试的一个要点是
db.ContextOptions.LazyLoadingEnabled = false / true ; 时有没有什么区别。

不过个人先臆断一下,像你这样写,如果A、B、C三个条件都成立,那么最终生成的SQL可能就是三个嵌套的查询了,而不是简单的一个Where语句,这可能是会极大的影响查询效率的。
lkj7b226 2010-05-14
  • 打赏
  • 举报
回复
问下楼主entityframework的动态条件查询
若有这样几个参数 A,B,C,D,E,....根据这个参数从实体 myEntity中查询数据:
var info=ObjectContext.myEntity;
if(A!=nul)
info.where(a=>a.name=A);
if(B!=null)
info.where(a=>a.age=b)
if(C!=null)
info.where(a=>a.city=C)
.
.
.
.

这种方式是不是每次where都查询一次数据库呢?还是将where都判断完后才查询数据库?
大聪 2010-05-13
  • 打赏
  • 举报
回复
我是这样增加子表数据的
dataModel.LC = txtlc.Text;
dataModel.LD = txtld.Text;
dataModel.SKFJ = txtskfj.Value;
string room_type_id= "002";
ROOM_TYPE room_type = _context.ROOM_TYPE.Where(p => p.ID == room_type_id).FirstOrDefault();
//父表增加
room_type.ROOM.Add(dataModel);

_context.AddToROOM(dataModel);
_context.SaveChanges();
Joetao 2010-05-10
  • 打赏
  • 举报
回复
很好的文章,留一个记号!学习下!
blackant2 2010-05-05
  • 打赏
  • 举报
回复
学习了
websco 2010-04-29
  • 打赏
  • 举报
回复
[Quote=引用 58 楼 lei7993 的回复:]
楼主,我目前项目用的是Silverlight4 + WCF RIA Service,现在遇到了一个问题,我现在数据库中的某些表在ADD、Update、Delete时会自动触发服务端的一个方法,我想通过这个方法去刷新Silverlight客户端的数据,但现在我不知道如何在RIA Service中实现回调,或者采用其它机制也可以,还望楼主指点一二,谢谢啦
[/Quote]

我才刚刚开始从 WCF RIA Services 的 Walkthrough 学习起,要学习的还很多很多,目前还帮不了你哦~
lei7993 2010-04-29
  • 打赏
  • 举报
回复
楼主,我目前项目用的是Silverlight4 + WCF RIA Service,现在遇到了一个问题,我现在数据库中的某些表在ADD、Update、Delete时会自动触发服务端的一个方法,我想通过这个方法去刷新Silverlight客户端的数据,但现在我不知道如何在RIA Service中实现回调,或者采用其它机制也可以,还望楼主指点一二,谢谢啦
mingtian_2011 2010-04-28
  • 打赏
  • 举报
回复
这个好……
yimeng19881114 2010-04-28
  • 打赏
  • 举报
回复
受益匪浅
caotoulei 2010-04-27
  • 打赏
  • 举报
回复
这个是好文章,留名。
mane_yao 2010-04-27
  • 打赏
  • 举报
回复
不错,学习
加载更多回复(53)

8,735

社区成员

发帖
与我相关
我的任务
社区描述
WPF/Silverlight相关讨论
社区管理员
  • WPF/Silverlight社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧