5

How to create transaction using crm 2011 sdk and XrmServiceContext?

In next example 'new_brand' is some custom entity. I want to create three brands. Third has wrong OwnerID guid. When I call SaveChanges() method, two brands are created and I've got exception. How to rollback creating of first two brands?

Is it possible without using pluggins and workflows?

using (var context = new XrmServiceContext(connection))
{
    SystemUser owner = context.SystemUserSet.FirstOrDefault(s => s.Id == new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"));

    // create 3 brands
    new_brand b1 = new new_brand();
    b1.new_brandidentification = 200;
    b1.new_name = "BRAND 200";
    b1.OwnerId = owner.ToEntityReference();
    context.AddObject(b1);

    new_brand b2 = new new_brand();
    b2.new_brandidentification = 300;
    b2.new_name = "BRAND 300";
    b2.OwnerId = owner.ToEntityReference();
    context.AddObject(b2);

    new_brand b3 = new new_brand();
    b3.new_brandidentification = 400;
    b3.new_name = "BRAND 400";
    b3.OwnerId = new EntityReference(SystemUser.EntityLogicalName, new Guid("00000000-0000-0000-0000-000000000000"));
    context.AddObject(b3);

    context.SaveChanges();
}
shytikov
  • 9,155
  • 8
  • 56
  • 103
lazarus
  • 1,997
  • 3
  • 26
  • 44
  • What exception are you getting? – glosrob Jun 14 '12 at 11:10
  • Good point @glosrob, I presumed that the exception wasn't the question, rather the need to roll back. I suspect that the exception will be because there is no such SystemUser with an id of `new Guid("00000000-0000-0000-0000-000000000000");`. Lazarus - if the exception is resolved is there still a need to know how to roll back? – Greg Owens Jun 14 '12 at 14:49
  • 1
    @Greg: True, but I think this question still holds academic merit, even if the question was answered @[MSDN](http://social.microsoft.com/Forums/en/crmdevelopment/thread/229dec17-6c49-43ad-9751-6ea61e4ecd36). – Peter Majeed Jun 14 '12 at 16:43

3 Answers3

3

Actually this is possible without the use of Plugins.

You can use CRM relationship links to force transactional behaviour:

EntityA primaryEntity = new EntityA() { //initialise me... };
EntityB secondaryEntity = new EntityB() { //initialise me... };

context.AddObject(primaryEntity);
context.AddObject(secondaryEntity);

// This is the key part: explicitly link the two entities
context.AddLink(primaryEntity, 
    new Relationship("relationship_name_here"), secondaryEntity);

// commit changes to CRM
context.SaveChanges();

There are a few drawbacks to this approach:

  • Obviously, a relationship must exist in CRM between the two entities. If no relationship exists you will have to go with a plugin.
  • I personally find the code can become messy if you need to save a large hierarchy of objects all in one go.

An alternative approach might be to consider implementing a command pattern using plugins.

The idea is that you create the CRM objects on the client, serialize them and pass them to CRM via a custom entity. A pre-create plugin is then set-up on this entity to de serialize and create the objects within the plugin transaction scope.

An excellent blog post describing both strategies can be found here: http://crm.davidyack.com/journal/2012/6/26/crm-client-extension-data-access-strategies.html

tswann
  • 131
  • 1
  • 4
2

The only support CRM provides for transactions is within a plugin, and I don't even believe that will help you in this case, since each creation of a brand, will take place in its own transaction.

The easiest way I can think of to implement this sort of logic is adding a new field to new_Brand called new_TransactionGroupId, and use it to delete any records that were created like so:

using (var context = new XrmServiceContext(connection))
{
    SystemUser owner = context.SystemUserSet.FirstOrDefault(s => s.Id == new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"));
    var transactionGroupId = Guid.NewGuid();

    // create 3 brands
    new_brand b1 = new new_brand();
    b1.new_brandidentification = 200;
    b1.new_name = "BRAND 200";
    b1.OwnerId = owner.ToEntityReference();
    b1.new_TransactionGroupId = transactionGroupId ;
    context.AddObject(b1);

    new_brand b2 = new new_brand();
    b2.new_brandidentification = 300;
    b2.new_name = "BRAND 300";
    b2.OwnerId = owner.ToEntityReference();
    b2.new_TransactionGroupId = transactionGroupId ;
    context.AddObject(b2);

    new_brand b3 = new new_brand();
    b3.new_brandidentification = 400;
    b3.new_name = "BRAND 400";
    b3.OwnerId = new EntityReference(SystemUser.EntityLogicalName, new Guid("00000000-0000-0000-0000-000000000000"));
    b3.new_TransactionGroupId = transactionGroupId;
    context.AddObject(b3);

    try{
        context.SaveChanges();
    } catch (Exception ex){
        // Since one brand failed, cleanup all brands
        foreach(brand in context.new_brand.where(b => b.new_TransactionGroupId == transactionGroupId)){
            context.Delete(brand);
        }
    }
}
Daryl
  • 18,592
  • 9
  • 78
  • 145
1

Is it possible without using plugins and workflows?

No I don't believe that it is. Each context.AddObject() is atomic. If you don't want to use plug-ins then all I think you can do is have some sort of clean-up logic that deletes the created records if your conditions are not met.

Greg Owens
  • 3,878
  • 1
  • 18
  • 42
  • Thanks for your answer. Is workflow transactional operation? I think that it doesn't support rollback operation? – lazarus Jun 14 '12 at 12:25
  • @lazarus: I don't believe it is, but would something like the [answer](http://stackoverflow.com/a/5093860/684271) to the question [Call C# Code from Ribbon JScript CRM Online 2011](http://stackoverflow.com/questions/5091565) meet your needs? – Peter Majeed Jun 14 '12 at 16:47