Background

This is the second part on how to delete accounts. In this blog post we will explain why we also exposed CRUD request on our CrmData module.

We also moved the repetitive code (Client SDK and Proxy) to a Helper script file DG.Delegate.HowToDaxif.DataManagement.Helper.fsx

Delete all accounts

As mentioned above, we just moved a few function to another file and created a prime version of the script we used in the previous blog post DG.Delegate.HowToDaxif.DataManagement.Prime.fsx.

It’s only the second step that we changed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 2) Delete all entities from query (with ExecuteMultiple and parallelism)
accounts ()
|> hlp.Seq.split (10*1000)
|> Seq.iter(fun xs -> 
  printfn "- Chunks of 10.000"
  xs 
  |> Array.Parallel.map(fun e -> CRUD.deleteReq e.LogicalName e.Id)
  |> Array.toSeq
  // ExecuteMultiple - Run-time limitations:
  // https://msdn.microsoft.com/en-us/library/jj863631.aspx#limitations
  |> hlp.Seq.split 1000 
  |> Seq.toArray
  |> Array.Parallel.map (fun dreqs ->
      let orc = new OrganizationRequestCollection()

      dreqs // OrganizationRequestCollection is not thread-safe
      |> Array.iter (fun x -> orc.Add(x))

      let emreqs = new ExecuteMultipleRequest()
      emreqs.Settings <- new ExecuteMultipleSettings()
      emreqs.Settings.ContinueOnError <- true
      emreqs.Settings.ReturnResponses <- true
      emreqs.Requests <- orc
      emreqs, dreqs)
  |> Array.Parallel.map (fun (emreqs, dreqs) -> 
      try 
        (hlp.proxy().Execute(emreqs) :?> ExecuteMultipleResponse, dreqs) |> Some
      with ex -> 
        Console.Error.WriteLine(sprintf "* Proxy execute: %s" ex.Message); None)
  |> Array.Parallel.choose (id)
  |> Array.Parallel.iter (fun (emresps, _) -> 
      emresps.Responses
      |> Seq.toArray
      |> Array.Parallel.iter(fun kv ->
        match kv.Fault with
          | null -> ()
          | fault ->
            Console.Error.WriteLine(
              sprintf "  --Execute multiple: %s" fault.Message))))

If we now run the two deletion script for comparison, we can see that they have similar performance, due to the size of data:

Note: There is a limitations on MS CRM Online (only two ExecuteMultiple can be executed at the same time.

Output from evaluating script from part 1:

> #time;;

--> Timing now on

> 
Chunks of 1000
Chunks of 1000
Real: 00:00:57.960, CPU: 00:00:08.109, GC gen0: 2, gen1: 0, gen2: 0
val it : unit = ()
> 

Output from evaluating script from part 2:

> #time;;

--> Timing now on

> 
- Chunks of 10.000
Real: 00:00:55.922, CPU: 00:00:00.187, GC gen0: 0, gen1: 0, gen2: 0
val it : unit = ()
> 

Bonus

While I was writing this blogpost, we got the following question: “We have a question about timezones. It seems like there is no way to set up a default timezone that will be used when new users are added to CRM. Do you know of a way that we can set up a timezone that gets used for each user without having to edit them individually?

We provided the following answers:

  • Post plug-in added on the SystemUser Create event: You can hook up an event that sets the time zone when a new users is added (you will have to find the related UserSettings created by the kernel). Downside is that this approach only work with new created SystemUsers.

  • As Microsoft have bought AdxStudio, they are slowly moving all the fancy PowerShell scripts from their ALM Toolkit to Microsoft Xrm Data Powershell library (which is nice). Here is an example on how to update a System Users settings: UpdateCrmUsersSettings.ps1

  • Last but not least, you could use Daxif (now it’s open source) and run the following F# script when a user is created and also on some time frequency, to ensure that users are using the time zone that you specify:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let timeZoneCode () =
  CRUD.retrieve 
    (hlp.proxy())
    target.Metadata.TimeZoneDefinition.``(LogicalName)``
    target.Records.TimeZoneDefinition.``(GMT+01:00) Brussels, Copenhagen, Madrid, Paris``
  |> fun e -> e.Attributes.[target.Metadata.TimeZoneDefinition.TimeZoneCode]

target.Records.SystemUser.``(All Records)``
|> Array.filter(fun guid -> guid <> target.Records.SystemUser.SYSTEM)
|> Array.filter(fun guid -> guid <> target.Records.SystemUser.INTEGRATION)
|> Array.Parallel.map(
  fun guid ->
    try
      let e = new Entity(entityName = target.Metadata.UserSettings.``(LogicalName)``)
      e.Id <- guid
      e.Attributes.Add(target.Metadata.UserSettings.TimeZoneCode,timeZoneCode())
      CRUD.update (hlp.proxy()) e |> Choice1Of2
    with ex -> ex |> Choice2Of2)

(semi-type safe approach which is readable and generic for all MS CRM instances)

More info:

References: