如何使用breezejs从服务器获取新的(EntityState.New)实体

时间:2013-08-29 15:31:06

标签: breeze

我有一个返回一些数据的Web服务,然后将其转换为服务器上的新实体,然后传递给客户端,用户可以在其中编辑它们。如果他选择SaveChanges,则应将实体提交给服务器并插入数据库。我有两个问题:

  • 当实体返回客户端时,Breeze将其标记为EntityState.New
  • Breeze预计,未更改的实体具有主键集。因为没有返回的新实体具有密钥集(Int32类型的键值等于0),Breeze认为服务器返回了同一实体的多个实例

要演示此问题,请更改AngularJS示例中的ToDosController.ToDos方法以匹配以下内容:

[HttpGet]
public IEnumerable<TodoItem> Todos()
{
    return new TodoItem[]
    {
        // Keys are not set because (equals to 0) these are new entities 
        new TodoItem() { Description="First item"},
        new TodoItem() { Description="Second item"},
    };
}

运行示例时,HTML页面将显示两行,两行都有描述“第二项”。如果我在服务器上显式设置了这些项的ID(我不想这样做,因为密钥是由数据库生成的),问题就不会显现出来了。

问题:如何正确地从服务器返回实体,以便将它们标记为EntityState.New,并在调用SaveChanges时将它们保存到数据库中(使用生成的密钥)。

我希望客户端上有一些MergeStrategy或服务器上有一些额外的数据/属性来实现这一点,但无法找到。

更新

澄清:

我试图支持用户选择和编辑其中一个来自其他来源的实体的情况,这些实体应该稍后添加到我的数据库中。

详情:

  1. 客户端使用搜索条件作为方法参数调用服务器。服务器方法返回IEnumerable<Customer> - 返回IQueriable<Customer>

  2. 服务器查询后端Web服务(CRM系统)并将后端Web服务的结果转换为Customer个实体。结果将返回给客户端。客户端无法使用后端Web服务。

  3. 客户端将结果显示给用户

  4. 用户选择其中一个实体并编辑其属性(例如更改客户名称或地址)

  5. 选定的客户将添加到客户端的实体管理器中。它应该在EntityState.Added

  6. 中 调用
  7. em.SaveChanges,将Add ed实体提交给服务器

  8. 服务器将新客户插入数据库,数据库生成新的主键,返回给客户端,EM更新实体密钥并将实体状态设置为EntityState.Unchanged

  9. 也许我需要分离的实体,正确的问题是:“如何在不将实体添加到实体管理器的情况下将实体返回给客户端?”(它们将在上面的步骤5中添加)。

    P.S: 一种解决方案是使用自定义的非实体数据类型(例如CustomerFromCRM)作为我的服务器方法的结果。然后我会将它们转换为客户端的实体客户。但我想避免创建其他类。

    UPDATE2:我在这里找到了一个类似的问题(没有接受的答复):is there an easy way to mark an entity in the cache as "added"?

3 个答案:

答案 0 :(得分:1)

没有EntityState.New这样的东西。 当您在客户端上创建新实体并将其添加到EM时,EntityState将下注设置为已添加。一旦调用em.SaveChanges,新实体将保存在DB中,其EntityState将更新为Unchanged。 如果查询数据,服务器将返回EntityState.Unchanged的实体。如果您对这些实体中的任何一个进行更改,EntityState将设置为Modified(如果已更新)或Deleted(如果您调用EntityAspect.setDeleted)。

在您的代码段中,您只是返回2个对象。它们尚未保存到数据库中,因此尚未设置任何键值。

目前还不清楚你的问题到底是做什么的......

  • 您是否尝试使用一些初始数据?如果是这样,您应该为您的数据库设定种子。
  • 您是否只是想创建新实体?如果是这样,你为什么要在服务器上这样做?你应该在客户端上做这件事。

var todoItem1 = em.createEntity("TodoItem", { description: "First Item" });
var todoItem2 = em.createEntity("TodoItem", { description: "Second Item" });

修改

我的情况仍然不清楚,但这是一个达到目标的解决方案:

1-确保手动向您的实体添加临时ID(临时ID必须是唯一的):

[HttpGet]
public IEnumerable<TodoItem> Todos() {
   return new TodoItem[] {
      new TodoItem() { Id=-1001, Description="First item"},
      new TodoItem() { Id=-1002, Description="Second item"}
   };
}

2-您会注意到这些实体在客户端上将具有EntityState.Unchanged,因此您将它们分离:

var todoItem1 = data.results[0];
var todoItem2 = data.results[1];
todoItem1.entityAspect.setDetached();
todoItem2.entityAspect.setDetached();

3-现在您可以根据需要操作它们,如果您决定保存它们,则在进行saveChanges调用之前将它们添加到管理器中:

manager.addEntity(todoItem1);
manager.addEntity(todoItem2);
manager.saveChanges();

答案 1 :(得分:1)

我想我明白了。外部服务为潜在新实体提供数据。显而易见的是将该数据作为除Customer以外的某种类型发送,也许是匿名类型;有趣的是你在制作Customer对象的服务器上遇到了麻烦;为什么要这么麻烦?

无论如何,您采用这些非客户数据并以您建议的方式在客户端上创建新客户。

如果您的心脏在服务器上使用Customer类型,则可以编写一个自定义JasonResultsAdapter,告诉breeze在查询时不要缓存数据(清除每个节点上的$ type)。仅将此适配器用于此查询!

但首先我会重新考虑为什么你首先将它们作为客户发送,因为它们确实不是。

答案 2 :(得分:0)

回答我自己的问题:

我能够通过某种方式修改breeze源代码来实现这一点,如果设置了指定的标志,它不会将实体附加到EntityManager。我可以定义自定义MergeStreategy,但由于我不想在breeze代码中弄乱枚举,我已经在从FetchAsDetached的自定义实现返回的对象上定义了自定义归档JsonResultAdapter.visitNode方法

我必须通过以下方式更改mergeEntity功能:

更改第13334行:

if (targetEntity)  {

if (targetEntity && ! (meta.FetchAsDetached == true)) { 

在第13380行附近添加附加条件,以便在必要时跳过附加实体:

if (!(meta.FetchAsDetached  == true)) { // attach only if necessarry
    attachEntityCore(em, targetEntity, EntityState.Unchanged);
    targetEntity.entityAspect.wasLoaded = true;
    em.entityChanged.publish({ entityAction: EntityAction.AttachOnQuery, entity: targetEntity });
}

这使我能够从处于分离状态的服务器获取潜在的新实体。后来,我能够将它们添加到EntityManager,并且它们正确地将数据库作为新记录插入到数据库中。

这也解决了微风处理两个不同的项目(都使用非初始化的密钥)作为同一个项目的问题,因为它期望'他们有现有的唯一密钥。