Update entity with relations.

13 posts, 0 answers
  1. Jose Mejia
    Jose  Mejia avatar
    107 posts
    Member since:
    May 2009

    Posted 25 Mar 2014 Link to this post

    Hi.

    I have many-to-many relation between two tables. Say Products and Details. And from db point of view there is a table Detail_Product to support many-to-many.
    I need to update relateions, say remove some detail from product. But I can't do that using  detach/attach.
    Suppose, I  have code:
    Product prod;
     using(var ct = new CascadeTest.CascadeTest(t2))
         {
     
              var tp = ct.Products.First();
              prod = ct.CreateDetachedCopy(tp, tp1 => tp1.Details);
     
          }
     
    using(var ct = new CascadeTest.CascadeTest(t2))
      {
              var det = prod.Details.First();
              prod.Details.Remove(det);
              prod.Description = "test update";
              det.Product.Remove(prod);
              
             
    ct.AttachCopy(det);
              ct.AttachCopy(prod);
              ct.SaveChanges();
       }


    After this code executed, I hoped to see in Detail_Product table (many-to-many table)  minus  one record. But the only thing which is obviously updated is Description  field.

    The question is how correctly update relations?

     My desired scenario is: I deserialize some entity with lots of modified relations from client (web client), and use the code like above. But it doesn't work...
    I supposed that OA runtime could auomatically resolve all changes, but it seems that with this approach  I can only update entity fields, but not relations (adding/removing).

    It also should be fast without unneeded queries to db...

    I hope for your advice.

    Thanks in advance.
  2. Jose Mejia
    Jose  Mejia avatar
    107 posts
    Member since:
    May 2009

    Posted 28 Mar 2014 Link to this post

    Anyone?
  3. DevCraft banner
  4. Viktor Zhivkov
    Admin
    Viktor Zhivkov avatar
    291 posts

    Posted 28 Mar 2014 Link to this post

    Hello Jose,

    Attaching object graphs to a data context is a complex task that has to take into account of many different scenarios and sometimes use certain assumptions. 
    We have designed this method to have non-destructive behavior which means that if you have removed a link between two objects in the attached object graph we will not remove the objects or the join table records in your data.
    In certain scenarios such as yours this is not helpful, but the behavior can be easily augmented by writing a few lines of code that implement your specific needs.
    My suggestion to you is to implement a function that matches the content of the detached graph with the graph after the attachment and perform any necessary delete (or unlink) and create operations manipulating the attached entity instances.

    Regards,
    Viktor Zhivkov
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  5. Jose Mejia
    Jose  Mejia avatar
    107 posts
    Member since:
    May 2009

    Posted 31 Mar 2014 in reply to Viktor Zhivkov Link to this post

    Thank you, Viktor for reply!

    One more question: am I right that in all cases I have to access db at least once, to get real db entity to compare with?

    I'm not quite understood your explanations in last paragraph related to detached/attached instances...

    I have deserialized entity from client. It obviously detached, so for now I should simply copy all the fields to the
    attached entity (requested from db) and compare  and update relations?

  6. Jose Mejia
    Jose  Mejia avatar
    107 posts
    Member since:
    May 2009

    Posted 03 Apr 2014 Link to this post

    One more question.

    I have following code:

    public int UpdateEntity(Entity entity, bool useGeo)
           {
               using(var model = new ArchivariusModel(ArchivariusModel.ArchConnectionString))
               {
                   var dbEntity = model.Entities.Single(t => t.EntityId == entity.EntityId);
     
              
                   if (dbEntity.EntityName != entity.EntityName)
                       dbEntity.EntityName = entity.EntityName;
     
                   if (dbEntity.Description != entity.Description)
                       dbEntity.Description = entity.Description;
     
                   if (dbEntity.StartTime != entity.StartTime)
                       dbEntity.StartTime = entity.StartTime;
     
                   if (dbEntity.EndTime != entity.EndTime)
                       dbEntity.EndTime = entity.EndTime;
     
                   if (dbEntity.EntityTypeId != entity.EntityTypeId)
                   {
                       dbEntity.EntityType = model.EntityTypes.Single(t => t.EntityTypeId == entity.EntityTypeId);
                   }
              
                   var currentRoles = dbEntity.EntityRoles.ToArray();
                   var updatedRoles = entity.EntityRoles;
     
                   var rolesToDeleteId = currentRoles.Except(updatedRoles).ToArray();
                   foreach (var entityRole in rolesToDeleteId)
                   {
                       dbEntity.EntityRoles.Remove(entityRole);
                   }
                   foreach (var updatedRole in updatedRoles)
                   {
                       if (!dbEntity.EntityRoles.Contains(updatedRole))
                       {
                           dbEntity.EntityRoles.Add(model.EntityRoles.Single(t=>t.EntityRoleId==updatedRole.EntityRoleId));
                       }
                   }
     
                model.SaveGeoInfo();
               }
               return entity.EntityId;
           }


    Entity parameter  is deserialized from json. So it is not correct OA entity like dbEntity because it loses all back references, so it is not valid OA entity. But the question is that in code above I have to request db for every updated referenced property , can it be avoided?
    I can't simply copy/add updateRoles because the are not managed by OA runtime and I got exception...

    Have you got any suggestions about this case?

    Thanks in advance!

  7. Viktor Zhivkov
    Admin
    Viktor Zhivkov avatar
    291 posts

    Posted 03 Apr 2014 Link to this post

    Hi Jose,

    The approach you have chosen is in the right direction.
    I have few suggestion for your implementation:
    • When adding and deleting related entityRoles use the database context directly and set their foreign key in order to link them to the entity object.
    • Create a copy of the original deserialized collection of entityRoles as you do for currentRoles and then .Clear() the collection on the entity object. After that you should be able to call context.Attach(entity) and skip manual assignment of property values.
    If you need any further assistance do not hesitate to contact us with your questions.

    Regards,
    Viktor Zhivkov
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  8. Jose Mejia
    Jose  Mejia avatar
    107 posts
    Member since:
    May 2009

    Posted 07 Apr 2014 in reply to Viktor Zhivkov Link to this post

    Hi, Viktor!

    Thank you so much for your time and patience!  I'm still very confuse about my implementation and your suggestions.
    I try to have as few db queries as possible. Ok, I understood that for relation updates I have to query database. It is unavoidable, because I have complex relations with backreferences (many-to-many) and serialization can't handle it.

    But your suggestion with attaching entity is confusing me. I'm sure that for mature ORM there should be possibilities to simply update  object with newest data -- request from db and attach deserialized object. But it doesn't work with relations. When I clear or my relations and attach object, then all relations from dbEntity are cleared too. And all changes to my relations are lost.

    Put is simply, how can I preserve my relation changes when I attach new entity to update fields? Should I use 2 models and so 2 separete queries for db entity?

    Thanks in advance!



  9. Jose Mejia
    Jose  Mejia avatar
    107 posts
    Member since:
    May 2009

    Posted 08 Apr 2014 Link to this post

    Some additions. Because of a cascading relations it is impossible to me when attaching new entity to loose them. Because in this case instead of update I get some other objects to be deleted.
  10. Viktor Zhivkov
    Admin
    Viktor Zhivkov avatar
    291 posts

    Posted 10 Apr 2014 Link to this post

    Hello Jose,

    We have release a new official build of Data Access (Q1 2014 SP1) that contains a few fixes for attach/detach scenarios. (Release notes)
    Can you please update your version either by download the installer or by installing Telerik Data Access NuGet packages from www.nuget.org and try again your original code to see if our fixes have improved the situation?
    With the latest changes of attach/detach functionality no deletes will be performed in related join tables when you are attaching a graph of interconnected objects so you will need to implement un-linking manually if your scenario requires that.

    If you are using Cascade Delete rule on some of you associations you should be careful because the rule can delete objects that Data Access will try to delete after the rule has been executed and this may trigger exceptions and transaction rollback.

    We are looking forward your feedback if the latest release resolves any of the issues you have experienced. Do not hesitate to contact us if it does not.

    Regards,
    Viktor Zhivkov
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  11. Jose Mejia
    Jose  Mejia avatar
    107 posts
    Member since:
    May 2009

    Posted 16 Apr 2014 in reply to Viktor Zhivkov Link to this post

    Hi, Viktor!

    Yes, it seems to be working now as expected, so when I attaching entity with cleared references, no cascading delete operation occures, in other words it doesn't affect relations of dbEntity and simply updates fields .

    Here is code I have now:
    using(var model = new ArchivariusModel(ArchivariusModel.ArchConnectionString))
            {
                var currentDbEntity = model.Entities.Single(t => t.EntityId == updatedEntity.EntityId);
     
                //update reference changes...
                UpdateRoles(model, updatedEntity, currentDbEntity);

               
    updatedEntity.EntityRoles.Clear();
                model.AttachCopy(updatedEntity);
     
                model.SaveGeoInfo();
            }


    where UpdateRoles is :

    private static void UpdateRoles(ArchivariusModel model, Entity entity, Entity dbEntity)
            {
                var updatedRoles = entity.EntityRoles.ToArray();
                var currentRoles = dbEntity.EntityRoles.ToArray();
     
                var rolesToDeleteId = currentRoles.Except(updatedRoles).ToArray();
                foreach (var entityRole in rolesToDeleteId)
                {
                    dbEntity.EntityRoles.Remove(entityRole);
                }
                foreach (var updatedRole in updatedRoles)
                {
                    if (!dbEntity.EntityRoles.Contains(updatedRole,RoleComp))
                    {
                        dbEntity.EntityRoles.Add(
                                model.EntityRoles.Single(t => t.EntityRoleId == updatedRole.EntityRoleId));
                    }
                }
            }

    So, am I rigth that in this situation and similiar situations related to references update it is unavoidable to request database?
    Because of deserialization it doesn't handle back references, say from EntityRole to Entities (entityRole.Entities), right, so in order to handle it properly I have to quesry db... It is very important question for me from performance perspectives, because I'm using Sqlite, so it is not very cool to have db accesses while it could be avoided...

    Nonetheless, thank you so much for your help!




  12. Viktor Zhivkov
    Admin
    Viktor Zhivkov avatar
    291 posts

    Posted 18 Apr 2014 Link to this post

    Hi Jose,

    I am glad that your attach routing is working fine.
    In general having a query to load back-references is the general way to go.
    If you are certain that the object you are about to set as back-reference has it's real identity value (has been persisted to the database and loaded back before you are trying to set it) you can skip loading it from the database and just set the foreign key value instead of the navigation property.
    Feel free to experiment with such approach in your environment and make sure you have (unit/integration) tests that cover all the possible paths in your workflow.

    Regards,
    Viktor Zhivkov
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
  13. Jose Mejia
    Jose  Mejia avatar
    107 posts
    Member since:
    May 2009

    Posted 21 Apr 2014 in reply to Viktor Zhivkov Link to this post

    Hi, Viktor!

    I'm sorry, but one more question.
    You mentioned about foreign key value instead of the navigation property. I have similiar situation with one of the fields, but it works very strange.

    Here is code:
      ...
                    model.AttachCopy(updatedEntity);
                   
                    UpdateType(model, updatedEntity, currentDbEntity);
     
    ...
     
    private static void UpdateType(ArchivariusModel model, Entity updatedEntity, Entity currentDbEntity)
            {
                 
                //temp action
                currentDbEntity.EntityTypeId = 0;
                currentDbEntity.EntityTypeId = updatedEntity.EntityTypeId;
              
       /*
                if (updatedEntity.EntityTypeId != currentDbEntity.EntityType.EntityTypeId)
                {
                    currentDbEntity.EntityType = model.EntityTypes.Single(t => t.EntityTypeId == updatedEntity.EntityTypeId);
                }*/
            }

    Commented code is one that I used before and that was bother me about performance (unneeded request to sqlite db).

    EntityTypeId is a field of entity and also navigational property. When I attach updated object the current EntityTypeId changes to new one, but it doesn't change navigation property and doesn't reflected in db, so no updates were generated. That is why I used db query. Reading your reply  about foreign keys make me think that I'm  doing smth wrong.  I decided to change to  the simple code

    currentDbEntity.EntityTypeId = updatedEntity.EntityTypeId;

    but it doesn't work either. (Note, it is after attach, so EntityTypeId of currentDbEntity is already changed). Then I decided to add transition value/operation (see above):

     currentDbEntity.EntityTypeId = 0;

    And yes, now  it works correctly -- it updates data in db. But it  is very inconvenient  behaviour and strange situation. What I'm doing wrong in this case? I tried to mark this field as dirty but it doesn't help...


    Thanks in advance again.

    PS: Thank you so much for your help and patience.

  14. Viktor Zhivkov
    Admin
    Viktor Zhivkov avatar
    291 posts

    Posted 24 Apr 2014 Link to this post

    Hello Jose,

    The work around with setting an arbitrary value to the foreign key is a safe one and will mark the field as dirty which will results in proper update statement.
    I will have to investigate if the attach has any relation to the behavior that you are observing.
    Otherwise the additional set operation should not be much of concern performance-wise for your application as it will happen only in memory and will not trigger database query.

    If you are not feeling comfortable with this approach please let me know.

    Regards,
    Viktor Zhivkov
    Telerik
     
    OpenAccess ORM is now Telerik Data Access. For more information on the new names, please, check out the Telerik Product Map.
     
Back to Top
DevCraft banner