locked
CRM 2011 LINQ query -> modify fields - Causing all record's fields to update RRS feed

  • Question

  • Hi folks,

    I am working with a CRM 2011 install that has some pretty complex data import code. It's almost all ASP.NET. The major problem that it has is that whenever a record is modified programatically (thousands of records are modified every day), it fires all of our "Record fields change" style workflows, even though those fields aren't being touched (well, not intentionally).

    I think I've got a solution to the issue, but I'd love some input on this, as I'm inexperienced with both CRM and with programming. Here is one of my test functions. The first one causes the issue. The second one, which I just made, seems to solve the problem, and no workflows fire when it is run.

    'test sub. finds lead by email address. updates lead's middlename to [current datetime].
    'this one causes the issue described

    Public Sub UpdateTestRecordOld(ByVal inputEmail As String) Dim record = (From s As Lead In _crmDataContext.LeadSet Where s.EMailAddress1.Equals(inputEmail)).FirstOrDefault record.MiddleName = Now().ToString _crmDataContext.UpdateObject(record) _crmDataContext.SaveChanges() End Sub

        'this one gets around the issue by creating a new entity and attaching it, fresh
    Public Sub UpdateTestRecord(ByVal inputEmail As String) Dim record = (From s As Lead In _crmDataContext.LeadSet Where s.EMailAddress1.Equals(inputEmail) Select New With {s.Id, s.EMailAddress1}).FirstOrDefault _crmDataContext.ClearChanges() Dim leado As New Lead leado.Id = record.Id leado.MiddleName = Now().ToString _crmDataContext.Attach(leado) _crmDataContext.UpdateObject(leado) _crmDataContext.SaveChanges() End Sub

    The first one is obviously much simpler, and I'm dreading going through many dozens of subroutines and changing it all, while keeping the logic perfect :). So - is this normal? Is there an easier way?

    Thanks is owed to Pogo69 and his (or her?) blog for showing me these ClearChanges and Attach functions.

    Thanks,

    James

    Tuesday, February 21, 2012 6:49 AM

Answers

All replies

  •         Dim record = (From s As Lead In _crmDataContext.LeadSet Where s.EMailAddress1.Equals(inputEmail)).FirstOrDefault
    
            record.MiddleName = Now().ToString
    
            _crmDataContext.UpdateObject(record)
            _crmDataContext.SaveChanges()

    This one causes ALL fields to trigger Update Workflows because the record you are sending back to the CRM via the UpdateObject method contains ALL fields.  That is; you are first selecting ALL fields, modifying one, then returning ALL fields (due to the fact that there values are already in the object originally returned.

            Dim record = (From s As Lead In _crmDataContext.LeadSet Where s.EMailAddress1.Equals(inputEmail) Select New With {s.Id, s.EMailAddress1}).FirstOrDefault
    
            _crmDataContext.ClearChanges()
    
            Dim leado As New Lead
    
            leado.Id = record.Id
            leado.MiddleName = Now().ToString
    
            _crmDataContext.Attach(leado)
            _crmDataContext.UpdateObject(leado)
            _crmDataContext.SaveChanges()
    

    This updates ONLY the Middle Name, because it is the only field within the Lead object that has been populated (because the Lead object was created from scratch rather than updating the returned version).

    Due to all of the above; yes, this is normal.  Is there an easier way?  I don't know, but you could play with the Context object's MergeOption property (modifies how/when attributes of an entity are tracked), to overcome having to inject all the ClearChanges/Attach code.

    NB: ClearChanges clears ALL changes relating to the Context object's tracking, so it could be somewhat dangerous in a multi-threaded application.  There are alternative methods that allow you to Detach a single object, rather than clearing everything, if this is a concern.

    Thanks is owed to Pogo69 and his (or her?) blog for showing me these ClearChanges and Attach functions.

    Definitely "his"; just checked and can confirm unequivocally.


    --pogo (pat) @ pogo69.wordpress.com

    Wednesday, February 22, 2012 12:29 AM
  • Your first sample should work if you only return the middle name attribute.  Since your LINQ query isn't specifying specific fields you are returning all fields as Pat mentioned.  I would try changing your first LINQ Query to select into a new Lead object with only specifying the leadid and middlename. 

    Check out my blog post on this topic:  http://mscrmdev.blogspot.com/2012/01/crm-2011-linq-all-columns-vs-selected.html


    Blake Scarlavai - http://mscrmdev.blogspot.com/ - Sonoma Partners - http://www.sonomapartners.com/ - Follow @bscarlav

    Wednesday, February 22, 2012 5:23 AM
  • Ah, thanks Blake! This is perfect. I'm not familiar with LINQ or VB.NET, and as such I was just having trouble with the LINQ query syntax. In my second example, I was piping the LINQ query out to an anonymous type variable (I think that's the term?) rather than the early-bound defined 'Lead' entity variable.

    Thanks to your example, I changed "select new with" to "select new Lead with" and now it's how I want it! And actually, in my crude example, I don't even need to specify the middlename or the email address :)

        Public Sub UpdateTestRecord(ByVal inputEmail As String)
    
            Dim record = (From s As Lead In _crmDataContext.LeadSet _
                          Where s.EMailAddress1.Equals(inputEmail) _
                          Select New Lead With {.Id = s.Id}).FirstOrDefault
    
            record.MiddleName = Now().ToString
    
            _crmDataContext.UpdateObject(record)
            _crmDataContext.SaveChanges()
    
        End Sub


    This is totally efficient!

    And thanks for your reply too, Pat. I've got ClearChanges in the error handling code, as I was having trouble where a bad record would stay in the cache after a failed save, and would mess up the rest of the job. The program isn't currently multithreaded, but I'll keep 'Detach' in mind, for maybe using in the future.

    Cheers,

    James


    • Edited by James Hale Thursday, February 23, 2012 2:34 AM
    Thursday, February 23, 2012 2:25 AM
  • Try stick to this approach:
    1) select just the attributes you want to change from context, along with id (trick here, see below)
    2) update that attributes
    3) updateobject()
    4) savechanges()

    at step 1), create the object you're changing with just the attributes you have selected populated.
    ie. 
    var obj = (from o in ctx.object where (...) select new object() { attr1=o.attr1, attr2=o.attr2, id=o.id}).FirstOrDefault();
    obj.attr1 = attr1valueyouwant
    obj.attr2 = attr2valueyouwant
    ctx.updateobject(obj)
    ctx.SaveChanges();

    This way, you don't have to detach the original read object and then create another object, attach it, then update it.
    Tuesday, April 10, 2012 3:29 PM