【问题标题】:How do I update with a newly-created detached entity using NHibernate?如何使用 NHibernate 更新新创建的分离实体?
【发布时间】:2011-01-29 04:18:44
【问题描述】:

说明:

假设我有一个嵌套了好几层的对象图,每个实体之间都有双向关系。

A -> B -> C -> D -> E

或者换句话说,A 有一个B 的集合,B 有一个对A 的引用,而B 有一个C 的集合,C 有一个返回的引用到B等...

现在假设我想为C 的实例编辑一些数据。在 Winforms 中,我会使用这样的东西:

var instanceOfC;

using (var session = SessionFactory.OpenSession())
{
    // get the instance of C with Id = 3
    instanceOfC = session.Linq<C>().Where(x => x.Id == 3);
}

SendToUIAndLetUserUpdateData(instanceOfC);

using (var session = SessionFactory.OpenSession())
{
    // re-attach the detached entity and update it
    session.Update(instanceOfC);
}

简单来说,我们从数据库中抓取一个持久实例,将其分离,将其提供给 UI 层进行编辑,然后重新附加并将其保存回数据库。

问题:

这适用于 Winform 应用程序,因为我们始终使用相同的实体,唯一的区别是它从持久化到分离再到持久化。

问题是,现在我正在使用 Web 服务和浏览器,通过 JSON 数据发送。实体被序列化为字符串,并反序列化为 new 实体。它不再是一个分离的实体,而是一个临时实体,恰好与持久实体(和更新的字段)具有相同的 ID。如果我使用此实体进行更新,它将消除与BD 的关系,因为它们不存在于这个新的临时实体中。

问题:

我的问题是,我如何通过网络将分离的实体序列化到客户端,接收它们并保存它们,同时保留我没有明确更改的任何关系?我知道ISession.SaveOrUpdateCopyISession.Merge()(他们似乎做同样的事情?),但如果我没有明确设置它们,这仍然会消除关系。我可以将临时实体中的字段一个一个地复制到持久实体中,但这在关系方面效果不佳,我必须手动处理版本比较。

【问题讨论】:

  • 我还想补充一点,我无法在网上找到任何涵盖此问题的资源。我在分离实体上找到的所有 NHibernate 指南都将其作为您保留在一个应用程序范围内的参考,或者将其作为与任何其他实体没有关系的单个实体来处理。
  • 嗨,你有没有得到这个问题的答案?
  • 嗨 CraftyFella,我刚刚发布了一个答案,详细说明了我在案例中所做的事情。
  • merge 和 saveorupdatecopy 做同样的事情,但是 saveorupdatecopy 不再使用,merge 是 api 标准向前发展。
  • 根据我的经验,Merge() 在取消关系方面存在同样的问题。

标签: web-services nhibernate serialization session-state entity-relationship


【解决方案1】:

我通过使用一个中间类来保存来自 Web 服务的数据,然后将其属性复制到数据库实体来解决这个问题。例如,假设我有两个这样的实体:

实体类

public class Album
{
    public virtual int Id { get; set; }
    public virtual ICollection Photos { get; set; }
}

public class Photo
{
    public virtual int Id { get; set; }
    public virtual Album Album { get; set; }
    public virtual string Name { get; set; }
    public virtual string PathToFile { get; set; }
}

Album 包含一个Photo 对象的集合,而Photo 有一个指向它所在的Album 的引用,所以这是一个双向关系。然后我创建一个PhotoDTO 类:

DTO 类

public class PhotoDTO
{
    public virtual int Id { get; set; }
    public virtual int AlbumId { get; set; }
    public virtual string Name { get; set; }
    // note that the DTO does not have a PathToFile property
}

现在假设我在数据库中存储了以下Photo

服务器数据

new Photo
{
    Id = 15,
    Name = "Fluffy Kittens",
    Album = Session.Load<Album>(3)
};

客户现在想要更新照片的名称。他们通过以下 JSON 向服务器发送:

客户数据

输入http://server/photos/15

{
    "id": 15,
    "albumid": 3,
    "name": "Angry Kittens"
}

然后服务器将 JSON 反序列化为 PhotoDTO 对象。在服务器端,我们像这样更新Photo

服务器代码

var photoDTO = DeserializeJson();
var photoDB = Session.Load(photoDTO.Id); // or use the ID in the URL

// copy the properties from photoDTO to photoDB
photoDB.Name = photoDTO.Name;
photoDB.Album = Session.Load<Album>(photoDTO.AlbumId);

Session.Flush(); // save the changes to the DB

说明

这是我找到的最佳解决方案,因为:

  1. 您可以选择允许客户端修改的属性。例如,PhotoDTO 没有 PathToFile 属性,因此客户端永远无法修改它。

  2. 您还可以选择是否更新属性。例如,如果客户端没有通过 AlbumId 发送,它将为 0。如果 ID 为 0,您可以检查并不要更改 Album。同样,如果用户没有通过Name,您可以选择不更新该属性。

  3. 您不必担心实体的生命周期,因为它始终会在单个会话的范围内被检索和更新。

自动映射器

我建议使用AutoMapper 自动将属性从 DTO 复制到实体,尤其是当您的实体有很多属性时。它省去了你必须手动编写每个属性的麻烦,并且具有很多可配置性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-17
    • 2012-03-30
    • 1970-01-01
    相关资源
    最近更新 更多