How to fetch new (EntityState.New) entities from server with breezejs

霸气de小男生 提交于 2019-12-13 04:36:47

问题


I have a web services that returns some data, which is then transformed into new entities on the server and then passed to the client, where user can edit them. If he chooses to SaveChanges, the entities should be submitted to the server and inserted into database. I have two problems:

  • When entities are returned to client, Breeze marks them as EntityState.New
  • Breeze expects, that unchanged entities have primary key set. Because none of the returned new entities have the key set (the key value of type Int32 equals to 0), Breeze thinks, that the server returned multiple instances of the same entity

To demonstrate the problem, change the ToDosController.ToDos method in AngularJS sample to match the following:

[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"},
    };
}

When you run the sample, the HTML page will shows two lines, both will have description "Second item". If I explicitly set the Ids of those items on the server (which I do not want to do, because the keys are generated by database), the problem is not manifested.

The question: how to correctly return entities from the server, so that they will be marked as EntityState.New and they will be saved into database (with generated keys) when SaveChanges is called.

I would expect some MergeStrategy on the client or some extra data/attribute on the server to achieve this, but was unable to find one.

UPDATE:

To clarify:

I am try to support a scenario when users selects and edits one of the entities which originate from some other source, that should be later added to my database.

In details:

  1. Client calls the server with search criteria as method parameters. Server method returns IEnumerable<Customer> - it does not return IQueriable<Customer>

  2. Server queries a backend web service (CRM system) and transforms the result of backend web service into Customer entities. The result is returned to the client. The backend web service is not available to the client.

  3. Client displays the result to the user

  4. User selects one of the entities and edit its properties (such as change customer name or address)

  5. The selected customer is added to entity manager on the client. It should be in the EntityState.Added

  6. em.SaveChanges is called, which submits the Added entity to the server

  7. Server inserts the new customer into database, database generates new primary key, which is returned to the client, where EM updates the entity key and set entity state to EntityState.Unchanged

Maybe I need detached entities and the right question is: "How to return entities to the client without adding them to entity manager?" (they will be added in step 5 above).

P.S: One solution would be to use a custom, non-entity datatype (such as CustomerFromCRM) as result of my server method. I would then transform them to entity Customer on the client side. But I would like to avoid creating additional classes.

UPDATE2: I have found a similar question (with no accepted answere) here: is there an easy way to mark an entity in the cache as "added"?


回答1:


There is not such thing as EntityState.New. When you create a new entity on the client and add it to the EM, EntityState will bet set to Added. Once you call em.SaveChanges, the new entity will be saved in the DB and its EntityState will be updated to Unchanged. If you query data, the server will return entities with EntityState.Unchanged. If you make changes to any of these entities, the EntityState will be set to Modified (if updated) or Deleted (if you call EntityAspect.setDeleted).

In your snippet, you are simply returning 2 objects. They have not been saved to the database, so no key values have been already set.

It's unclear on your question what exactly you are trying to do...

  • Are you trying to have some initial data to work with? If so, you should seed your DB instead.
  • Are you just trying to create new entities? If so, why are you doing it on the server? You should do it on the client.

i.e.

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

Edit:

Your scenario is still unclear to me, but here's a solution to reach your goal:

1- Be sure to manually add a temp Id to your entities (the temp ids must be unique):

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

2- You will notice that these entities will have EntityState.Unchanged on the client, so you detach them:

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

3- Now you can manipulate them as you wish and, if you decide to save them, you add them to the manager before making the saveChanges call:

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



回答2:


I think I get it. The external service provides data for potential new entities. The obvious thing is to send that data as some type other than Customer, perhaps an anonymous type; its interesting that you go to the trouble on the server of making Customer objects; why bother?

Anyway, you take these non customer data and create new Customers on the client in the manner you proposed.

If your heart is set on using the Customer type on the server, you can write a custom JasonResultsAdapter that tells breeze not to cache the data when queried (clear the $type on each node). Use this adapter for this query only!

But first I'd reconsider why your sending them as Customers in the first place because they really aren't.




回答3:


To answer my own questiosn:

I was able to get this working by modifying breeze source code in a way, that it does NOT attach entities to EntityManager if specified flag is set. I could define custom MergeStreategy, but since I didn't want to mess with enums in breeze code, I've defined custom filed FetchAsDetached on an object returned from my custom implementation of JsonResultAdapter.visitNode method.

I had to change the mergeEntity function in the following way:

Change line 13334 from:

if (targetEntity)  {

to

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

Add additional condition around line 13380, to skip over attaching the entities if necessary:

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 });
}

This enables me to fetch the potential new entities from server in detached state. Later, I was able to add them to were EntityManager and they wre correctly inserte dinto the databaes as new records.

This also solves the problem with breeze treating two different items (both with non-initialized key) as the same one, becaue it expects' that they have existing unique keys.



来源:https://stackoverflow.com/questions/18515136/how-to-fetch-new-entitystate-new-entities-from-server-with-breezejs

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!