Explaining Data Migration & Data Integration to…The Wife

Data migration and data integration are one of those subject that you either love or hate – the Marmite (Vegimite)  syndrome . I’m one of those people who love it.

Whilst working on a project that required both, I tried to explain the process to my wife – ‘cos she asked. As I went into the intricacies of staging and transformations, scheduled process etc.. I noticed I had lost her at the staging database – the clue was she was on facebook!

So, rewinding, I started again – using CRM, data migration terms and a real world example:

The Objective

We are on a family holiday for 2 weeks, beach , sun, self-catering etc…We take suitcases full of clothes. The usual holiday approach of we take too much, clothes we won’t use, clothes we think we will need and the pair of shorts you live and die in.

When we get home , my daughter and I would like to find our clothes washed , ironed and placed in the correct room, wardrobe, chest of drawer and draw.

The Client.

My daughter and me!.

Disparate Data sources.

On arriving home, neither myself or my daughter have any interest in the “laundry” cycle. We just assume there is a laundry fairy that makes things happen. So, as suitcases get emptied, piles of clothes appear – general in the same location of the opening of the suitcase.

Staging Database.

My wife will collect all the piles of laundry and move these many piles to one big pile in the utility room.

Cleansing & De-Duplication.

From the big pile , she will sort the big pile into various smaller piles based on colour, material etc.. These piles enter the washing machine. The washing cycle takes a few hours and can only take the sorted pile of laundry of the same colour and type.

Scope Creep.

My daughter , in boredom mode from returning from a fun filled holiday, finds the PE bag in the corner of the room with 2 week old kit which she forgot about. This is mainly due to the excitement of the impending holiday. So, she dutifully bring the previously unknown bag of laundry down to the utility and dumps it in the middle of the other segmented piles of laundry.

The usual discussion happens along the lines of “Why didn’t you mention this earlier”, “I didn’t think it would matter”.

The effect of scope creep.

My wife, now has to sort the new bag of laundry and assess whether each item can be added to the existing pile or a new pile should be started. In some instances the new pile is not viable for a full load in the washing machine so she will make the decision to defer that pile for another week.

The net result of the sudden appearance of the PE kit is Dinner will be late “or go and get a take away”. The latter is more expense after a holiday.

Deltas.

The washing ironing cycle will take a few days to complete, largely due to the volume of clothes taken on holiday , bad planning on what was actually needed and even worse planning for the return trip when everything was thrown into the first opened suitcase.

However, during the weekend, we are still wearing clothes and still placing in the linen basket. Yes, we have been trained to place in the appropriate basket now instead of a pile on the floor. My wife periodically will collect the baskets content and move these to the now small piles of laundry in the utility room.

Data Loading.

After a few days of the weekend, the washing , iron cycle is almost complete. Clothes are clean, ironed and have now been placed in the correct wardrobe etc. As part of the housework process, my wife will match socks – so I have a pair! and throw out old socks that are no longer in use.

The sign-off process of the bulk data load.

In this instance, wine. As the holiday bulk washing load has been completed.

Batch Data Integration as a scheduled task.

The daily repetitive weekly cycle of laundry basket to utility pile to washing machine to iron to wardrobe, happens as part of our weekly routine. It just gets done, its so mundane we don’t know we are doing it because it so automated.

On-Demand data integration.

The usual, 6am alarm clock as I get up to visit a client in London. “Do I have a shirt ?”. I won’t complete this discussion….

Summary.

At the end of the “real” world example – it was clear , she had got it.

Posted in Uncategorized | Comments Off

Resuming a “system job”

I have a requirement to force “waiting” system jobs to start and continue working.  My scenario is that I have 200k records in the async queue and its stopped!.

I found that resuming a job allows it to run to completion. However, resuming too many jobs cause the job to fail.

My solution is to write a simple app that searching for “Waiting” jobs and resumes them. Mindful of a limit (to be discovered) that too many will cause the jobs to fail.

My fetch returns all the jobs:

  1.             string fetchXml = @"<fetch mapping='logical'>
  2.                                     <entity name='asyncoperation'>
  3.                                         <attribute name='asyncoperationid'/>
  4.                                         <attribute name='statecode'/>
  5.                                         <attribute name='statuscode'/>
  6.                                         <filter type='and'>
  7.                                             <condition attribute='statecode' operator='eq' value='1'/>
  8.                                             <condition attribute='statuscode' operator='eq' value='10'/>
  9.                                         </filter>
  10.                                     </entity>
  11.                                 </fetch>";

 

The SDK shows statecode =1 is Suspended and statuscode =10 is Waiting

  1. List<EntityCollection> fetchedEntityCollection = curoHelper.FetchRecords(curoTenant, fetchXml);
  2.  
  3. foreach (EntityCollection entitycollection in fetchedEntityCollection)
  4. {
  5.     foreach (Entity entity in entitycollection.Entities)
  6.     {
  7.         waitingJobs.Add(entity);
  8.     }
  9. }

 

My FetchRecords using a parallel paging that will return ALL the pages, in this case about 130000 records in about 20-30 secs.

Now for the resume code. Simply, I need to set the statecode to Ready and the statuscode to Waiting for Resource

  1. /// <summary>
  2.  /// Resume waiting jobs
  3.  /// </summary>
  4.  /// <param name="jobs"></param>
  5.  public void ResumeJobs(List<Entity> jobs)
  6.  {
  7.      // Split job into 100 record blocks
  8.      int blkSize = 100;
  9.  
  10.      // 2 threads
  11.      ParallelOptions ops = new ParallelOptions();
  12.      ops.MaxDegreeOfParallelism = 2;
  13.  
  14.      // Process 2 threads, 100 records per thread
  15.      Parallel.ForEach(Partitioner.Create(0,jobs.Count,blkSize), ops, t =>
  16.      {
  17.  
  18.          // get a block of records
  19.          List<Entity> _jobs = jobs.Skip(t.Item1).Take(blkSize).ToList();
  20.  
  21.          // loop each record
  22.          foreach (Entity job in _jobs)
  23.          {
  24.              try
  25.              {
  26.                  job["statuscode"] = new OptionSetValue(0); // Waiting for resource
  27.                  job["statecode"] = new OptionSetValue(0); //Ready
  28.                  
  29.                  // update
  30.                  curoHelper.CrmHelper.Update(job, throwException);
  31.              }
  32.              catch (Exception ex)
  33.              {
  34.                  writeProgess("F");
  35.              }
  36.          }
  37.  
  38.          writeProgess("*");
  39.          System.Threading.Thread.Sleep(60000); // Pause 1 minute
  40.      });
  41.  }

My resume function works a follows: batch 100 records at a time (across 2 threads), process and wait for minute for the queue to clear. The Parallel function Partitioner is another favourite from the parallel library :

  1. Parallel.ForEach(Partitioner.Create(0, jobs.Count, blkSize), ops, t =>
  2. {

 

and this will get the next block of records:

  1. // get a block of records
  2.  List<Entity> _jobs = jobs.Skip(t.Item1).Take(blkSize).ToList();

 

Running this process also reduces strain on the CRM Server therefore allowing other jobs to be actioned.

Posted in Uncategorized | Comments Off

Invalid User Authorisation

“The user authentication passed to the platform is not valid”

Over the Xmas break I decided to rebuild my lab servers from the ISO upwards. Limited on HW and memory, I planned on the the following config:

Box 1 : AD, SQL, CRM

Box 2: Windows 7 client

Simple?

In my haste, I installed CRM 2011 on the DC using the “Network Service” account. All appeared to be fine until I tried to access the discovery service. OK, a quick repair to a “proper” account failed also.

My only solution was to uninstall CRM and reinstall with a “Domainuser” account.

Lesson learnt : Don’t install CRM 2011 on a DC using “Network Service” as the CRM accounts.

Posted in Uncategorized | Comments Off

A parallel approach to CRM 2011

I’m reading a lot of blog posts where people are starting to use the parallel libraries in CRM development. I’ve been using the C# .NET 4.0 parallel library for a while.

Firstly, the Parallel.Invoke method, This allows the running of processes in parallel. A simple example of this is:

  1. Parallel.Invoke
  2.     (
  3.         () =>
  4.         {
  5.             currencies = pCrmHelper.CrmHelper.CuroFunctions.GetAllCurrencies();
  6.             Console.WriteLine("Currencies fetched");
  7.         },
  8.         () =>
  9.         {
  10.             provider = getProviderFromTenant(CuroTenant.OrgName);
  11.             Console.WriteLine("Providers fetched");
  12.         },
  13.         () =>
  14.         {
  15.             curoHoldings = curoFundActions.GetCuroHoldings();
  16.             Console.WriteLine("Holdings fetched");
  17.         }
  18.     );

 

The above snippet just demonstrates that I want to fetch currencies, providers and holdings at the same time.  The Invoke is completed when the longest running routine is complete. Time saving = “sum of the time to complete the slowest functions”

Secondly, the Parallel.ForEach method, this simply allows the iteration through a collection of objects and runs each on a separate thread.

  1. ParallelOptions ops = new ParallelOptions();
  2. ops.MaxDegreeOfParallelism = 6;
  3.  
  4. Parallel.ForEach(incomeProfiles,ops,incomeProfile =>
  5. {
  6.     utility.ProcessIncomeprofile(incomeProfile, incomeProfile.Id, "jit");
  7. });

 

The above runs through a collection of incomeprofiles and actions each incomeprofile on a separate thread. The current thread can be identified by the

“Thread.CurrentThread.ManagedThreadId”

The Parallel library will manage the threadpool and ensure you will have a new thread, once the thread has been completed its returned to the pool.  In the context of CRM this can be quite useful.

Within the above process I use a CRM library (that connects to CRM). I can either instantiate the library outside the loop – this is not a good idea as it becomes not very thread safe and a bottleneck – invalidating the use of parallelism. My preferred approach is to store instantiated services and index them by the current managed threadid, by doing this I can reuse CRM library services:

This code snippet controls this process:

  1. /// <summary>
  2. /// Collection of started Services for the threads
  3. /// Key ID = OrgName_ThreadId
  4. /// </summary>
  5. private Dictionary<string, CrmHelper> _services = new Dictionary<string, CrmHelper>();
  6.  
  7. /// <summary>
  8. /// Get/Start a crm service from a pooled collection
  9. /// </summary>
  10. /// <param name="ThreadId">Entry to the dictionary format: [OrgName_ThreadId] </param>
  11. /// <param name="CuroTenant"></param>
  12. /// <returns></returns>
  13. public CrmHelper GetCrmService(int ThreadId, string OrgName)
  14. {
  15.     CrmHelper currentService;
  16.  
  17.     // Was a service already created for this thread ID?
  18.     if (_services.TryGetValue(OrgName + "_" + ThreadId, out currentService))
  19.     {
  20.         return currentService;
  21.     }
  22.     else
  23.     {
  24.         // Configure the CRM service
  25.         currentService = new CrmHelper(OrgName);
  26.         currentService.ConnectCrm();
  27.  
  28.         // Dictionary Objects aren't thread safe
  29.         lock (((ICollection)_services).SyncRoot)
  30.         {
  31.             // Cache the instance for this thread ID
  32.             Debug.WriteLine("New connection " + OrgName + "_" + ThreadId.ToString() + " created");
  33.             _services.Add(OrgName + "_" + ThreadId.ToString(), currentService);
  34.         }
  35.         return currentService;
  36.     }
  37. }

The routine locks the dictionary object therefore preventing cross thread issues. A property on the main class allows for a new instance or existing instance of the service class to be returned:

  1. /// <summary>
  2. /// Returns a new instant of the CRM serivce
  3. /// </summary>
  4. public CrmHelper CrmHelper
  5. {
  6.     get
  7.     {
  8.         if (tenant != null)
  9.         {
  10.             return GetCrmService(Thread.CurrentThread.ManagedThreadId, tenant);
  11.         }
  12.         else
  13.         {
  14.             throw new Exception("GetCrmService failed due to 'TenantScope' not supplied to constructor.");
  15.         }
  16.  
  17.         return null;
  18.     }
  19. }

 

Note: the use of the ManagedThreadId to determine the index into the dictionary object.

The use of the parallel library improves performance considerably, however, it still can be tuned that little bit further!.

Increasing the throughput via the DefaultConnectionLimit (MSDN), increasing this to 20 will give that little boost. I would, however, monitor the servers performance as this may have an adverse impact on the server!.

Hopefully, the above code snippets will inspire you to investigate further and lead you to writing faster and more efficient code.

Posted in Uncategorized | Comments Off

CRM 2011 Auditing obscures the real error!!!

I’ve just written a new plugin and made a typo in one of the selected attributes. The purpose of the plugin was simple (for this example), create an invoice from another entity and assign a tax code. The latter is a lookup to a custom table called t4a_taxrating.

Whilst saving the parent entity that will trigger the plugin to create an invoice I get this:

image

The download logfile shows little more info except which plugin failed.

Running through an attached debugger I still get the timeout on the creating of the invoice.

Checking the event logs on the server revealed this:

“Query execution time of 30.0 seconds exceeded the threshold of 10 seconds. Thread: 13; Database: OFFICE1V2_MSCRM; Query: select top 1 ObjectTypeCode from AuditBase where ([Action] = 100 and [ChangeData] = ”t4a_taxrate”).”

A little more information, indicating the query is failing when reading the auditbase.

So, to remove auditing from the equation I switched it off completely (as this is a dev system it doesn’t matted about loosing data).

Back to the parent entity and saving the record no displays this:

image

The message now tells me there is a problem with a query, the plugin and the offending attribute.

Summary:

  • SQL Timeout
  • Check the event log – any hint of auditing exceeding threshholds
  • Switch off auditing to get the ‘real’ message.
Posted in CRM 2011 | Tagged | Comments Off

CRM 2011 / Sharepoint – Importing a solution failed

Just another interesting observation about the Sharepoint integration to CRM 2011.  I have customised the Document Location entities to include some additional fields.  My dev environment worked fine. A solution was build and then moved to a vanilla and the import failed with the following:

image

As this is a Vanilla system,  Sharepoint has never been configured. The process of running the Document Management settings ALSO creates N:1 relationships between the Document Locations entity the entity to be enabled for sharepoint.

So, if you are importing a solution that contains the Document Locations entity – ensure the destination system is also setup with the same enabled entities

Posted in CRM 2011 | Tagged , | Comments Off

Visual Studio 2010 slow to load…

For a while since loading Silverlight 4/5/SDK/Toolkit – I’ve been having issues with VS 2010 starting. It will take about +30 secs , the status bar indicates:

Loading toolbox content from package Microsoft.VisualStudio.ide.toolboxcontrolinstaller… {guid}

After this quick fix – all projects load instantly (i.e < 5 secs from tfs)

NOTE: Ensure you have a backup of the registry before performing this:

1. Close all instances of VS 2010

2. Using RegEdit, rename all instances of the key {2c298b35-07da-45f1-96a3-be55d91c8d7a} on the machine you are having performance problems with

3. Relaunch VS 2010, In the toolbox, Right Click + Resettoolbox.

The last operation will reinstate your custom toolbox.

Posted in Uncategorized | Tagged , | Comments Off

PixRM 2011

Sometime ago, I wrote a complete image handling application for CRM 4.0 called PixRM. Over the past week I decided it was time to update the product to support Microsoft Dynamics CRM 2011.

Gallery view

image

Detail View: (click on the image above)

image

Summary of features:

  • Gallery view of image attachments (jpg, bmp, gif)
  • Image Detail record created for each image.
  • Additional details extracted from jpg files include, GPS coordinates, image date/time, Camera make & model.
  • Easy to configure.
  • Support for on-premise, Microsoft Online and partner hosted.

Ideal for recording and previewing images stored against record. Ideal for housing , estate agents (real estate), commercial property.

Drop me an email if you would like more info mread@mike-read.co.uk.

Posted in CRM 2011 | Tagged , | Comments Off

Create, Send, Route an Email with a template an attachment

As the title suggests, I want to Create an email from a template and attach a file.  I also want the option to send the email or route to a queue for validation before manually hitting Send.

The purpose for the is an invoice process. Generate a batch of invoices, create a PDF from a PDF template , attach the file to a templated email etc…

Firstly, create an email from a template.  The template has been created and is saved as an Email Template:

  1. // Create an e-mail message using a template.
  2. InstantiateTemplateRequest instTemplateReq = new InstantiateTemplateRequest
  3. {
  4.     TemplateId = fetchTemplateId(LocalName),
  5.     ObjectId = Id,
  6.     ObjectType = "invoice"
  7. };
  8. InstantiateTemplateResponse instTemplateResp = null;
  9. Entity eMail = null;
  10.  
  11. try
  12. {
  13.     instTemplateResp = (InstantiateTemplateResponse)helper.Execute(instTemplateReq, true);
  14. }
  15. catch (Exception ex)
  16. {
  17.     log.Write(this, "Failed to create email from template (using " + LocalName + ") " + ex.Message.ToString());
  18. }

 

Using the InstantiateTemplateRequest, I need to specify the templateid (derived the the PDF template name),the objectid (the previously created invoiceid) and the objecttype – the invoice.

The result is an email Entity:

  1. eMail = instTemplateResp.EntityCollection.Entities[0];

 

From this point I can create my PDF and attach:

  1. // Fetch invoicenumber and add to parameters
  2. string prefix = "topmostSubform[0].Page1[0].";
  3. string invno = fetchInvoiceNumber(Id);
  4. Parameters.Add(new PDFParameter() { PathName = prefix + "invno[0]", Value = invno });
  5.  
  6. writeEmailAttachmentToCrm(emailId, "email", invno + ".pdf", createPDFArray(LocalName, Parameters));

 

Depending on the parameters passed I need to either SEND or ROUTE to an invoice queue for checking:

  1.  
  2. if (Send)
  3. {
  4.     // Send the email
  5.     SendEmailRequest send = new SendEmailRequest();
  6.     send.EmailId = emailId;
  7.     send.TrackingToken = "";
  8.     send.IssueSend = true;
  9.  
  10.     //SendEmailResponse sendEmailresp = (SendEmailResponse)helper.Execute(send, true);
  11. }
  12. else
  13. {
  14.     // write to queue
  15.     AddToQueueRequest routeRequest = new AddToQueueRequest
  16.     {
  17.         Target = new EntityReference("email", emailId),
  18.         DestinationQueueId = Guid.Parse("3CEB6F78-2698-E111-9B55-782BCB776B8C")
  19.     };
  20.  
  21.     helper.Execute(routeRequest, true);
  22.  
  23. }

 

During testing , I’ve taken precautions to NOT send emails:

1. Comment out the SendRequest

2. Hardcode the partyIds

3. Hardcode the Queue

Hopefully this demonstrates how easy it is to send templated emails with attachments. 

Oh Yes – the PDF routines – using iTextSharp:

  1. /// <summary>
  2. /// Create a PDFstream from a localfile
  3. /// </summary>
  4. /// <param name="LocalName"></param>
  5. /// <returns></returns>
  6. public PDFData CreatePDF(Guid Id, string LocalName, ObservableCollection<PDFParameter> Parameters)
  7. {
  8.     PDFData returnData = new PDFData();
  9.  
  10.     string currentDir = AppDomain.CurrentDomain.BaseDirectory + @"Templates\";
  11.  
  12.     PdfReader reader = new PdfReader(currentDir + LocalName );
  13.  
  14.     // filling in the form
  15.  
  16.     MemoryStream op = new MemoryStream();
  17.     PdfStamper stamp1 = new PdfStamper(reader, op);
  18.     AcroFields form1 = stamp1.AcroFields;
  19.  
  20.     foreach (PDFParameter parameter in Parameters)
  21.     {
  22.         try
  23.         {
  24.             form1.SetField(parameter.PathName, parameter.Value);
  25.         }
  26.         catch (Exception ex)
  27.         { }
  28.     }
  29.     stamp1.FormFlattening = true;
  30.     stamp1.Close();
  31.  
  32.     returnData.Data = op.ToArray();
  33.     returnData.Id = Id;
  34.  
  35.     op.Close();
  36.     op.Dispose();
  37.     reader.Close();
  38.  
  39.     return returnData;
  40. }

The PDF files are preformatted with placeholders (fields) to be substituted here .

Posted in CRM 2011 | Comments Off

Parse savedquery and userquery attributes with Linq

I’ve been writing a Silverlight grid that displays columns from a CRM view.  (dynamic grid,  display labels as column names and built at runtime). 

Some challenges, the first being how to parse the output from the returned fetch query to get the view columns, linked entities and conditions.

Firstly, my fetchxml looks like this:

  1.             string fetchXml = @"<fetch mapping='logical'>
  2.                                     <entity name='{1}'>
  3.                                         <attribute name='name'/>
  4.                                         <attribute name='layoutxml'/>
  5.                                         <attribute name='fetchxml'/>
  6.                                         <filter type='and'>
  7.                                             <condition attribute='returnedtypecode' operator='eq' value='{0}'/>
  8.                                         </filter>
  9.                                     </entity>
  10.                                 </fetch>
  11.                             ";

{0} = either savedquery or userquery – depending on the purpose of the grid.

The output from the above used for the test code below:

  1. static string LayoutXml = "<grid name=\"resultset\" object=\"1090\" jump=\"name\" select=\"1\" icon=\"1\" preview=\"1\"><row name=\"result\" id=\"invoiceid\"><cell name=\"customerid\" width=\"150\"/><cell name=\"name\" width=\"300\"/><cell name=\"invoicenumber\" width=\"125\"/><cell name=\"t4a_collectionmethod\" width=\"125\"/><cell name=\"t4a_invoicedate\" width=\"100\"/><cell name=\"t4a_issueddate\" width=\"100\"/><cell name=\"statecode\" width=\"100\"/><cell name=\"totalamount\" width=\"100\"/><cell name=\"t4a_adviserid\" width=\"150\"/><cell name=\"a_550a704187f6e01187a9b8ac6f1693a8.fullname\" width=\"100\" disableSorting=\"1\"/></row></grid>";
  2.  static string FetchXml = "<fetch version=\"1.0\" output-format=\"xml-platform\" mapping=\"logical\" distinct=\"false\"><entity name=\"invoice\"><attribute name=\"name\"/><attribute name=\"customerid\"/><attribute name=\"totalamount\"/><attribute name=\"statecode\"/><attribute name=\"t4a_issueddate\"/><attribute name=\"t4a_invoicedate\"/><attribute name=\"t4a_collectionmethod\"/><attribute name=\"t4a_adviserid\"/><attribute name=\"invoicenumber\"/><attribute name=\"invoiceid\"/><order attribute=\"customerid\" descending=\"false\"/><link-entity name=\"systemuser\" from=\"systemuserid\" to=\"t4a_adviserid\" visible=\"false\" link-type=\"outer\" alias=\"a_550a704187f6e01187a9b8ac6f1693a8\"><attribute name=\"fullname\"/></link-entity></entity></fetch>";
  3.  
  4.  static string FetchConditionXml = "<fetch version=\"1.0\" output-format=\"xml-platform\" mapping=\"logical\" distinct=\"false\"><entity name=\"invoice\"><attribute name=\"name\" /><attribute name=\"totalamount\" /><attribute name=\"customerid\" /><attribute name=\"statecode\" /><attribute name=\"t4a_issueddate\" /><attribute name=\"t4a_invoicedate\" /><attribute name=\"t4a_collectionmethod\" /><attribute name=\"t4a_adviserid\" /><attribute name=\"invoicenumber\" /><order attribute=\"t4a_invoicedate\" descending=\"true\" /><filter type=\"and\"><filter type=\"and\"><condition attribute=\"statecode\" operator=\"eq\" value=\"0\" /><condition attribute=\"duedate\" operator=\"last-x-years\" value=\"5\" /></filter></filter><attribute name=\"invoiceid\" /></entity></fetch>";

 

1. Parse the display columns:

  1. XDocument layoutRows = XDocument.Parse(LayoutXml);
  2.  
  3. var lrows = from i in layoutRows.Descendants("grid").Descendants("row").Descendants("cell")
  4.            select new
  5.            {
  6.                name = i.Attribute("name").Value
  7.            };
  8.  
  9. foreach (var item in lrows)
  10. {
  11.     Console.WriteLine(item.name);
  12. }

 

2. Parse the “linked” entities for the view

  1. XDocument fetchRows = XDocument.Parse(FetchXml);
  2.  
  3. var frows = from i in fetchRows.Descendants("fetch").Descendants("entity").Descendants("link-entity")
  4.            select new
  5.            {
  6.                name = i.Attribute("name").Value,
  7.                alias = i.Attribute("alias").Value
  8.            };
  9.  
  10. foreach (var item in frows)
  11. {
  12.     Console.WriteLine(item.name);
  13.     Console.WriteLine(item.alias);
  14. }

 

3. Parse the “condition” for the view

  1. XDocument fetchConditionRows = XDocument.Parse(FetchConditionXml);
  2.  
  3. var fcrows = from i in fetchConditionRows.Descendants("fetch").Descendants("entity").Descendants("condition")
  4.             select new
  5.             {
  6.                 attribute = i.Attribute("attribute").Value,
  7.                 cOperator = i.Attribute("operator").Value,
  8.                 value = i.Attribute("value").Value
  9.             };
  10.  
  11. foreach (var item in fcrows)
  12. {
  13.     Console.WriteLine(item.attribute);
  14.     Console.WriteLine(item.cOperator);
  15.     Console.WriteLine(item.value);
  16.     Console.WriteLine();
  17. }

 

The above three routines demonstrate how to extract the necessary attributes using Linq.

NOTE: If you are using Silverlight, you will need to add System.Xml.Linq as a reference to use XDocument.

Posted in CRM 2011 | Tagged , , , | Comments Off