如何在使用Ado.Net的三层架构中实现延迟加载外键对象

showlin 2010-07-01 10:50:40
这个问题其实困扰很久了,前两天看到论坛上有人在问权限管理系统,就想起来我做权限管理时遇到的这么个问题。

有以下实体:

/// <summary>
/// 用户类
/// </summary>
public class User
{
private IList<Role> roles;

/// <summary>
/// 用户所属的角色集合
/// </summary>
public IList<Role> Roles
{
get
{
return roles;
}
set
{
roles = value;
}
}
}

/// <summary>
/// 角色类
/// </summary>
public class Role
{
private IList<Right> rights;
private IList<User> users;

/// <summary>
/// 该角色拥有的用户集合
/// </summary>
public IList<User> Users
{
get
{
return users;
}
set { users = value; }
}
/// <summary>
/// 该角色拥有的权限集合
/// </summary>
public IList<Right> Rights
{
get
{

return rights;
}
set { rights = value; }
}
}

/// <summary>
/// 权限类
/// </summary>
public class Right
{
private IList<Role> roles;

/// <summary>
/// 拥有该权限的角色集合
/// </summary>
public IList<Role> Roles
{
get
{

return roles;
}
set { roles = value; }
}
}


这样其实
User、Right、Role就是两两Many-Many的关系
访问时
可以通过这样
User.Roles[0].Rights的方式访问用户的第一个角色拥有的所有权限
也可以通过
foreach(Role role in user.Roles)
{
foreach(Right right in role.Rights)
{
yield return right;
}
}
这样的方式获取用户拥有的所有权限集合。
但是,问题来了
user.Roles[0].Rights[0].Roles[0].Users[0].....................
这样的结构也是允许的,就会导致数据访问层做ORM映射的时候,出现循环调用堆栈溢出的情况
获取user的Roles集合时,要实例化每一个Role,而实例化每一个Role的时候,要获取其Users集合,实例化其中每一个User,再获取User的Roles集合......

在例如NHibernate等ORM框架中,延迟加载就没有这种问题
也就是说user的Roles集合并没有一开始就在在数据访问层获取,而是当业务逻辑层或是表示层有需要读取的时候,才去获取Roles集合
这个读取的机制应该是要做在实体层的

最开始的时候,曾设想这样的解决方案


/// <summary>
/// 用户类
/// </summary>
public class User
{
private IList<Role> roles;

/// <summary>
/// 用户所属的角色集合
/// </summary>
public IList<Role> Roles
{
get
{
if (roles==null)
//调用数据访问层获取Roles集合
return roles;
}
set
{
roles = value;
}
}
}


但是这样做明显会出现数据访问层引用实体层,而实体层引用数据访问层这样的循环引用

接着试图使用事件机制来通知数据访问层



public IList<Role> Roles
{
get
{
if (roles == null)
{
if (BeforeReadNullRoles != null)
{
BeforeReadNullRoles(this, EventArgs.Empty);
}
}
return roles;
}
set
{
roles = value;
}
}
/// <summary>
/// 用于在获取Roles集合前,如果Roles集合为空则触发的事件
/// </summary>
public event EventHandler BeforeReadNullRoles;
}


数据访问层采用类似这样的方式来监听事件


if (reader.Read())
{
User user = new User();
user.BeforeReadNullRoles+=new EventHandler(user_BeforeReadNullRoles);
reader.Close();
}

void user_BeforeReadNullRoles(object sender,EventArgs e)
{
User user=sender as User;
user.Roles=new RoleService().GetRolesByUserId(user.Id);//调用数据访问层方法获取用户的角色集合
}


好像能够完成目标了,但是:
当在Asp.Net页面绑定User或IList<User>数据之后,第一次加载页面是可以正常获取user的外键对象的,但是,一旦页面回传后,利用ObjectDataSource绑定的user对象就没有办法获取Roles等外键对象了,跟踪后发现user对象的事件是null,事件没了。。。。。。

我想知道,这类问题该如何处理,使用事件监听机制正确吗?如何解决事件丢失的情况?
NHibernate等ORM是如何实现延迟加载的?
如果没有实现延迟加载如何避免user.Roles[0].User[0].Roles这类循环调用导致堆栈溢出的情况?
如果有可能,请尽量多给些示例代码,也请有过类似实现经验的多给意见。
...全文
287 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
阿非 2010-07-16
  • 打赏
  • 举报
回复
你的例子中没有删除,我无法重现问题。
阿非 2010-07-16
  • 打赏
  • 举报
回复
我看到错误了。

如果你仍然使用 ObjectDataSource 的话,那做如下修改。


public static bool DeleteUser(User user)
{
if (user.Roles == null)
{
user = GetUserByUserId(user.UserId);
}

if (user.Roles.Count > 0)
return true;
else
return false;
}


ObjectDataSource 只指定了删除的方法,没有指定参数。而删除的参数则是由 GridView 提供的

因为在触发 GridView 的相关事件时 (GridView_RowDeleting),

只能获取到GridView设置的DataKeyNames属性 所对应的当前删除项的值 ,

DataKeyNames属性设置的是UserID ,因为删除方法需要的是 User 这个类的实例,

所以会实例化一个 User 对象,并将 当前删除项的DataKeyNames所对应的值 赋值给 UserID

然后才执行 UserService.cs 中的 DeleteUser 方法

showlin 2010-07-16
  • 打赏
  • 举报
回复
新的例子在这

ORMDemo.rar
showlin 2010-07-13
  • 打赏
  • 举报
回复
麻烦你看看那个例子,删除的时候,外键集合和延迟加载的事件都是null了
阿非 2010-07-13
  • 打赏
  • 举报
回复
刚看到你的私信。

你现在遇到什么问题了
阿非 2010-07-02
  • 打赏
  • 举报
回复
哦,我是指gridview和detailview。

objectDatasource 我用的不多,它应该是应用了反射,故应该有比它更好的方案。

其实许多时候没有绝对的正确,取舍是问题,就向在应用viewstate 造成带宽浪费和不应用viewstate

进行数据库查询 或 缓存查询。

你了解应用环境和相关需要就可以了。

showlin 2010-07-02
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 sandy945 的回复:]
...
只是过多的应用了重量级服务器控件,也可能实际中你不会这样,只是便于演示。
[/Quote]
你指的是gridview和detailview加上objectDatasource么?
后台开发的时候是常用的,毕竟快。
如果是网站前台,div布局时,需要前端优化就不可能这样用了,viewstate是个大问题。

或者你指的是EnterPrise企业库?请指教
阿非 2010-07-02
  • 打赏
  • 举报
回复
除了事件这个方式,也可借助接口来实现。

DEMO 我下载了,运行了几次 没想到现在的事件方式有什么不妥。

只是过多的应用了重量级服务器控件,也可能实际中你不会这样,只是便于演示。
showlin 2010-07-01
  • 打赏
  • 举报
回复
奇怪的是,重现不了当时的事件丢失的bug了。。。。。怪
Demo的地址 http://download.csdn.net/source/2506390

NHibernate等ORM是如何实现延迟加载的?
使用事件监听机制正确吗?
showlin 2010-07-01
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 sandy945 的回复:]
你指的是dll的循环引用? 为何domain object 要放在单独的项目中呢?

[/Quote]
习惯上都是这样啊,好像PetShop也是这样干的
阿非 2010-07-01
  • 打赏
  • 举报
回复
你指的是dll的循环引用? 为何domain object 要放在单独的项目中呢?

showlin 2010-07-01
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 sandy945 的回复:]
实体层引用数据访问层 是否会造成循环引用 这个不一定,要看具体代码。按照正常的需求,是不会造成循环引用的。

[/Quote]

数据访问层项目要生成实体,自然要添加实体层项目的引用,而如果实体层如果要直接调用数据访问层的方法,自然会造成循环引用

Demo我得整理一个看看
阿非 2010-07-01
  • 打赏
  • 举报
回复
public IList<Role> Roles
{
get
{
if (roles==null)
//调用数据访问层获取Roles集合
return roles;
}
set
{
roles = value;
}
}


但是这样做明显会出现数据访问层引用实体层,而实体层引用数据访问层这样的循环引用
------------------------------------------------------------------------
实体层引用数据访问层 是否会造成循环引用 这个不一定,要看具体代码。按照正常的需求,是不会造成循环引用的。


跟踪后发现user对象的事件是null,事件没了。。。。。。
--------------------------------------------------
EventHandler 应该是可序列化的,目前不知道没了的原因。


ps:最好提供测试的DEMO,这样有助于更好的解决问题。
showlin 2010-07-01
  • 打赏
  • 举报
回复
问题很长,请耐心慢慢看

题外话:
我最终是回避了延迟加载的,多加了个标记来判断是否获取外键来绕过循环调用的问题的,大概的实现如下:


public User GetUserByUserId(int userId)
{
......
User user=new User();
user.Roles=new RoleService().GetRolesByUserId(user.Id,true);
}

public IList<Role> GetRolesByUserId(int userId,bool noForeignKey)
{
foreach(....)
{
Role role=new Role();
if (!noForeignKey)//如果需要获取外键
{
role.Users=new UserService.GetUsersByRoleId(role.Id,true);
role.Rights=new RightService.GetRightsByRoleId(role.Id,true);
}
}
}

public IList<User> GetUsersByRoleId(int roleId,bool noForeignKey)
{
foreach(....)
{
User user=new User();
if (!noForeignKey)//如果需要获取外键
{
user.Roles=new RoleService().GetRolesByUserId(user.Id,true);
}
}
}


这样的笨办法,避免了循环调用,但外键就只能获取user.Roles这样第一层外键了,一般情况下,足够使用了

lds1ove 2010-07-01
  • 打赏
  • 举报
回复
学习 曾分
showlin 2010-07-01
  • 打赏
  • 举报
回复
是这个问题很无聊么?半天才几个人来。。。
showlin 2010-07-01
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 sandy945 的回复:]


具体问题具体分析,没有通用的。

但不是说放在单独项目中不可以,你要是放在单独项目中,不使用项目引用就不会出现循环引用了。
用引用DLL的方式。更多的……
[/Quote]
实际上这样的,如果把实体层和数据访问层放在一块,那么意味着表示层引用实体对象的时候,同时也会引用到数据访问层,耦合度提高了,而且也不符合规范,单为了不出现循环引用,代价太高了,如果所有都放在表示层项目中,那么什么问题都没有了,不妥。

事件消息通知机制实现延迟加载是我唯一能想到的,Demo放上去了,阿非能有空帮我看看可能会有缺陷否?
阿非 2010-07-01
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 showlin 的回复:]
奇怪的是,重现不了当时的事件丢失的bug了。。。。。怪
Demo的地址 http://download.csdn.net/source/2506390

NHibernate等ORM是如何实现延迟加载的?
使用事件监听机制正确吗?
[/Quote]

现在好用不丢失了是么?
-------------------------

Q:NHibernate等ORM是如何实现延迟加载的?

A:应该是类似

public IList<Role> Roles
{
get
{
if (roles==null)
//调用数据访问层获取Roles集合
return roles;
}
set
{
roles = value;
}
}


这样的方式。

----------------------------------------------------
Q:使用事件监听机制正确吗?

A:可以啊,其实这样更灵活。只是之前你说有事件丢失的情况
阿非 2010-07-01
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 showlin 的回复:]
引用 5 楼 sandy945 的回复:
你指的是dll的循环引用? 为何domain object 要放在单独的项目中呢?


习惯上都是这样啊,好像PetShop也是这样干的
[/Quote]

具体问题具体分析,没有通用的。

但不是说放在单独项目中不可以,你要是放在单独项目中,不使用项目引用就不会出现循环引用了。

用引用DLL的方式。更多的做法是借助第三方项目来进行引用。

daihua_1113 2010-07-01
  • 打赏
  • 举报
回复
看的有点晕 帮忙学习

110,533

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

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