├── .gitattributes ├── .gitignore ├── .versionrc.json ├── CHANGELOG.md ├── D365Extensions ├── D365Extensions.Benchmark │ ├── App.config │ ├── D365Extensions.Benchmark.csproj │ ├── EntityLogicalName.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ProperyExpression.cs │ ├── TestEntities.cs │ └── packages.config ├── D365Extensions.Tests │ ├── AliasedValueExtensionsTests.cs │ ├── AliasedValueTests.cs │ ├── ColumnSetExtensionsTests.cs │ ├── ColumnSetTests.cs │ ├── ConditionExpressionTests.cs │ ├── D365Extensions.Tests.csproj │ ├── DataCollectionExtensionsTests.cs │ ├── EarlyBoundTypes.cs │ ├── EntityCollectionExtensionsTests.cs │ ├── EntityExtensionsTests.cs │ ├── EntityIdComparer.cs │ ├── EntityImageCollectionExtensionsTests.cs │ ├── EntityReferenceExtensionsTests.cs │ ├── EntityReferenceTests.cs │ ├── ExecuteMultipleProgressTests.cs │ ├── FakeExecuteMultipleExecutor.cs │ ├── FakeExecuteMultipleExecutorTests.cs │ ├── FilterExpressionExtensionsTests.cs │ ├── IOrganizationServiceExecuteExtensionsTests.cs │ ├── IOrganizationServiceRetrieveMultipleExtensionsTests.cs │ ├── IPluginExecutionContextExtensionsTests.cs │ ├── IServiceProviderExtensionsTests.cs │ ├── ITracingServiceExtensionsTests.cs │ ├── KeyAttributeCollectionExtensionsTests.cs │ ├── KeyAttributeCollectionTests.cs │ ├── LinkEntityExtensionsTests.cs │ ├── LinkEntityTests.cs │ ├── LogicalNameTests.cs │ ├── OptionSetValueTests.cs │ ├── OrderExpressionTests.cs │ ├── OrganizationRequestCollectionEnumeratorTests.cs │ ├── OrganizationServiceFaultExtensionsTests.cs │ ├── PluginExecutionTraceContextTests.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── QueryBaseExtensionsTests.cs │ ├── QueryByAttributeExtensionsTests.cs │ ├── QueryExpressionExtensionsTests.cs │ ├── StringBuilderExtensionsTests.cs │ ├── TestEntities.cs │ └── app.config ├── D365Extensions.sln └── D365Extensions │ ├── AliasedValue.cs │ ├── AliasedValueExtensions.cs │ ├── CodeActivityContextExtensions.cs │ ├── ColumnSet.cs │ ├── ColumnSetExtensions.cs │ ├── ConditionExpression.cs │ ├── D365Extensions.csproj │ ├── D365Extensions.nuspec │ ├── DataCollectionExtensions.cs │ ├── EntityCollectionExtensions.cs │ ├── EntityExtensions.cs │ ├── EntityImageCollectionExtensions.cs │ ├── EntityReference.cs │ ├── EntityReferenceExtensions.cs │ ├── ErrorCodes.cs │ ├── ExecuteMultipleOperationResponse.cs │ ├── ExecuteMultipleProgress.cs │ ├── ExecuteMultipleResponseExtensions.cs │ ├── ExecuteMultipleResponseItemExtensions.cs │ ├── FilterExpressionExtensions.cs │ ├── IOrganizationServiceAssosiateExtensions.cs │ ├── IOrganizationServiceDeleteExtensions.cs │ ├── IOrganizationServiceDisassociateExtensions.cs │ ├── IOrganizationServiceExecuteExtensions.cs │ ├── IOrganizationServiceRetrieveExtensions.cs │ ├── IOrganizationServiceRetrieveMultipleExtensions.cs │ ├── IOrganizationServiceUpdateExtensions.cs │ ├── IPluginExecutionContextExtensions.cs │ ├── IServiceProviderExtensions.cs │ ├── ITracingServiceExtensions.cs │ ├── KeyAttributeCollection.cs │ ├── KeyAttributeCollectionExtensions.cs │ ├── LinkEntity.cs │ ├── LinkEntityExtentions.cs │ ├── LogicalName.cs │ ├── MoneyExtensions.cs │ ├── OptionSetValue.cs │ ├── OptionSetValueExtensions.cs │ ├── OrderExpression.cs │ ├── OrganizationRequestCollectionEnumerator.cs │ ├── OrganizationServiceFaultExtensions.cs │ ├── PluginExecutionTraceContext.cs │ ├── PluginExecutionTraceContextSettings.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── QueryBaseExtensions.cs │ ├── QueryByAttributeExtensions.cs │ ├── QueryExpressionExtensions.cs │ ├── StringBuilderExtensions.cs │ ├── scripts │ └── GenerateErrorCodesEnum.ps1 │ └── СheckParam.cs ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── commitlint.config.js ├── fixrmlogo_64.png ├── package-lock.json ├── package.json └── scripts └── azure-pipeline-updater.js /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.snk 202 | *.publishsettings 203 | orleans.codegen.cs 204 | 205 | # Since there are multiple workflows, uncomment next line to ignore bower_components 206 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 207 | #bower_components/ 208 | 209 | # RIA/Silverlight projects 210 | Generated_Code/ 211 | 212 | # Backup & report files from converting an old project file 213 | # to a newer Visual Studio version. Backup files are not needed, 214 | # because we have git ;-) 215 | _UpgradeReport_Files/ 216 | Backup*/ 217 | UpgradeLog*.XML 218 | UpgradeLog*.htm 219 | 220 | # SQL Server files 221 | *.mdf 222 | *.ldf 223 | *.ndf 224 | 225 | # Business Intelligence projects 226 | *.rdl.data 227 | *.bim.layout 228 | *.bim_*.settings 229 | 230 | # Microsoft Fakes 231 | FakesAssemblies/ 232 | 233 | # GhostDoc plugin setting file 234 | *.GhostDoc.xml 235 | 236 | # Node.js Tools for Visual Studio 237 | .ntvs_analysis.dat 238 | node_modules/ 239 | 240 | # Typescript v1 declaration files 241 | typings/ 242 | 243 | # Visual Studio 6 build log 244 | *.plg 245 | 246 | # Visual Studio 6 workspace options file 247 | *.opt 248 | 249 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 250 | *.vbw 251 | 252 | # Visual Studio LightSwitch build output 253 | **/*.HTMLClient/GeneratedArtifacts 254 | **/*.DesktopClient/GeneratedArtifacts 255 | **/*.DesktopClient/ModelManifest.xml 256 | **/*.Server/GeneratedArtifacts 257 | **/*.Server/ModelManifest.xml 258 | _Pvt_Extensions 259 | 260 | # Paket dependency manager 261 | .paket/paket.exe 262 | paket-files/ 263 | 264 | # FAKE - F# Make 265 | .fake/ 266 | 267 | # JetBrains Rider 268 | .idea/ 269 | *.sln.iml 270 | 271 | # CodeRush 272 | .cr/ 273 | 274 | # Python Tools for Visual Studio (PTVS) 275 | __pycache__/ 276 | *.pyc 277 | 278 | # Cake - Uncomment if you are using it 279 | # tools/** 280 | # !tools/packages.config 281 | 282 | # Telerik's JustMock configuration file 283 | *.jmconfig 284 | 285 | # BizTalk build output 286 | *.btp.cs 287 | *.btm.cs 288 | *.odx.cs 289 | *.xsd.cs 290 | 291 | # Visual Studio Life Unit Testing config 292 | *.lutconfig -------------------------------------------------------------------------------- /.versionrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": "# What's new", 3 | "skip": { 4 | "bump": false, 5 | "changelog": false, 6 | "commit": false, 7 | "tag": false 8 | }, 9 | "bumpFiles": [ 10 | { 11 | "temporary_workaround": "https://github.com/conventional-changelog/standard-version/issues/533", 12 | "filename": "package.json", 13 | "type": "json" 14 | }, 15 | { 16 | "filename": "azure-pipelines.yml", 17 | "updater": "./scripts/azure-pipeline-updater.js" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Benchmark/App.config: -------------------------------------------------------------------------------- 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 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Benchmark/EntityLogicalName.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Reflection; 8 | using Microsoft.Xrm.Sdk.Client; 9 | using System.Collections.Concurrent; 10 | 11 | namespace D365Extensions 12 | { 13 | public static class EntityLogicalName 14 | { 15 | static ConcurrentDictionary typeChache = new ConcurrentDictionary(); 16 | 17 | static ConcurrentDictionary typeChache2 = new ConcurrentDictionary(); 18 | 19 | public static string GetName() where T : Entity 20 | { 21 | return typeof(T).Name.ToLowerInvariant(); 22 | } 23 | 24 | public static string GetNameC() where T : Entity 25 | { 26 | var type = typeof(T); 27 | 28 | typeChache.TryGetValue(type, out string logicalName); 29 | if (logicalName == null) 30 | { 31 | logicalName = type.Name.ToLowerInvariant(); 32 | 33 | typeChache.TryAdd(type, logicalName); 34 | } 35 | 36 | return logicalName; 37 | } 38 | 39 | public static string GetNameR() where T : Entity 40 | { 41 | return typeof(T).GetCustomAttribute().LogicalName; 42 | } 43 | 44 | public static string GetNameRC() where T : Entity 45 | { 46 | var type = typeof(T); 47 | 48 | typeChache2.TryGetValue(type, out string logicalName); 49 | if (logicalName == null) 50 | { 51 | logicalName = type.GetCustomAttribute().LogicalName; 52 | 53 | typeChache2.TryAdd(type, logicalName); 54 | } 55 | 56 | return logicalName; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Running; 3 | using D365Extensions.Tests; 4 | using System; 5 | 6 | namespace D365Extensions.Benchmark 7 | { 8 | [MemoryDiagnoser] 9 | public class ENBenchmark 10 | { 11 | [Benchmark] 12 | public string GetName() => EntityLogicalName.GetName(); 13 | 14 | [Benchmark] 15 | public string GetNameC() => EntityLogicalName.GetNameC(); 16 | 17 | [Benchmark] 18 | public string GetNameR() => EntityLogicalName.GetNameR(); 19 | 20 | [Benchmark] 21 | public string GetNameRC() => EntityLogicalName.GetNameRC(); 22 | } 23 | 24 | [MemoryDiagnoser] 25 | public class PEBenchmark 26 | { 27 | [Benchmark] 28 | public string LambdaRef() => ProperyExpression.GetName(e => e.Property_1); 29 | 30 | [Benchmark] 31 | public string LambdaVal() => ProperyExpression.GetName(e => e.Property_2); 32 | 33 | [Benchmark] 34 | public string ReflectionRef() => ProperyExpression.GetNameR(e => e.Property_1); 35 | 36 | [Benchmark] 37 | public string ReflectionVal() => ProperyExpression.GetNameR(e => e.Property_2); 38 | 39 | [Benchmark] 40 | public string ChacheRef() => ProperyExpression.GetNameC(e => e.Property_1); 41 | 42 | [Benchmark] 43 | public string ChacheVal() => ProperyExpression.GetNameC(e => e.Property_2); 44 | 45 | [Benchmark] 46 | public string Chache2Ref() => ProperyExpression.GetNameC2(e => e.Property_1); 47 | 48 | [Benchmark] 49 | public string Chache2Val() => ProperyExpression.GetNameC2(e => e.Property_2); 50 | } 51 | 52 | public class ToLowerBenchmark 53 | { 54 | [Benchmark] 55 | public string ToLowerInvariant() => "AccountNumber".ToLowerInvariant(); 56 | 57 | [Benchmark] 58 | public string ToLower() => "AccountNumber".ToLower(); 59 | } 60 | 61 | public class Program 62 | { 63 | static void Main(string[] args) 64 | { 65 | var summary = BenchmarkRunner.Run(); 66 | //var summary = BenchmarkRunner.Run(); 67 | //var summary = BenchmarkRunner.Run(); 68 | 69 | Console.ReadKey(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Benchmark/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("D365Extensions.Benchmark")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("D365Extensions.Benchmark")] 13 | [assembly: AssemblyCopyright("Copyright © 2021")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1eca11b0-de44-4c41-ac11-85b7c4af0bfa")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Benchmark/ProperyExpression.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using Microsoft.Xrm.Sdk; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using System.Reflection; 9 | 10 | namespace D365Extensions 11 | { 12 | /// 13 | /// Helper class for reading property names from lambda expressions 14 | /// 15 | public static class ProperyExpression 16 | { 17 | public static List GetNames(params Expression>[] expressions) 18 | { 19 | return expressions 20 | .Select(e => GetName(e)) 21 | .ToList(); 22 | } 23 | 24 | public static string GetName(Expression> expression) 25 | { 26 | return GetName(expression?.Body); 27 | } 28 | 29 | public static string GetNameR(Expression> expression) 30 | { 31 | return GetNameR(expression?.Body); 32 | } 33 | 34 | public static string GetNameC(Expression> expression) 35 | { 36 | return GetNameC(expression?.Body); 37 | } 38 | 39 | static ConcurrentDictionary expressionChache = new ConcurrentDictionary(); 40 | 41 | public static string GetNameC2(Expression> expression) 42 | { 43 | expressionChache.TryGetValue(expression, out string logicalName); 44 | 45 | if (logicalName == null) 46 | { 47 | logicalName = GetNameC2(expression?.Body); 48 | expressionChache.TryAdd(expression, logicalName); 49 | } 50 | 51 | return logicalName; 52 | } 53 | 54 | static string GetName(Expression expression) 55 | { 56 | if (expression == null) return null; 57 | 58 | // Property, field of method returning value type 59 | if (expression is UnaryExpression unaryExpression) 60 | { 61 | expression = unaryExpression.Operand; 62 | } 63 | 64 | // Reference type property or field 65 | if (expression is MemberExpression memberExpession) 66 | { 67 | MemberInfo member = memberExpession.Member; 68 | 69 | return member.Name.ToLowerInvariant(); 70 | } 71 | 72 | throw new ArgumentException(nameof(expression)); 73 | } 74 | 75 | static string GetNameR(Expression expression) 76 | { 77 | if (expression == null) return null; 78 | 79 | // Property, field of method returning value type 80 | if (expression is UnaryExpression unaryExpression) 81 | { 82 | expression = unaryExpression.Operand; 83 | } 84 | 85 | // Reference type property or field 86 | if (expression is MemberExpression memberExpession) 87 | { 88 | MemberInfo member = memberExpession.Member; 89 | 90 | return member.GetCustomAttribute(false).LogicalName; 91 | } 92 | 93 | throw new ArgumentException(nameof(expression)); 94 | } 95 | 96 | static ConcurrentDictionary memberChache = new ConcurrentDictionary(); 97 | 98 | static string GetNameC(Expression expression) 99 | { 100 | if (expression == null) return null; 101 | 102 | // Property, field of method returning value type 103 | if (expression is UnaryExpression unaryExpression) 104 | { 105 | expression = unaryExpression.Operand; 106 | } 107 | 108 | // Reference type property or field 109 | if (expression is MemberExpression memberExpession) 110 | { 111 | MemberInfo member = memberExpession.Member; 112 | 113 | memberChache.TryGetValue(member, out string logicalName); 114 | 115 | if (logicalName == null) 116 | { 117 | logicalName = member.GetCustomAttribute(false).LogicalName; 118 | 119 | memberChache.TryAdd(member, logicalName); 120 | } 121 | 122 | return logicalName; 123 | } 124 | 125 | throw new ArgumentException(nameof(expression)); 126 | } 127 | 128 | static string GetNameC2(Expression expression) 129 | { 130 | if (expression == null) return null; 131 | 132 | // Property, field of method returning value type 133 | if (expression is UnaryExpression unaryExpression) 134 | { 135 | expression = unaryExpression.Operand; 136 | } 137 | 138 | // Reference type property or field 139 | if (expression is MemberExpression memberExpession) 140 | { 141 | MemberInfo member = memberExpession.Member; 142 | 143 | return member.GetCustomAttribute(false).LogicalName; 144 | } 145 | 146 | throw new ArgumentException(nameof(expression)); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Benchmark/TestEntities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xrm.Sdk; 7 | using Microsoft.Xrm.Sdk.Client; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | /// 12 | /// Entity for bencmark 13 | /// 14 | [EntityLogicalName("custom_entity")] 15 | public class CustomEntity : Entity 16 | { 17 | public static string EnityLogicalName = "custom_entity"; 18 | 19 | public CustomEntity() : base(EnityLogicalName) 20 | { 21 | } 22 | 23 | [AttributeLogicalName("prop_1")] 24 | public string Property_1 25 | { 26 | get => GetAttributeValue("prop_1"); 27 | set => SetAttributeValue("prop_1", value); 28 | } 29 | 30 | [AttributeLogicalName("prop_2")] 31 | public int? Property_2 32 | { 33 | get => GetAttributeValue ("prop_2"); 34 | set => SetAttributeValue("prop_2", value); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Benchmark/packages.config: -------------------------------------------------------------------------------- 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 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/AliasedValueExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class AliasedValueExtensionsTests 13 | { 14 | [TestMethod()] 15 | public void IsPrimaryKeyShouldReturnTrueTest() 16 | { 17 | // Setup 18 | var av = new AliasedValue( 19 | entityLogicalName: "account", 20 | attributeLogicalName: "accountid", 21 | value: Guid.NewGuid()); 22 | 23 | // Act 24 | var idPrimaryKey = av.IsPrimaryKey(); 25 | 26 | // Assert 27 | Assert.IsTrue(idPrimaryKey); 28 | } 29 | 30 | [TestMethod()] 31 | public void IsPrimaryKeyShouldReturnTrueForActivityTest() 32 | { 33 | FakeXrmEasy.XrmFakedContext context = new FakeXrmEasy.XrmFakedContext(); 34 | 35 | 36 | // Setup 37 | var av = new AliasedValue( 38 | entityLogicalName: "task", 39 | attributeLogicalName: "activityid", 40 | value: Guid.NewGuid()); 41 | 42 | // Act 43 | var idPrimaryKey = av.IsPrimaryKey(); 44 | 45 | // Assert 46 | Assert.IsTrue(idPrimaryKey); 47 | } 48 | 49 | [TestMethod()] 50 | public void IsPrimaryKeyShouldReturnFalseTest() 51 | { 52 | // Setup 53 | var av = new AliasedValue( 54 | entityLogicalName: "account", 55 | attributeLogicalName: "accountnumber", 56 | value: "42"); 57 | 58 | // Act 59 | var idPrimaryKey = av.IsPrimaryKey(); 60 | 61 | // Assert 62 | Assert.IsFalse(idPrimaryKey); 63 | } 64 | 65 | [TestMethod()] 66 | public void GetValueTest() 67 | { 68 | // Setup 69 | var av = new AliasedValue( 70 | entityLogicalName: "account", 71 | attributeLogicalName: "accountnumber", 72 | value: "42"); 73 | 74 | // Act 75 | var actualValue = av.GetValue(); 76 | 77 | // Assert 78 | Assert.AreEqual("42", actualValue); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/AliasedValueTests.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions.Tests.Entities; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Microsoft.Xrm.Sdk; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace D365Extensions.Tests 11 | { 12 | [TestClass()] 13 | public class AliasedValueTests 14 | { 15 | [TestMethod()] 16 | public void AliasedValueTest() 17 | { 18 | // Setup 19 | var expectedEntityName = "account"; 20 | var expectedAttributeName = "accountnumber"; 21 | var expectedValue = "42"; 22 | 23 | // Act 24 | AliasedValue aliasedValue = new AliasedValue(a=> a.AccountNumber, expectedValue); 25 | 26 | // Assert 27 | Assert.AreEqual(expectedEntityName, aliasedValue.EntityLogicalName); 28 | Assert.AreEqual(expectedAttributeName, aliasedValue.AttributeLogicalName); 29 | Assert.AreEqual(expectedValue, aliasedValue.Value); 30 | } 31 | 32 | [TestMethod()] 33 | public void NullTest() 34 | { 35 | // Setup 36 | AliasedValue aliasedValueT = null; 37 | 38 | // Act 39 | AliasedValue aliasedValue = aliasedValueT; 40 | 41 | // Assert 42 | Assert.IsNull(aliasedValue); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/ColumnSetExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class ColumnSetExtensionsTests 13 | { 14 | [TestMethod()] 15 | public void AddColumnTest() 16 | { 17 | // Setup 18 | string expectedColumn = nameof(TestEntity.ReferenceTypeProperty).ToLower(); 19 | 20 | // Act 21 | ColumnSet columnSet = new ColumnSet(); 22 | columnSet.AddColumn(t => t.ReferenceTypeProperty); 23 | 24 | // Assert 25 | Assert.AreEqual(1, columnSet.Columns.Count); 26 | Assert.AreEqual(expectedColumn, columnSet.Columns[0]); 27 | } 28 | 29 | [TestMethod()] 30 | public void AddColumnsTest() 31 | { 32 | // Setup 33 | string expectedColumn1 = nameof(TestEntity.ReferenceTypeProperty).ToLower(); 34 | string expectedColumn2 = nameof(TestEntity.ValueTypeProperty).ToLower(); 35 | 36 | // Act 37 | ColumnSet columnSet = new ColumnSet(); 38 | columnSet.AddColumns(t => t.ReferenceTypeProperty, t=> t.ValueTypeProperty); 39 | 40 | // Assert 41 | Assert.AreEqual(2, columnSet.Columns.Count); 42 | Assert.AreEqual(expectedColumn1, columnSet.Columns[0]); 43 | Assert.AreEqual(expectedColumn2, columnSet.Columns[1]); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/ColumnSetTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace D365Extensions.Tests 11 | { 12 | [TestClass()] 13 | public class ColumnSetTests 14 | { 15 | [TestMethod()] 16 | public void Defaut_Constructor_Test() 17 | { 18 | // Act 19 | ColumnSet columnSet = new ColumnSet(); 20 | 21 | var query = new QueryExpression(); 22 | 23 | // Assert 24 | var actual = columnSet.Columns; 25 | 26 | Assert.AreEqual(0, actual.Count); 27 | } 28 | 29 | [TestMethod()] 30 | public void Implicit_Cast_Test() 31 | { 32 | // Setup 33 | string expected1 = nameof(TestEntity.ReferenceTypeProperty).ToLower(); 34 | string expected2 = nameof(TestEntity.ValueTypeProperty).ToLower(); 35 | 36 | // Act 37 | ColumnSet columnSet = new ColumnSet( 38 | t => t.ReferenceTypeProperty, 39 | t => t.ValueTypeProperty); 40 | 41 | var actual = columnSet.Columns; 42 | 43 | Assert.AreEqual(2, actual.Count); 44 | Assert.AreEqual(expected1, actual[0]); 45 | Assert.AreEqual(expected2, actual[1]); 46 | } 47 | 48 | [TestMethod()] 49 | public void ColumnSet_Test() 50 | { 51 | // Setup 52 | Expression> expected1 = (t) => t.ReferenceTypeProperty; 53 | Expression> expected2 = (t) => t.ValueTypeProperty; 54 | 55 | // Act 56 | ColumnSet columnSet = new ColumnSet( 57 | expected1, 58 | expected2); 59 | 60 | var actual = columnSet.Columns; 61 | 62 | Assert.AreEqual(2, actual.Count); 63 | Assert.AreEqual(expected1, actual[0]); 64 | Assert.AreEqual(expected2, actual[1]); 65 | } 66 | 67 | [TestMethod()] 68 | public void AddColumn_Test() 69 | { 70 | // Setup 71 | ColumnSet columnSet = new ColumnSet(); 72 | Expression> expected = (t) => t.ReferenceTypeProperty; 73 | 74 | // Act 75 | columnSet.AddColumn(t => t.ReferenceTypeProperty); 76 | var actual = columnSet.Columns; 77 | 78 | // Assert 79 | Assert.AreEqual(1, actual.Count); 80 | Assert.AreEqual(LogicalName.GetName(expected), LogicalName.GetName(actual[0])); 81 | } 82 | 83 | [TestMethod()] 84 | public void AddColumns_Test() 85 | { 86 | // Setup 87 | ColumnSet columnSet = new ColumnSet(); 88 | Expression> expected1 = (t) => t.ReferenceTypeProperty; 89 | Expression> expected2 = (t) => t.ValueTypeProperty; 90 | 91 | // Act 92 | columnSet.AddColumns(t => t.ReferenceTypeProperty, t => t.ValueTypeProperty); 93 | var actual = columnSet.Columns; 94 | 95 | // Assert 96 | Assert.AreEqual(2, actual.Count); 97 | Assert.AreEqual(LogicalName.GetName(expected1), LogicalName.GetName(actual[0])); 98 | Assert.AreEqual(LogicalName.GetName(expected2), LogicalName.GetName(actual[1])); 99 | } 100 | 101 | [TestMethod()] 102 | public void Null_Test() 103 | { 104 | // Setup 105 | ColumnSet columnSetT = null; 106 | 107 | // Act 108 | ColumnSet columnSet = columnSetT; 109 | 110 | // Assert 111 | Assert.IsNull(columnSet); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/ConditionExpressionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class ConditionExpressionTests 13 | { 14 | [TestMethod()] 15 | public void Constructor_Full_Test() 16 | { 17 | // Setup 18 | var expectedEntityName = TestEntity.EntityLogicalName; 19 | var expectedAttributeName = nameof(TestEntity.ValueTypeProperty).ToLower(); 20 | var expectedOperator = ConditionOperator.Equal; 21 | var expectedValue = new object[] { 42 }; 22 | 23 | // Act 24 | ConditionExpression condition = new ConditionExpression( 25 | expectedEntityName, 26 | t => t.ValueTypeProperty, 27 | expectedOperator, 28 | expectedValue); 29 | 30 | // Assert 31 | Assert.AreEqual(expectedEntityName, condition.EntityName); 32 | Assert.AreEqual(expectedAttributeName, condition.AttributeName); 33 | Assert.AreEqual(expectedOperator, condition.Operator); 34 | CollectionAssert.AreEqual(expectedValue, condition.Values.ToArray()); 35 | } 36 | 37 | [TestMethod()] 38 | public void Constructor_No_Entity_Name_Test() 39 | { 40 | // Setup 41 | var expectedAttributeName = nameof(TestEntity.ValueTypeProperty).ToLower(); 42 | var expectedOperator = ConditionOperator.Equal; 43 | var expectedValue = new object[] { 42 }; 44 | 45 | // Act 46 | ConditionExpression condition = new ConditionExpression( 47 | t => t.ValueTypeProperty, 48 | expectedOperator, 49 | expectedValue); 50 | 51 | // Assert 52 | Assert.IsNull(condition.EntityName); 53 | Assert.AreEqual(expectedAttributeName, condition.AttributeName); 54 | Assert.AreEqual(expectedOperator, condition.Operator); 55 | CollectionAssert.AreEqual(expectedValue, condition.Values.ToArray()); 56 | } 57 | 58 | [TestMethod()] 59 | public void Constructor_Single_Value_Test() 60 | { 61 | // Setup 62 | var expectedAttributeName = nameof(TestEntity.ValueTypeProperty).ToLower(); 63 | var expectedOperator = ConditionOperator.Equal; 64 | int expectedValue = 42; 65 | 66 | // Act 67 | ConditionExpression condition = new ConditionExpression( 68 | t => t.ValueTypeProperty, 69 | expectedOperator, 70 | expectedValue); 71 | 72 | // Assert 73 | Assert.IsNull(condition.EntityName); 74 | Assert.AreEqual(expectedAttributeName, condition.AttributeName); 75 | Assert.AreEqual(expectedOperator, condition.Operator); 76 | Assert.AreEqual(expectedValue, condition.Values[0]); 77 | } 78 | 79 | [TestMethod()] 80 | public void Constructor_Mimimal_Test() 81 | { 82 | // Setup 83 | var expectedAttributeName = nameof(TestEntity.ValueTypeProperty).ToLower(); 84 | var expectedOperator = ConditionOperator.Null; 85 | 86 | // Act 87 | ConditionExpression condition = new ConditionExpression( 88 | t => t.ValueTypeProperty, 89 | expectedOperator); 90 | 91 | // Assert 92 | Assert.IsNull(condition.EntityName); 93 | Assert.AreEqual(expectedAttributeName, condition.AttributeName); 94 | Assert.AreEqual(expectedOperator, condition.Operator); 95 | Assert.AreEqual(0, condition.Values.Count); 96 | } 97 | 98 | [TestMethod()] 99 | public void Constructor_Default_Test() 100 | { 101 | //Setup 102 | ConditionExpression conditionDefault = new ConditionExpression(); 103 | 104 | // Act 105 | ConditionExpression condition = new ConditionExpression(); 106 | 107 | // Assert 108 | Assert.AreEqual(conditionDefault.EntityName, condition.EntityName); 109 | Assert.AreEqual(conditionDefault.AttributeName, condition.AttributeName); 110 | Assert.AreEqual(conditionDefault.Operator, condition.Operator); 111 | Assert.AreEqual(0, condition.Values.Count); 112 | } 113 | 114 | [TestMethod()] 115 | public void Null_Test() 116 | { 117 | // Setup 118 | ConditionExpression conditionT = null; 119 | 120 | // Act 121 | ConditionExpression condition = conditionT; 122 | 123 | // Assert not throw 124 | Assert.IsNull(condition); 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/D365Extensions.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net452 4 | latest 5 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 6 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 7 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 8 | False 9 | UnitTest 10 | D365Extensions.Tests 11 | D365Extensions.Tests 12 | Copyright © 2018 13 | 14 | 15 | true 16 | 17 | 18 | FixRM.snk 19 | 20 | 21 | 1701;1702;CS0618 22 | 23 | 24 | 1701;1702;CS0618 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/DataCollectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class DataCollectionExtensionsTests 13 | { 14 | [TestMethod()] 15 | public void GetValueTest() 16 | { 17 | // Setup 18 | string expectedSring = "Artem"; 19 | int? expectedInt = 36; 20 | DateTime? expectedDateTime = new DateTime(1985, 8, 8); 21 | 22 | ParameterCollection paramCollection = new ParameterCollection(); 23 | paramCollection.Add("name", expectedSring); 24 | paramCollection.Add("age", expectedInt); 25 | paramCollection.Add("birthdate", expectedDateTime); 26 | 27 | // Act 28 | string actualString = paramCollection.GetValue("name"); 29 | int? actualInt = paramCollection.GetValue("age"); 30 | DateTime? actualDateTime = paramCollection.GetValue("birthdate"); 31 | 32 | //Assert 33 | Assert.AreEqual(expectedSring, actualString); 34 | Assert.AreEqual(expectedInt, actualInt); 35 | Assert.AreEqual(expectedDateTime, actualDateTime); 36 | 37 | Assert.IsNull(paramCollection.GetValue("notexisting")); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/EntityCollectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using D365Extensions.Tests.Entities; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Microsoft.Xrm.Sdk; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace D365Extensions.Tests 12 | { 13 | [TestClass()] 14 | public class EntityCollectionExtensionsTests 15 | { 16 | [TestMethod()] 17 | public void ContainsAddressTest() 18 | { 19 | // Setup 20 | var expectedEmail1 = "artem@grunin.ru"; 21 | var expectedEmail2 = "doesnt@matter.com"; 22 | 23 | var party1 = new ActivityParty() 24 | { 25 | AddressUsed = expectedEmail1 26 | }; 27 | 28 | var party2 = new ActivityParty() 29 | { 30 | AddressUsed = expectedEmail2.ToUpper() 31 | }; 32 | 33 | var collection = new EntityCollection(); 34 | collection.EntityName = ActivityParty.EntityLogicalName; 35 | collection.Entities.Add(party1); 36 | collection.Entities.Add(party2); 37 | 38 | // Act + Assert 39 | Assert.IsTrue(collection.ContainsAddress(expectedEmail1)); 40 | Assert.IsTrue(collection.ContainsAddress(expectedEmail2)); 41 | } 42 | 43 | [TestMethod()] 44 | public void ContainsAddressShouldThrowTest() 45 | { 46 | var collection = new EntityCollection(); 47 | collection.EntityName = Account.EntityLogicalName; 48 | 49 | var error = Assert.ThrowsException(()=> collection.ContainsAddress("any")); 50 | Assert.AreEqual(CheckParam.InvalidCollectionMessage, error.Message); 51 | } 52 | 53 | [TestMethod()] 54 | public void InDomainTest() 55 | { 56 | // Setup 57 | var expectedDomain = "grunin.ru"; 58 | 59 | var party1 = new ActivityParty() 60 | { 61 | Id = Guid.NewGuid(), 62 | AddressUsed = $"artem@{expectedDomain}" 63 | }; 64 | 65 | var party2 = new ActivityParty() 66 | { 67 | Id = Guid.NewGuid(), 68 | AddressUsed = "doesnt@matter.com" 69 | }; 70 | 71 | var collection = new EntityCollection(); 72 | collection.EntityName = ActivityParty.EntityLogicalName; 73 | collection.Entities.Add(party1); 74 | collection.Entities.Add(party2); 75 | 76 | // Act 77 | var inDomainParties = collection.GetPartiesInDomain(expectedDomain); 78 | 79 | // Assert 80 | Assert.AreEqual(ActivityParty.EntityLogicalName, inDomainParties.EntityName); 81 | Assert.AreEqual(1, inDomainParties.Entities.Count); 82 | Assert.AreEqual(party1.Id, inDomainParties.Entities[0].Id); 83 | } 84 | 85 | [TestMethod()] 86 | public void InDomainShouldThrowTest() 87 | { 88 | var collection = new EntityCollection(); 89 | collection.EntityName = Account.EntityLogicalName; 90 | 91 | var error = Assert.ThrowsException(() => collection.GetPartiesInDomain("any")); 92 | Assert.AreEqual(CheckParam.InvalidCollectionMessage, error.Message); 93 | } 94 | 95 | [TestMethod()] 96 | public void NotInDomainTest() 97 | { 98 | // Setup 99 | var expectedDomain = "grunin.ru"; 100 | 101 | var party1 = new ActivityParty() 102 | { 103 | Id = Guid.NewGuid(), 104 | AddressUsed = $"artem@{expectedDomain}" 105 | }; 106 | 107 | var party2 = new ActivityParty() 108 | { 109 | Id = Guid.NewGuid(), 110 | AddressUsed = "doesnt@matter.com" 111 | }; 112 | 113 | var collection = new EntityCollection(); 114 | collection.EntityName = ActivityParty.EntityLogicalName; 115 | collection.Entities.Add(party1); 116 | collection.Entities.Add(party2); 117 | 118 | // Act 119 | var notInDomainParties = collection.GetPartiesNotInDomain(expectedDomain); 120 | 121 | // Assert 122 | Assert.AreEqual(ActivityParty.EntityLogicalName, notInDomainParties.EntityName); 123 | Assert.AreEqual(1, notInDomainParties.Entities.Count); 124 | Assert.AreEqual(party2.Id, notInDomainParties.Entities[0].Id); 125 | } 126 | 127 | [TestMethod()] 128 | public void NotInDomainShouldThrowTest() 129 | { 130 | var collection = new EntityCollection(); 131 | collection.EntityName = Account.EntityLogicalName; 132 | 133 | var error = Assert.ThrowsException(() => collection.GetPartiesNotInDomain("any")); 134 | Assert.AreEqual(CheckParam.InvalidCollectionMessage, error.Message); 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/EntityIdComparer.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS0618 // Type or member is obsolete 2 | 3 | using Microsoft.Xrm.Sdk; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | namespace D365Extensions.Tests 8 | { 9 | class EntityIdComparer : IComparer, IComparer 10 | { 11 | public int Compare(Entity x, Entity y) 12 | { 13 | return x.Id.CompareTo(y.Id); 14 | } 15 | 16 | public int Compare(object x, object y) 17 | { 18 | return this.Compare(x as Entity, y as Entity); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/EntityImageCollectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions.Tests.Entities; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Microsoft.Xrm.Sdk; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace D365Extensions.Tests 11 | { 12 | [TestClass()] 13 | public class EntityImageCollectionExtensionsTests 14 | { 15 | [TestMethod()] 16 | public void Should_Return_Null_Test() 17 | { 18 | 19 | // Setup 20 | var images = new EntityImageCollection(); 21 | 22 | // Act 23 | var nullImage = images.GetImage("PreImage"); 24 | 25 | // Assert 26 | Assert.IsNull(nullImage); 27 | } 28 | 29 | [TestMethod()] 30 | public void Should_Return_Image_Test() 31 | { 32 | // Setup 33 | var image = new Entity() 34 | { 35 | Id = Guid.NewGuid(), 36 | LogicalName = Account.EntityLogicalName, 37 | [nameof(Account.Name).ToLower()] = "Test Account" 38 | }; 39 | 40 | var expectedImageName = "PreImage"; 41 | 42 | var images = new EntityImageCollection 43 | { 44 | { expectedImageName, image } 45 | }; 46 | 47 | // Act 48 | var actualImage = images.GetImage(expectedImageName); 49 | 50 | // Assert 51 | Assert.IsNotNull(actualImage); 52 | 53 | Assert.AreEqual(image.Id, actualImage.Id); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/EntityReferenceExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions.Tests.Entities; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Microsoft.Xrm.Sdk; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace D365Extensions.Tests 11 | { 12 | [TestClass()] 13 | public class EntityReferenceExtensionsTests 14 | { 15 | [TestMethod()] 16 | public void ToEntityTest() 17 | { 18 | // Setup 19 | var reference = new EntityReference() 20 | { 21 | Id = Guid.NewGuid(), 22 | LogicalName = "account", 23 | Name = "FixRM", 24 | KeyAttributes = 25 | { 26 | { "code", "123" } 27 | }, 28 | RowVersion = "456" 29 | }; 30 | 31 | // Act 32 | var entity = reference.ToEntity(); 33 | 34 | // Assert 35 | Assert.AreEqual(reference.Id, entity.Id); 36 | Assert.AreEqual(reference.LogicalName, entity.LogicalName); 37 | Assert.AreEqual(reference.KeyAttributes, entity.KeyAttributes); 38 | Assert.AreEqual(reference.RowVersion, entity.RowVersion); 39 | } 40 | 41 | [TestMethod()] 42 | public void ToEntityTTest() 43 | { 44 | // Setup 45 | var reference = new EntityReference() 46 | { 47 | Id = Guid.NewGuid(), 48 | LogicalName = "account", 49 | Name = "FixRM", 50 | KeyAttributes = 51 | { 52 | { "code", "123" } 53 | }, 54 | RowVersion = "456" 55 | }; 56 | 57 | // Act 58 | Account entity = reference.ToEntity(); 59 | 60 | // Assert 61 | Assert.AreEqual(reference.Id, entity.Id); 62 | Assert.AreEqual(reference.LogicalName, entity.LogicalName); 63 | Assert.AreEqual(reference.KeyAttributes, entity.KeyAttributes); 64 | Assert.AreEqual(reference.RowVersion, entity.RowVersion); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/EntityReferenceTests.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions.Tests.Entities; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Microsoft.Xrm.Sdk; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace D365Extensions.Tests 11 | { 12 | [TestClass()] 13 | public class EntityReferenceTests 14 | { 15 | [TestMethod()] 16 | public void ConstructorTest() 17 | { 18 | // Act 19 | EntityReference reference = new EntityReference(); 20 | 21 | // Assert 22 | Assert.AreEqual(Account.EntityLogicalName, reference.LogicalName); 23 | } 24 | 25 | [TestMethod()] 26 | public void Constructor2Test() 27 | { 28 | // Setup 29 | var expectedId = Guid.NewGuid(); 30 | 31 | // Act 32 | EntityReference reference = new EntityReference(expectedId); 33 | 34 | // Assert 35 | Assert.AreEqual(Account.EntityLogicalName, reference.LogicalName); 36 | Assert.AreEqual(expectedId, reference.Id); 37 | } 38 | 39 | [TestMethod()] 40 | public void Constructor3Test() 41 | { 42 | // Setup 43 | var expectedKeys = new KeyAttributeCollection(); 44 | 45 | // Act 46 | EntityReference reference = new EntityReference(expectedKeys); 47 | 48 | // Assert 49 | Assert.AreEqual(Account.EntityLogicalName, reference.LogicalName); 50 | Assert.AreEqual(expectedKeys, reference.KeyAttributes); 51 | } 52 | 53 | [TestMethod()] 54 | public void Constructor4Test() 55 | { 56 | // Setup 57 | var expectedKey = "accountnumber"; 58 | var expectedValue = "123"; 59 | 60 | // Act 61 | EntityReference reference = new EntityReference(a=> a.AccountNumber, expectedValue); 62 | var actualKey = reference.KeyAttributes.Single(); 63 | 64 | // Assert 65 | Assert.AreEqual(Account.EntityLogicalName, reference.LogicalName); 66 | Assert.AreEqual(expectedKey, actualKey.Key); 67 | Assert.AreEqual(expectedValue, actualKey.Value); 68 | } 69 | 70 | [TestMethod()] 71 | public void EqualsTest() 72 | { 73 | // Setup 74 | var id = Guid.NewGuid(); 75 | var rowVersion = "123"; 76 | var name = "FixRM"; 77 | var key = "accountnumber"; 78 | var value = "42"; 79 | 80 | var reference1 = new EntityReference() 81 | { 82 | Id = id, 83 | Name = name, 84 | LogicalName = Account.EntityLogicalName, 85 | RowVersion = rowVersion, 86 | KeyAttributes = 87 | { 88 | { key, value } 89 | } 90 | }; 91 | 92 | var reference2 = new EntityReference() 93 | { 94 | Id = id, 95 | Name = name, 96 | RowVersion = rowVersion, 97 | KeyAttributes = 98 | { 99 | { key, value } 100 | } 101 | }; 102 | 103 | // Act + Assert 104 | Assert.IsTrue(reference1.Equals((EntityReference) reference2)); 105 | Assert.IsTrue(reference2.Equals(reference1)); 106 | } 107 | 108 | [TestMethod()] 109 | public void GetHashCodeTest() 110 | { 111 | // Setup 112 | var id = Guid.NewGuid(); 113 | var rowVersion = "123"; 114 | var name = "FixRM"; 115 | var key = "accountnumber"; 116 | var value = "42"; 117 | 118 | var reference1 = new EntityReference() 119 | { 120 | Id = id, 121 | Name = name, 122 | LogicalName = Account.EntityLogicalName, 123 | RowVersion = rowVersion, 124 | KeyAttributes = 125 | { 126 | { key, value } 127 | } 128 | }; 129 | 130 | var reference2 = new EntityReference() 131 | { 132 | Id = id, 133 | Name = name, 134 | RowVersion = rowVersion, 135 | KeyAttributes = 136 | { 137 | { key, value } 138 | } 139 | }; 140 | 141 | // Act 142 | var code1 = reference1.GetHashCode(); 143 | var code2 = reference2.GetHashCode(); 144 | 145 | // Assert 146 | Assert.AreEqual(code1, code2); 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/ExecuteMultipleProgressTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | using System.Data.Common; 7 | using System.Text; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class ExecuteMultipleProgressTests 13 | { 14 | [DataTestMethod] 15 | [DataRow(100.0F, 0, 1000, 10000, DisplayName = "Progress should show 100% if 0 is queried test")] 16 | [DataRow(25.0F, 100, 25, 0, DisplayName = "Progress should show 25% if 100 is queried and 25 processed test")] 17 | [DataRow(50.0F, 100, 25, 25, DisplayName = "Progress should show 50% if 100 is queried and 25 processed and 25 skipped test")] 18 | [DataRow(100.0F, 100, 100, 0, DisplayName = "Progress should show 100% if 100 is queried and 100 processed test")] 19 | [DataRow(100.0F, 100, 0, 100, DisplayName = "Progress should show 100% if 100 is queried and 100 skipped test")] 20 | public void ProgressTest(float expectedProgress, int queried, int processed, int skipped) 21 | { 22 | // Setup 23 | const uint errors = 200000000; 24 | 25 | // Act 26 | var emProgress = new ExecuteMultipleProgress((uint)queried, (uint)processed, (uint)skipped, errors); 27 | 28 | // Assert 29 | Assert.AreEqual(expectedProgress, emProgress.Progress); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/FakeExecuteMultipleExecutor.cs: -------------------------------------------------------------------------------- 1 | using FakeXrmEasy; 2 | using FakeXrmEasy.FakeMessageExecutors; 3 | using Microsoft.Xrm.Sdk; 4 | using Microsoft.Xrm.Sdk.Messages; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Runtime; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace D365Extensions.Tests 13 | { 14 | /// 15 | /// To fake OOB behavior 16 | /// https://learn.microsoft.com/en-us/power-apps/developer/data-platform/org-service/execute-multiple-requests 17 | /// 18 | internal class FakeExecuteMultipleExecutor : IFakeMessageExecutor 19 | { 20 | private const string NameOfFaled = "IShouldFail"; 21 | 22 | private const string ResponceIdKey = "ResponseId"; 23 | 24 | public static OrganizationRequest GoodRequest => new OrganizationRequest() { RequestId = Guid.NewGuid() }; 25 | 26 | public static OrganizationRequest FailRequest => new OrganizationRequest() { RequestId = Guid.NewGuid(), RequestName = NameOfFaled }; 27 | 28 | public List ActualRequests { get; } = new List(); 29 | 30 | public List Responses { get; } = new List(); 31 | 32 | public static Guid GetRequestId(ExecuteMultipleResponseItem item) 33 | { 34 | if (item.IsFaulted()) 35 | { 36 | return Guid.Parse(item.Fault.Message); 37 | } 38 | 39 | return (Guid)item.Response[ResponceIdKey]; 40 | } 41 | 42 | public bool CanExecute(OrganizationRequest request) 43 | { 44 | return request is ExecuteMultipleRequest; 45 | } 46 | 47 | public bool IsFailed(OrganizationRequest orgRequest) 48 | { 49 | return orgRequest.RequestName == NameOfFaled; 50 | } 51 | 52 | public OrganizationResponse Execute(OrganizationRequest request, XrmFakedContext ctx) 53 | { 54 | var eMultipleRequest = request as ExecuteMultipleRequest; 55 | ActualRequests.Add(eMultipleRequest); 56 | 57 | var eMultipleResponce = new ExecuteMultipleResponse() 58 | { 59 | [nameof(ExecuteMultipleResponse.Responses)] = new ExecuteMultipleResponseItemCollection() 60 | }; 61 | Responses.Add(eMultipleResponce); 62 | 63 | for (var i = 0; i < eMultipleRequest.Requests.Count; i++) 64 | { 65 | var orgRequest = eMultipleRequest.Requests[i]; 66 | 67 | if (!IsFailed(orgRequest)) 68 | { 69 | //should return good responses 70 | if (eMultipleRequest.Settings.ReturnResponses) 71 | { 72 | //add response to results 73 | eMultipleResponce.Responses.Add(new ExecuteMultipleResponseItem 74 | { 75 | RequestIndex = i, 76 | Response = new OrganizationResponse() 77 | { 78 | //to simplify request-responce mathing in a test 79 | [ResponceIdKey] = orgRequest.RequestId 80 | } 81 | }); 82 | } 83 | } 84 | else 85 | { 86 | //should always return errors 87 | eMultipleResponce.Responses.Add(new ExecuteMultipleResponseItem 88 | { 89 | RequestIndex = i, 90 | Fault = new OrganizationServiceFault() 91 | { 92 | //to simplify request-responce mathing in a test 93 | Message = orgRequest.RequestId.ToString() 94 | } 95 | }); 96 | 97 | //should mark whole ExecuteMultipleResponse as faulted 98 | eMultipleResponce[nameof(ExecuteMultipleResponse.IsFaulted)] = true; 99 | 100 | // Should stop fake execution 101 | if (!eMultipleRequest.Settings.ContinueOnError) 102 | break; 103 | } 104 | } 105 | 106 | return eMultipleResponce; 107 | } 108 | 109 | public Type GetResponsibleRequestType() 110 | { 111 | return typeof(ExecuteMultipleRequest); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/FilterExpressionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class FilterExpressionExtensionsTests 13 | { 14 | [TestMethod()] 15 | public void AddCondition1Test() 16 | { 17 | // Setup 18 | FilterExpression filter = new FilterExpression(); 19 | 20 | var expectedAttribute = nameof(TestEntity.ReferenceTypeProperty).ToLower(); 21 | var expectedOperator = ConditionOperator.Equal; 22 | var expectedValue = "not used"; 23 | 24 | // Act 25 | filter.AddCondition(t => t.ReferenceTypeProperty, expectedOperator, expectedValue); 26 | 27 | // Assert 28 | Assert.AreEqual(1, filter.Conditions.Count); 29 | ConditionExpression actualtCondition = filter.Conditions[0]; 30 | 31 | Assert.AreEqual(expectedAttribute, actualtCondition.AttributeName); 32 | Assert.AreEqual(expectedOperator, actualtCondition.Operator); 33 | Assert.AreEqual(expectedValue, actualtCondition.Values[0]); 34 | } 35 | 36 | [TestMethod()] 37 | public void AddCondition2Test() 38 | { 39 | // Setup 40 | FilterExpression filter = new FilterExpression(); 41 | 42 | var expectedEntityName = TestEntity.EntityLogicalName; 43 | var expectedAttribute = nameof(TestEntity.ReferenceTypeProperty).ToLower(); 44 | var expectedOperator = ConditionOperator.Equal; 45 | var expectedValue = "not used"; 46 | 47 | // Act 48 | filter.AddCondition(t => t.ReferenceTypeProperty, expectedOperator, expectedValue); 49 | 50 | // Assert 51 | Assert.AreEqual(1, filter.Conditions.Count); 52 | ConditionExpression actualtCondition = filter.Conditions[0]; 53 | 54 | Assert.AreEqual(expectedAttribute, actualtCondition.AttributeName); 55 | Assert.AreEqual(expectedOperator, actualtCondition.Operator); 56 | Assert.AreEqual(expectedValue, actualtCondition.Values[0]); 57 | Assert.AreEqual(expectedEntityName, actualtCondition.EntityName); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/IServiceProviderExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FakeItEasy; 2 | using FakeXrmEasy; 3 | using FakeXrmEasy.Extensions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Microsoft.Xrm.Sdk; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace D365Extensions.Tests 13 | { 14 | [TestClass()] 15 | public class IServiceProviderExtensionsTests 16 | { 17 | [TestMethod()] 18 | public void GetPluginExecutionContextTest() 19 | { 20 | // Setup 21 | var expectedPluginExecutionContext = A.Fake(); 22 | 23 | var serviceProvider = A.Fake(); 24 | 25 | var call = A.CallTo(()=> serviceProvider.GetService(A.That.IsEqualTo(typeof(IPluginExecutionContext)))); 26 | call.Returns(expectedPluginExecutionContext); 27 | 28 | // Act 29 | var actualPluginExecutionContext = serviceProvider.GetPluginExecutionContext(); 30 | 31 | // Assert 32 | Assert.AreEqual(expectedPluginExecutionContext, actualPluginExecutionContext); 33 | 34 | call.MustHaveHappenedOnceExactly(); 35 | } 36 | 37 | [TestMethod()] 38 | public void GetOrganizationServiceFactoryTest() 39 | { 40 | // Setup 41 | var expectedOrgServiceFactory = A.Fake(); 42 | 43 | var serviceProvider = A.Fake(); 44 | 45 | var call = A.CallTo(() => serviceProvider.GetService(A.That.IsEqualTo(typeof(IOrganizationServiceFactory)))); 46 | call.Returns(expectedOrgServiceFactory); 47 | 48 | // Act 49 | var actualOrgServiceFactory = serviceProvider.GetOrganizationServiceFactory(); 50 | 51 | // Assert 52 | Assert.AreEqual(expectedOrgServiceFactory, actualOrgServiceFactory); 53 | 54 | call.MustHaveHappenedOnceExactly(); 55 | } 56 | 57 | [TestMethod()] 58 | public void GetTracingServiceTest() 59 | { 60 | // Setup 61 | var expectedTracingService = A.Fake(); 62 | 63 | var serviceProvider = A.Fake(); 64 | 65 | var call = A.CallTo(() => serviceProvider.GetService(A.That.IsEqualTo(typeof(ITracingService)))); 66 | call.Returns(expectedTracingService); 67 | 68 | // Act 69 | var actualTracingService = serviceProvider.GetTracingService(); 70 | 71 | // Assert 72 | Assert.AreEqual(expectedTracingService, actualTracingService); 73 | 74 | call.MustHaveHappenedOnceExactly(); 75 | } 76 | 77 | [TestMethod()] 78 | public void GetPluginExecutionTraceContextTest() 79 | { 80 | // Setup 81 | var expectedTracingService = A.Fake(); 82 | var expectedPluginExecutionContext = A.Fake(); 83 | 84 | var serviceProvider = A.Fake(); 85 | 86 | var callGetTracingService = A.CallTo(() => serviceProvider.GetService(A.That.IsEqualTo(typeof(ITracingService)))); 87 | callGetTracingService.Returns(expectedTracingService); 88 | 89 | var callGetPluginContext = A.CallTo(() => serviceProvider.GetService(A.That.IsEqualTo(typeof(IPluginExecutionContext)))); 90 | callGetPluginContext.Returns(expectedPluginExecutionContext); 91 | 92 | var expectedSettings = new PluginExecutionTraceContextSettings(); 93 | 94 | // Act 95 | var traceContext = serviceProvider.GetPluginExecutionTraceContext(expectedSettings); 96 | 97 | // Assert 98 | Assert.AreEqual(expectedPluginExecutionContext, traceContext.PluginExecutionContext); 99 | Assert.AreEqual(expectedTracingService, traceContext.TracingService); 100 | Assert.AreEqual(expectedSettings, traceContext.Settings); 101 | 102 | callGetTracingService.MustHaveHappenedOnceExactly(); 103 | callGetPluginContext.MustHaveHappenedOnceExactly(); 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/KeyAttributeCollectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions.Tests.Entities; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Microsoft.Xrm.Sdk; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace D365Extensions.Tests 11 | { 12 | [TestClass()] 13 | public class KeyAttributeCollectionExtensionsTests 14 | { 15 | [TestMethod()] 16 | public void AddTest() 17 | { 18 | // Setup 19 | const string expectedKey = "accountnumber"; 20 | const string expectedValue = "42"; 21 | 22 | var keyAttributeCollection = new KeyAttributeCollection(); 23 | 24 | // Act 25 | keyAttributeCollection.Add(a => a.AccountNumber, expectedValue); 26 | var actualKeyAttributes = keyAttributeCollection.SingleOrDefault(); 27 | 28 | // Assert 29 | Assert.AreEqual(expectedKey, actualKeyAttributes.Key); 30 | Assert.AreEqual(expectedValue, actualKeyAttributes.Value); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/KeyAttributeCollectionTests.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions.Tests.Entities; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Microsoft.Xrm.Sdk; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace D365Extensions.Tests 11 | { 12 | [TestClass()] 13 | public class KeyAttributeCollectionTests 14 | { 15 | [TestMethod()] 16 | public void KeyAttributeCollectionTest() 17 | { 18 | // Setup 19 | var expectedKey = "name"; 20 | var expectedValue = "FixRM"; 21 | 22 | var keys = new KeyAttributeCollection 23 | { 24 | { a => a.Name, expectedValue} 25 | }; 26 | 27 | // Act 28 | KeyAttributeCollection actualKeys = keys; 29 | var kv = actualKeys.Single(); 30 | 31 | // Assert 32 | Assert.AreEqual(expectedKey, kv.Key); 33 | Assert.AreEqual(expectedValue, kv.Value); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/LinkEntityExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class LinkEntityExtensionsTests 13 | { 14 | [TestMethod()] 15 | public void AddLink1Test() 16 | { 17 | // Setup 18 | string expectedFromEntityName = EntityFrom.EnityLogicalName; 19 | string expectedToEntityName = EntityTo.EnityLogicalName; 20 | string expectedFromAttributName = nameof(EntityFrom.FromId).ToLower(); 21 | string expectedToAttributName = nameof(EntityTo.ToId).ToLower(); 22 | JoinOperator expectedOperator = JoinOperator.LeftOuter; 23 | 24 | LinkEntity linkEntity = new LinkEntity() 25 | { 26 | LinkFromEntityName = expectedFromEntityName 27 | }; 28 | 29 | // Act 30 | LinkEntity newLink = linkEntity.AddLink( 31 | EntityTo.EnityLogicalName, 32 | f=> f.FromId, 33 | t=> t.ToId, 34 | expectedOperator); 35 | 36 | // Assert 37 | Assert.AreEqual(expectedFromEntityName, newLink.LinkFromEntityName); 38 | Assert.AreEqual(expectedToEntityName, newLink.LinkToEntityName); 39 | Assert.AreEqual(expectedToAttributName, newLink.LinkToAttributeName); 40 | Assert.AreEqual(expectedFromAttributName, newLink.LinkFromAttributeName); 41 | Assert.AreEqual(expectedOperator, newLink.JoinOperator); 42 | } 43 | 44 | [TestMethod()] 45 | public void AddLink2Test() 46 | { 47 | // Setup 48 | string expectedFromEntityName = EntityFrom.EnityLogicalName; 49 | string expectedToEntityName = EntityTo.EnityLogicalName; 50 | string expectedFromAttributName = nameof(EntityFrom.FromId).ToLower(); 51 | string expectedToAttributName = nameof(EntityTo.ToId).ToLower(); 52 | 53 | LinkEntity linkEntity = new LinkEntity() 54 | { 55 | LinkFromEntityName = expectedFromEntityName 56 | }; 57 | 58 | // Act 59 | LinkEntity newLink = linkEntity.AddLink( 60 | EntityTo.EnityLogicalName, 61 | f => f.FromId, 62 | t => t.ToId); 63 | 64 | // Assert 65 | Assert.AreEqual(expectedFromEntityName, newLink.LinkFromEntityName); 66 | Assert.AreEqual(expectedToEntityName, newLink.LinkToEntityName); 67 | Assert.AreEqual(expectedToAttributName, newLink.LinkToAttributeName); 68 | Assert.AreEqual(expectedFromAttributName, newLink.LinkFromAttributeName); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/LinkEntityTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class LinkEntityTests 13 | { 14 | [TestMethod()] 15 | public void LinkEntityTest() 16 | { 17 | // Setup 18 | string expectedFromEntityName = EntityFrom.EnityLogicalName; 19 | string expectedToEntityName = EntityTo.EnityLogicalName; 20 | string expectedFromAttributName = nameof(EntityFrom.FromId).ToLower(); 21 | string expectedToAttributName = nameof(EntityTo.ToId).ToLower(); 22 | JoinOperator expectedOperator = JoinOperator.LeftOuter; 23 | string expectedAlias = "Alias"; 24 | ColumnSet expectedColumnSet = new ColumnSet(); 25 | FilterExpression expectedFilter = new FilterExpression(); 26 | OrderExpression expectedOrder = new OrderExpression(); 27 | LinkEntity expectedLink = new LinkEntity(); 28 | 29 | // Act 30 | LinkEntity linkGen = new LinkEntity( 31 | f => f.FromId, 32 | t => t.ToId, 33 | expectedOperator); 34 | linkGen.EntityAlias = expectedAlias; 35 | linkGen.Columns = expectedColumnSet; 36 | linkGen.LinkCriteria = expectedFilter; 37 | linkGen.Orders.Add(expectedOrder); 38 | linkGen.LinkEntities.Add(expectedLink); 39 | 40 | // implicit cast 41 | LinkEntity linkEntity = linkGen; 42 | 43 | // Assert 44 | Assert.AreEqual(expectedFromEntityName, linkEntity.LinkFromEntityName); 45 | Assert.AreEqual(expectedToEntityName, linkEntity.LinkToEntityName); 46 | Assert.AreEqual(expectedFromAttributName, linkEntity.LinkFromAttributeName); 47 | Assert.AreEqual(expectedToAttributName, linkEntity.LinkToAttributeName); 48 | Assert.AreEqual(expectedOperator, linkEntity.JoinOperator); 49 | Assert.AreEqual(expectedAlias, linkEntity.EntityAlias); 50 | Assert.AreEqual(expectedColumnSet, linkEntity.Columns); 51 | Assert.AreEqual(expectedFilter, linkEntity.LinkCriteria); 52 | Assert.AreEqual(1, linkEntity.Orders.Count); 53 | Assert.AreEqual(expectedOrder, linkEntity.Orders[0]); 54 | Assert.AreEqual(1, linkEntity.LinkEntities.Count); 55 | Assert.AreEqual(expectedLink, linkEntity.LinkEntities[0]); 56 | } 57 | 58 | [TestMethod()] 59 | public void LinkEntity_Default_Test() 60 | { 61 | // Setup 62 | JoinOperator expectedOperator = JoinOperator.Inner; 63 | 64 | // Act 65 | LinkEntity linkEntity = new LinkEntity(); 66 | 67 | // Assert 68 | Assert.AreEqual(expectedOperator, linkEntity.JoinOperator); 69 | } 70 | 71 | [TestMethod()] 72 | public void Null_Test() 73 | { 74 | // Setup 75 | LinkEntity linkEntityT = null; 76 | 77 | // Act 78 | LinkEntity linkEntity = linkEntityT; 79 | 80 | // Assert 81 | Assert.IsNull(linkEntity); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/OptionSetValueTests.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions.Tests; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Microsoft.Xrm.Sdk; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace D365Extensions.Tests 11 | { 12 | [TestClass()] 13 | public class OptionSetValueTests 14 | { 15 | [TestMethod()] 16 | public void OptionSetValueTest() 17 | { 18 | // Setup 19 | var expectedValue = StateCode.Active; 20 | 21 | // Act 22 | OptionSetValue optionSet = new OptionSetValue(expectedValue); 23 | 24 | // Assert 25 | Assert.AreEqual((int) expectedValue, optionSet.Value); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/OrderExpressionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class OrderExpressionTests 13 | { 14 | [TestMethod()] 15 | public void OrderExpressionTest() 16 | { 17 | // Setup 18 | var expectedAttributeName = nameof(TestEntity.ValueTypeProperty).ToLower(); 19 | var expectedOrderType = OrderType.Descending; 20 | 21 | // Act 22 | OrderExpression order = new OrderExpression(t => t.ValueTypeProperty, expectedOrderType); 23 | 24 | // Assert 25 | Assert.AreEqual(expectedAttributeName, order.AttributeName); 26 | Assert.AreEqual(expectedOrderType, order.OrderType); 27 | } 28 | 29 | [TestMethod()] 30 | public void OrderExpression_Default_Test() 31 | { 32 | //Setup 33 | var orderDefault = new OrderExpression(); 34 | // Act 35 | OrderExpression order = new OrderExpression(); 36 | 37 | // Assert 38 | Assert.AreEqual(orderDefault.AttributeName, order.AttributeName); 39 | Assert.AreEqual(orderDefault.OrderType, order.OrderType); 40 | } 41 | 42 | [TestMethod()] 43 | public void Null_Test() 44 | { 45 | //Setup 46 | OrderExpression orderT = null; 47 | // Act 48 | OrderExpression order = orderT; 49 | 50 | // Assert not throw 51 | Assert.IsNull(order); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/OrganizationRequestCollectionEnumeratorTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.Xrm.Sdk; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class OrganizationRequestCollectionEnumeratorTests 13 | { 14 | [TestMethod()] 15 | public void Should_Throw_If_Incorrect_Size_Test() 16 | { 17 | //Setup 18 | var collection = new List(); 19 | var size = -10; 20 | 21 | //Act + Assert 22 | Assert.ThrowsException(()=> collection.Chunk(size)); 23 | } 24 | 25 | [TestMethod()] 26 | public void Should_Retun_Zero_Length_Test() 27 | { 28 | //Setup 29 | var collection = new List(); 30 | var size = 10; 31 | 32 | //Act 33 | var result = collection.Chunk(size).ToList(); 34 | 35 | //Assert 36 | Assert.AreEqual(0, result.Count); 37 | } 38 | 39 | [TestMethod()] 40 | public void Should_Return_Whole_Collection_Of_Same_SizeTest() 41 | { 42 | //Setup 43 | var collection = new List() 44 | { 45 | new OrganizationRequest(), 46 | new OrganizationRequest(), 47 | new OrganizationRequest(), 48 | new OrganizationRequest(), 49 | new OrganizationRequest(), 50 | }; 51 | 52 | var size = collection.Count; 53 | 54 | //Act 55 | var result = collection.Chunk(size).ToList(); 56 | 57 | //Assert 58 | Assert.AreEqual(1, result.Count); 59 | CollectionAssert.AreEqual(collection, result[0]); 60 | } 61 | 62 | 63 | [TestMethod()] 64 | public void Should_Return_Whole_Collection_Test() 65 | { 66 | //Setup 67 | var collection = new List() 68 | { 69 | new OrganizationRequest(), 70 | new OrganizationRequest(), 71 | new OrganizationRequest(), 72 | new OrganizationRequest(), 73 | new OrganizationRequest(), 74 | }; 75 | 76 | var size = collection.Count + 1; 77 | 78 | //Act 79 | var result = collection.Chunk(size).ToList(); 80 | 81 | //Assert 82 | Assert.AreEqual(1, result.Count); 83 | CollectionAssert.AreEqual(collection, result[0]); 84 | } 85 | 86 | [TestMethod()] 87 | public void Should_Split_Collection_On_Two_Equal_Parts() 88 | { 89 | //Setup 90 | var collection = new List() 91 | { 92 | new OrganizationRequest(), 93 | new OrganizationRequest(), 94 | new OrganizationRequest(), 95 | new OrganizationRequest(), 96 | new OrganizationRequest(), 97 | new OrganizationRequest(), 98 | }; 99 | 100 | var size = collection.Count / 2; 101 | 102 | //We dont use Skip+Take in real method as they iterate over collection from the beginning for each attempt 103 | List expectedChunk1 = collection.Take(size).ToList(); 104 | List expectedChunk2 = collection.Skip(size).Take(size).ToList(); 105 | 106 | //Act 107 | var result = collection.Chunk(size).ToList(); 108 | var actualChunk1 = result[0]; 109 | var actualChunk2 = result[1]; 110 | 111 | //Assert 112 | Assert.AreEqual(2, result.Count); 113 | CollectionAssert.AreEqual(expectedChunk1, actualChunk1); 114 | CollectionAssert.AreEqual(expectedChunk2, actualChunk2); 115 | } 116 | 117 | [TestMethod()] 118 | public void Should_Split_Collection_On_Two_Parts() 119 | { 120 | //Setup 121 | var collection = new List() 122 | { 123 | new OrganizationRequest(), 124 | new OrganizationRequest(), 125 | new OrganizationRequest(), 126 | new OrganizationRequest(), 127 | new OrganizationRequest(), 128 | new OrganizationRequest(), 129 | new OrganizationRequest(), 130 | }; 131 | 132 | var size = collection.Count / 2 + 1; 133 | 134 | //We dont use Skip+Take in real method as they iterate over collection from the beginning for each attempt 135 | List expectedChunk1 = collection.Take(size).ToList(); 136 | List expectedChunk2 = collection.Skip(size).Take(size).ToList(); 137 | 138 | //Act 139 | var result = collection.Chunk(size).ToList(); 140 | var actualChunk1 = result[0]; 141 | var actualChunk2 = result[1]; 142 | 143 | //Assert 144 | Assert.AreEqual(2, result.Count); 145 | CollectionAssert.AreEqual(expectedChunk1, actualChunk1); 146 | CollectionAssert.AreEqual(expectedChunk2, actualChunk2); 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/OrganizationServiceFaultExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.ServiceModel; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace D365Extensions.Tests 11 | { 12 | [TestClass()] 13 | public class OrganizationServiceFaultExtensionsTests 14 | { 15 | [TestMethod()] 16 | public void GetKnownErrorCodeTest() 17 | { 18 | // Setup 19 | const ErrorCodes expectedErrorCode = ErrorCodes.AccessDenied; 20 | 21 | var fault = new OrganizationServiceFault(); 22 | fault.ErrorCode = (int)expectedErrorCode; 23 | 24 | var exception = new FaultException(fault); 25 | 26 | // Act 27 | var actualErrorCode = exception.GetErrorCode(); 28 | 29 | // Assert 30 | Assert.IsNotNull(actualErrorCode); 31 | Assert.AreEqual(expectedErrorCode, actualErrorCode); 32 | } 33 | 34 | [TestMethod()] 35 | public void GetUnknownErrorCodeTest() 36 | { 37 | // Setup 38 | var fault = new OrganizationServiceFault(); 39 | fault.ErrorCode = 42; 40 | 41 | var exception = new FaultException(fault); 42 | 43 | // Act 44 | var error = exception.GetErrorCode(); 45 | 46 | // Assert 47 | Assert.IsNull(error); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using ExecutionScope = Microsoft.VisualStudio.TestTools.UnitTesting.ExecutionScope; 6 | 7 | // Setting ComVisible to false makes the types in this assembly not visible 8 | // to COM components. If you need to access a type in this assembly from 9 | // COM, set the ComVisible attribute to true on that type. 10 | [assembly: ComVisible(false)] 11 | 12 | // The following GUID is for the ID of the typelib if this project is exposed to COM 13 | [assembly: Guid("57ad9ed0-3237-44b4-bab6-d80870cc46d9")] 14 | 15 | // For running each method in separate thread 16 | // https://devblogs.microsoft.com/devops/mstest-v2-in-assembly-parallel-test-execution/ 17 | [assembly: Parallelize(Workers = 0, Scope = ExecutionScope.MethodLevel)] -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/QueryByAttributeExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class QueryByAttributeExtensionsTests 13 | { 14 | [TestMethod()] 15 | public void AddAttributeTest() 16 | { 17 | // Setup 18 | QueryByAttribute query = new QueryByAttribute(); 19 | 20 | var expectedAttributeName = nameof(TestEntity.ReferenceTypeProperty).ToLower(); 21 | var expectedAttributeValue = "not used"; 22 | 23 | // Act 24 | query.AddAttribute(t => t.ReferenceTypeProperty, expectedAttributeValue); 25 | 26 | // Assert 27 | Assert.AreEqual(1, query.Attributes.Count); 28 | 29 | var actualAttribute = query.Attributes[0]; 30 | Assert.AreEqual(expectedAttributeName, actualAttribute); 31 | 32 | var actualValue = query.Values[0]; 33 | Assert.AreEqual(expectedAttributeValue, actualValue); 34 | } 35 | 36 | [TestMethod()] 37 | public void AddOrderTest() 38 | { 39 | // Setup 40 | QueryByAttribute query = new QueryByAttribute(); 41 | 42 | var expectedAttributeName = nameof(TestEntity.ReferenceTypeProperty).ToLower(); 43 | var expectedOrder = OrderType.Ascending; 44 | 45 | // Act 46 | query.AddOrder(t => t.ReferenceTypeProperty, expectedOrder); 47 | 48 | // Assert 49 | Assert.AreEqual(1, query.Orders.Count); 50 | 51 | var actualOrder = query.Orders[0]; 52 | Assert.AreEqual(expectedAttributeName, actualOrder.AttributeName); 53 | Assert.AreEqual(expectedOrder, actualOrder.OrderType); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/QueryExpressionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | [TestClass()] 12 | public class QueryExpressionExtensionsTests 13 | { 14 | [TestMethod()] 15 | public void AddOrderTest() 16 | { 17 | // Setup 18 | QueryExpression query = new QueryExpression(); 19 | 20 | var expectedAttributeName = nameof(TestEntity.ReferenceTypeProperty).ToLower(); 21 | var expectedOrder = OrderType.Ascending; 22 | 23 | // Act 24 | query.AddOrder(t => t.ReferenceTypeProperty, expectedOrder); 25 | 26 | // Assert 27 | Assert.AreEqual(1, query.Orders.Count); 28 | 29 | var actualOrder = query.Orders[0]; 30 | Assert.AreEqual(expectedAttributeName, actualOrder.AttributeName); 31 | Assert.AreEqual(expectedOrder, actualOrder.OrderType); 32 | } 33 | 34 | [TestMethod()] 35 | public void AddLink1Test() 36 | { 37 | // Setup 38 | QueryExpression query = new QueryExpression(); 39 | 40 | string expectedToEntityName = EntityTo.EnityLogicalName; 41 | string expectedFromAttributName = nameof(EntityFrom.FromId).ToLower(); 42 | string expectedToAttributName = nameof(EntityTo.ToId).ToLower(); 43 | 44 | // Act 45 | LinkEntity actualLink = query.AddLink(f => f.FromId, t => t.ToId); 46 | 47 | // Assert 48 | Assert.AreEqual(1, query.LinkEntities.Count); 49 | Assert.AreEqual(query.LinkEntities[0], actualLink); 50 | Assert.AreEqual(expectedToEntityName, actualLink.LinkToEntityName); 51 | Assert.AreEqual(expectedFromAttributName, actualLink.LinkFromAttributeName); 52 | Assert.AreEqual(expectedToAttributName, actualLink.LinkToAttributeName); 53 | } 54 | 55 | [TestMethod()] 56 | public void AddLink2Test() 57 | { 58 | // Setup 59 | QueryExpression query = new QueryExpression(); 60 | 61 | string expectedToEntityName = EntityTo.EnityLogicalName; 62 | string expectedFromAttributName = nameof(EntityFrom.FromId).ToLower(); 63 | string expectedToAttributName = nameof(EntityTo.ToId).ToLower(); 64 | JoinOperator expectedOperator = JoinOperator.LeftOuter; 65 | 66 | // Act 67 | LinkEntity actualLink = query.AddLink(f => f.FromId, t => t.ToId, expectedOperator); 68 | 69 | // Assert 70 | Assert.AreEqual(1, query.LinkEntities.Count); 71 | Assert.AreEqual(query.LinkEntities[0], actualLink); 72 | Assert.AreEqual(expectedToEntityName, actualLink.LinkToEntityName); 73 | Assert.AreEqual(expectedFromAttributName, actualLink.LinkFromAttributeName); 74 | Assert.AreEqual(expectedToAttributName, actualLink.LinkToAttributeName); 75 | Assert.AreEqual(expectedOperator, actualLink.JoinOperator); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/TestEntities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xrm.Sdk; 7 | using Microsoft.Xrm.Sdk.Client; 8 | 9 | namespace D365Extensions.Tests 10 | { 11 | /// 12 | /// Simple Entity inheritor for Test purpose 13 | /// 14 | [EntityLogicalName("testentity")] 15 | public class TestEntity : Entity 16 | { 17 | public const string EntityLogicalName = "testentity"; 18 | 19 | public TestEntity() : base(EntityLogicalName) 20 | { 21 | } 22 | 23 | /// 24 | /// Reference type property 25 | /// 26 | [AttributeLogicalName("referencetypeproperty")] 27 | public string ReferenceTypeProperty 28 | { 29 | get 30 | { 31 | return this.GetAttributeValue("referencetypeproperty"); 32 | } 33 | set 34 | { 35 | this.SetAttributeValue("referencetypeproperty", value); 36 | } 37 | } 38 | 39 | /// 40 | /// Value type property 41 | /// 42 | [AttributeLogicalName("valuetypeproperty")] 43 | public System.Nullable ValueTypeProperty 44 | { 45 | get 46 | { 47 | return this.GetAttributeValue>("valuetypeproperty"); 48 | } 49 | set 50 | { 51 | this.SetAttributeValue("valuetypeproperty", value); 52 | } 53 | } 54 | } 55 | 56 | /// 57 | /// Simple Entity for LinkEntity tests 58 | /// 59 | [EntityLogicalName("entityfrom")] 60 | public class EntityFrom : Entity 61 | { 62 | public static string EnityLogicalName = "entityfrom"; 63 | 64 | public EntityFrom() : base(EnityLogicalName) 65 | { 66 | } 67 | 68 | [AttributeLogicalName("fromid")] 69 | public Guid FromId { get; set; } 70 | } 71 | 72 | /// 73 | /// Simple Entity for LinkEntity tests 74 | /// 75 | [EntityLogicalName("entityto")] 76 | public class EntityTo : Entity 77 | { 78 | public static string EnityLogicalName = "entityto"; 79 | 80 | public EntityTo() : base(EnityLogicalName) 81 | { 82 | } 83 | 84 | [AttributeLogicalName("toid")] 85 | public Guid ToId { get; set; } 86 | } 87 | 88 | /// 89 | /// Entity for PropertyExpression tests 90 | /// 91 | [EntityLogicalName("custom_entity")] 92 | public class CustomEntity : Entity 93 | { 94 | public static string EnityLogicalName = "custom_entity"; 95 | 96 | public CustomEntity() : base(EnityLogicalName) 97 | { 98 | } 99 | 100 | [AttributeLogicalName("string_prop")] 101 | public string StringProp { 102 | get => GetAttributeValue("string_prop"); 103 | set => SetAttributeValue("string_prop", value); 104 | } 105 | } 106 | 107 | /// 108 | /// Entity for PropertyExpression tests 109 | /// 110 | [EntityLogicalName("custom_entity2")] 111 | public class CustomEntity2 : Entity 112 | { 113 | public static string EnityLogicalName = "custom_entity2"; 114 | 115 | public CustomEntity2() : base(EnityLogicalName) 116 | { 117 | } 118 | 119 | [AttributeLogicalName("string_prop2")] 120 | public string StringProp 121 | { 122 | get => GetAttributeValue("string_prop2"); 123 | set => SetAttributeValue("string_prop2", value); 124 | } 125 | } 126 | 127 | /// 128 | /// Entity for PropertyExpression tests 129 | /// 130 | public class NotDecoratedEntity : Entity 131 | { 132 | public static string EnityLogicalName = "notdecoratedentity"; 133 | 134 | public NotDecoratedEntity() : base(EnityLogicalName) 135 | { 136 | } 137 | 138 | public string TheProp 139 | { 140 | get => GetAttributeValue("theprop"); 141 | set => SetAttributeValue("theprop", value); 142 | } 143 | } 144 | 145 | public enum StateCode 146 | { 147 | Active = 0, 148 | Inactive = 1 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31702.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D365Extensions", "D365Extensions\D365Extensions.csproj", "{0FD54081-45D9-41C1-8732-13034BDBB8E6}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "D365Extensions.Tests", "D365Extensions.Tests\D365Extensions.Tests.csproj", "{57AD9ED0-3237-44B4-BAB6-D80870CC46D9}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "D365Extensions.Benchmark", "D365Extensions.Benchmark\D365Extensions.Benchmark.csproj", "{1ECA11B0-DE44-4C41-AC11-85B7C4AF0BFA}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {0FD54081-45D9-41C1-8732-13034BDBB8E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {0FD54081-45D9-41C1-8732-13034BDBB8E6}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {0FD54081-45D9-41C1-8732-13034BDBB8E6}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {0FD54081-45D9-41C1-8732-13034BDBB8E6}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {57AD9ED0-3237-44B4-BAB6-D80870CC46D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {57AD9ED0-3237-44B4-BAB6-D80870CC46D9}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {57AD9ED0-3237-44B4-BAB6-D80870CC46D9}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {57AD9ED0-3237-44B4-BAB6-D80870CC46D9}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {1ECA11B0-DE44-4C41-AC11-85B7C4AF0BFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {1ECA11B0-DE44-4C41-AC11-85B7C4AF0BFA}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {1ECA11B0-DE44-4C41-AC11-85B7C4AF0BFA}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {1ECA11B0-DE44-4C41-AC11-85B7C4AF0BFA}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {CB4BDB0A-199A-4423-923D-9E9219CFA7C3} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/AliasedValue.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Linq.Expressions; 4 | 5 | namespace Microsoft.Xrm.Sdk 6 | { 7 | /// 8 | /// Generic version if AliasedValue class 9 | /// 10 | /// 11 | public class AliasedValue where T : Entity 12 | { 13 | public Expression> AttributeName { get; } 14 | 15 | public object Value { get; } 16 | 17 | public AliasedValue(Expression> attributeName, object value) 18 | { 19 | AttributeName = attributeName; 20 | Value = value; 21 | } 22 | 23 | public static implicit operator AliasedValue(AliasedValue t) 24 | { 25 | if (t is null) return null; 26 | 27 | return new AliasedValue( 28 | entityLogicalName: LogicalName.GetName(), 29 | attributeLogicalName: LogicalName.GetName(t.AttributeName), 30 | value: t.Value); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/AliasedValueExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.Xrm.Sdk 7 | { 8 | public static class AliasedValueExtensions 9 | { 10 | /// 11 | /// Checks if AliasedValue contains primary key attribute of linked entity 12 | /// 13 | /// Id of linked entity 14 | public static bool IsPrimaryKey(this AliasedValue aliasedValue) 15 | { 16 | return aliasedValue.AttributeLogicalName == "activityid" || 17 | aliasedValue.AttributeLogicalName.ConsistOf(aliasedValue.EntityLogicalName, "id"); 18 | } 19 | 20 | /// 21 | /// Gets the value of AliasedValue 22 | /// 23 | public static T GetValue(this AliasedValue aliasedValue) 24 | { 25 | return (T) aliasedValue.Value; 26 | } 27 | 28 | // to avoid string allocations 29 | private static bool ConsistOf(this string value, string part1, string part2) 30 | { 31 | if (value.Length != part1.Length + part2.Length) 32 | return false; 33 | 34 | return value.StartsWith(part1) && value.EndsWith(part2); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/CodeActivityContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | using Microsoft.Xrm.Sdk.Workflow; 3 | using System; 4 | using System.Activities; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace System.Activities 11 | { 12 | public static class CodeActivityContextExtensions 13 | { 14 | /// 15 | /// Gets IWorkflowContext extension from CodeActivityContext 16 | /// 17 | /// 18 | public static IWorkflowContext GetWorkflowContext(this CodeActivityContext codeActivityContext) 19 | { 20 | return codeActivityContext.GetExtension(); 21 | } 22 | 23 | /// 24 | /// Gets IOrganizationServiceFactory extension from CodeActivityContext 25 | /// 26 | /// 27 | /// 28 | public static IOrganizationServiceFactory GetOrganizationServiceFactory(this CodeActivityContext codeActivityContext) 29 | { 30 | 31 | return codeActivityContext.GetExtension(); 32 | } 33 | 34 | /// 35 | /// Gets ITracingService extension from CodeActivityContext 36 | /// 37 | /// 38 | /// 39 | public static ITracingService GetTracingService(this CodeActivityContext codeActivityContext) 40 | { 41 | return codeActivityContext.GetExtension(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/ColumnSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using D365Extensions; 5 | 6 | namespace Microsoft.Xrm.Sdk.Query 7 | { 8 | /// 9 | /// Strongly typed version of the ColumnSet class 10 | /// 11 | public sealed class ColumnSet where T : Entity 12 | { 13 | /// 14 | /// Gets the collection of lambdas containing the names of the attributes to be retrieved 15 | /// from a query. 16 | /// 17 | public List>> Columns { get; } = new List>>(); 18 | 19 | /// 20 | /// Initializes a new instance of the ColumnSet class. 21 | /// 22 | public ColumnSet() 23 | { 24 | } 25 | 26 | /// 27 | /// Initializes a new instance of the ColumnSet class setting the Columns property. 28 | /// 29 | /// Specifies an array of property expressions containing the names of the attributes. 30 | /// 31 | public ColumnSet(params Expression>[] columns) 32 | { 33 | Columns.AddRange(columns); 34 | } 35 | 36 | /// 37 | /// Adds the specified attribute to the column set 38 | /// 39 | /// Specifies a property expressions containing the name of the attribute. 40 | public void AddColumn(Expression> column) 41 | { 42 | Columns.Add(column); 43 | } 44 | 45 | /// 46 | /// Adds the specified attributes to the column set. 47 | /// 48 | /// Specifies an array of property expressions containing the names of the attributes. 49 | public void AddColumns(params Expression>[] columns) 50 | { 51 | Columns.AddRange(columns); 52 | } 53 | 54 | /// 55 | /// Converts ColumnSet to ColumnSet 56 | /// 57 | public static implicit operator ColumnSet(ColumnSet t) 58 | { 59 | if (t == null) return null; 60 | 61 | return new ColumnSet(LogicalName.GetNames(t.Columns.ToArray())); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/ColumnSetExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Linq.Expressions; 4 | 5 | namespace Microsoft.Xrm.Sdk.Query 6 | { 7 | public static class ColumnSetExtensions 8 | { 9 | /// 10 | /// Adds the specified attribute to the column set. 11 | /// 12 | /// Type of the entity to add column from 13 | /// The property expression containing the name of the attribute to add 14 | public static void AddColumn(this ColumnSet columnSet, Expression> column) where T: Entity 15 | { 16 | columnSet.AddColumn(LogicalName.GetName(column)); 17 | } 18 | 19 | /// 20 | /// Adds the specified attributes to the column set. 21 | /// 22 | /// Type of the entity to add column from 23 | /// The property expressions containing the name of the attribute to add 24 | public static void AddColumns(this ColumnSet columnSet, params Expression>[] columns) where T : Entity 25 | { 26 | columnSet.AddColumns(LogicalName.GetNames(columns)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/ConditionExpression.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Microsoft.Xrm.Sdk.Query 11 | { 12 | /// 13 | /// Strongly typed version of the ConditionExpression class 14 | /// 15 | public class ConditionExpression where T : Entity 16 | { 17 | /// 18 | /// Initializes a new instance of ConditionExpression class. 19 | /// 20 | public ConditionExpression() 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the ConditionExpression class. 26 | /// 27 | /// The logical name of a table, or a LinkEntity.EntityAlias value for a table, that contains the column to evaluate for the condition 28 | /// The logical name of the attribute in the condition expression. 29 | /// The condition operator. 30 | /// The array of attribute values. 31 | public ConditionExpression(string entityName, Expression> attributeName, ConditionOperator conditionOperator, params object[] values) 32 | { 33 | EntityName = entityName; 34 | AttributeName = attributeName; 35 | Operator = conditionOperator; 36 | if (values != null) 37 | { 38 | Values = new List(values); 39 | } 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the ConditionExpression class. 44 | /// 45 | /// The logical name of the attribute in the condition expression. 46 | /// The condition operator. 47 | /// The array of attribute values. 48 | public ConditionExpression(Expression> attributeName, ConditionOperator conditionOperator, params object[] values) : this(null, attributeName, conditionOperator, values) 49 | { 50 | } 51 | 52 | /// 53 | /// Initializes a new instance of the ConditionExpression class. 54 | /// 55 | /// The logical name of the attribute in the condition expression. 56 | /// The condition operator. 57 | public ConditionExpression(Expression> attributeName, ConditionOperator conditionOperator) : this(null, attributeName, conditionOperator, new object[0]) 58 | { 59 | } 60 | 61 | 62 | /// 63 | /// Initializes a new instance of the ConditionExpression class setting the attribute 64 | /// name, condition operator and value object. 65 | /// 66 | /// The logical name of the attribute in the condition expression. 67 | /// The condition operator. 68 | /// The attribute value 69 | public ConditionExpression(Expression> attributeName, ConditionOperator conditionOperator, object value) : this(null, attributeName, conditionOperator, new object[] { value }) 70 | { 71 | 72 | } 73 | 74 | /// 75 | /// Gets or sets the entity name for the condition. 76 | /// 77 | public string EntityName { get; set; } 78 | 79 | /// 80 | /// Gets or sets the logical name of the attribute in the condition expression. 81 | /// 82 | public Expression> AttributeName { get; set; } 83 | 84 | /// 85 | /// Gets or sets the condition operator. 86 | /// 87 | public ConditionOperator Operator { get; set; } 88 | 89 | /// 90 | /// Gets or sets the values for the attribute. 91 | /// 92 | public List Values { get; } = new List(); 93 | 94 | /// 95 | /// Converts ConditionExpression to ConditionExpression 96 | /// 97 | public static implicit operator ConditionExpression(ConditionExpression t) 98 | { 99 | if (t == null) return null; 100 | 101 | return new ConditionExpression(t.EntityName, 102 | LogicalName.GetName(t.AttributeName), 103 | t.Operator, 104 | t.Values.ToArray()); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/D365Extensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net452 4 | D365Extensions 5 | Artem Grunin 6 | D365Extensions 7 | D365Extensions 8 | A collection of extension methods for Microsoft Dynamics CRM/D365 SDK base classes 9 | Copyright 2018 10 | 1.0.0.0 11 | Artem Grunin 12 | 13 | 14 | latest 15 | 16 | 17 | true 18 | 19 | 20 | FixRM.snk 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/D365Extensions.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://github.com/FixRM/D365Extensions 10 | https://raw.githubusercontent.com/FixRM/D365Extensions/master/fixrmlogo_64.png 11 | 12 | false 13 | $description$ 14 | Copyright 2018 15 | D365 CRM 16 | 17 | 18 | 19 | 20 | 21 | docs\README.md 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/DataCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.Xrm.Sdk 9 | { 10 | public static class DataCollectionExtensions 11 | { 12 | /// 13 | /// Safely gets value from DataCollection/ParameterCollection 14 | /// 15 | /// parameter name 16 | /// 17 | public static T GetValue(this DataCollection collection, string key) 18 | { 19 | if (collection.TryGetValue(key, out var value)) 20 | { 21 | return (T)value; 22 | } 23 | else 24 | { 25 | return default; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/EntityCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.Xrm.Sdk; 10 | 11 | public static class EntityCollectionExtensions 12 | { 13 | /// 14 | /// Checks if ActivityParty list contains email address 15 | /// 16 | /// Email address to look for 17 | /// 18 | public static bool ContainsAddress(this EntityCollection collection, string addressUsed) 19 | { 20 | CheckParam.CheckForNull(addressUsed, nameof(addressUsed)); 21 | CheckParam.IsActivityPartyCollection(collection); 22 | 23 | return collection.Entities.Any(e => addressUsed.Equals(GetEmail(e), StringComparison.OrdinalIgnoreCase)); 24 | } 25 | 26 | /// 27 | /// Returns members of ActivityParty list whose email address refers to required domain 28 | /// 29 | /// Email address domain to look for 30 | /// 31 | public static EntityCollection GetPartiesInDomain(this EntityCollection collection, string addressUsedDomain) 32 | { 33 | CheckParam.CheckForNull(addressUsedDomain, nameof(addressUsedDomain)); 34 | CheckParam.IsActivityPartyCollection(collection); 35 | 36 | var partiesFromDomain = collection.Entities.Where(e => EmailEndsWith(e, addressUsedDomain)); 37 | 38 | var ec = new EntityCollection(); 39 | ec.EntityName = "activityparty"; 40 | ec.Entities.AddRange(partiesFromDomain); 41 | 42 | return ec; 43 | } 44 | 45 | /// 46 | /// Returns members of ActivityParty list whose email address DON'T refers to required domain 47 | /// 48 | /// Email address domain to look for 49 | /// 50 | public static EntityCollection GetPartiesNotInDomain(this EntityCollection collection, string addressUsedDomain) 51 | { 52 | CheckParam.CheckForNull(addressUsedDomain, nameof(addressUsedDomain)); 53 | CheckParam.IsActivityPartyCollection(collection); 54 | 55 | var partiesNotFromDomain = collection.Entities.Where(e => !EmailEndsWith(e, addressUsedDomain)); 56 | 57 | var ec = new EntityCollection(); 58 | ec.EntityName = "activityparty"; 59 | ec.Entities.AddRange(partiesNotFromDomain); 60 | 61 | return ec; 62 | } 63 | 64 | private static bool EmailEndsWith(Entity party, string value) 65 | { 66 | var email = GetEmail(party); 67 | 68 | return email?.EndsWith(value, StringComparison.OrdinalIgnoreCase) == true; 69 | } 70 | 71 | private static string GetEmail(Entity party) 72 | { 73 | return party.GetAttributeValue("addressused"); 74 | } 75 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/EntityImageCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.Xrm.Sdk 8 | { 9 | public static class EntityImageCollectionExtensions 10 | { 11 | /// 12 | /// Safely gets value from EntityImageCollection 13 | /// 14 | /// image name 15 | /// 16 | public static Entity GetImage(this EntityImageCollection images, string name) 17 | { 18 | if (images.TryGetValue(name, out var image)) 19 | { 20 | return image; 21 | } 22 | 23 | return null; 24 | } 25 | 26 | /// 27 | /// Safely gets value from EntityImageCollection 28 | /// 29 | /// Early Bound Entity type 30 | /// image name 31 | /// 32 | public static T GetImage(this EntityImageCollection images, string name) where T : Entity 33 | { 34 | return images.GetImage(name)?.ToEntity(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/EntityReference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.Xrm.Sdk 9 | { 10 | /// 11 | /// Strongly typed version of the EntityReference class 12 | /// 13 | /// 14 | public sealed class EntityReference where T : Entity 15 | { 16 | /// 17 | /// EntityReference has complex overrides of Equals and GetHashCode 18 | /// So unlike other strongly typed versions in this library, it's easier to have this one to be 19 | /// a wrapper of OOB type instead of independent implementation 20 | /// 21 | private readonly EntityReference reference; 22 | 23 | public EntityReference() 24 | { 25 | this.reference = new EntityReference(D365Extensions.LogicalName.GetName()); 26 | } 27 | 28 | public EntityReference(Guid id) 29 | { 30 | this.reference = new EntityReference() 31 | { 32 | Id = id, 33 | LogicalName = D365Extensions.LogicalName.GetName() 34 | }; 35 | } 36 | 37 | public EntityReference(KeyAttributeCollection keyAttributes) 38 | { 39 | this.reference = new EntityReference() 40 | { 41 | LogicalName = D365Extensions.LogicalName.GetName(), 42 | KeyAttributes = keyAttributes 43 | }; 44 | } 45 | 46 | public EntityReference(Expression> keyName, object keyValue) 47 | { 48 | this.reference = new EntityReference() 49 | { 50 | LogicalName = D365Extensions.LogicalName.GetName(), 51 | KeyAttributes = 52 | { 53 | { D365Extensions.LogicalName.GetName(keyName), keyValue } 54 | } 55 | }; 56 | } 57 | 58 | /// 59 | /// Gets or sets the ID of the record 60 | /// 61 | public Guid Id 62 | { 63 | get => reference.Id; 64 | set => reference.Id = value; 65 | } 66 | 67 | /// 68 | /// Gets or sets the logical name of the entity. 69 | /// 70 | public string LogicalName 71 | { 72 | get => reference.LogicalName; 73 | } 74 | 75 | /// 76 | /// Gets or sets the value of the primary attribute of the entity. 77 | /// 78 | public string Name 79 | { 80 | get => reference.Name; 81 | set => reference.Name = value; 82 | } 83 | 84 | /// 85 | /// Gets or sets the key attributes. 86 | /// 87 | public KeyAttributeCollection KeyAttributes 88 | { 89 | get => reference.KeyAttributes; 90 | set => reference.KeyAttributes = value; 91 | } 92 | 93 | /// 94 | /// Gets or sets the row version. 95 | /// 96 | public string RowVersion 97 | { 98 | get => reference.RowVersion; 99 | set => reference.RowVersion = value; 100 | } 101 | 102 | /// 103 | /// WARNING: EntityReference and EntityReference are different types! You can't compare them directly! 104 | /// You should always explicitly cast EntityReference to EntityReference before comparison: 105 | /// 106 | /// reference1.Equals((EntityReference) reference2) 107 | /// 108 | /// 109 | public override bool Equals(object obj) 110 | { 111 | return reference.Equals(obj); 112 | } 113 | 114 | public override int GetHashCode() 115 | { 116 | return reference.GetHashCode(); 117 | } 118 | 119 | public static implicit operator EntityReference(EntityReference t) 120 | { 121 | if (t == null) return null; 122 | 123 | return t.reference; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/EntityReferenceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.Xrm.Sdk 8 | { 9 | /// 10 | /// Set of extension methods for Microsoft.Xrm.Sdk.EntityReference base class. At the moment just two simple but sometimes useful type conversion methods. 11 | /// 12 | public static class EntityReferenceExtensions 13 | { 14 | /// 15 | /// Gets the entity based on the EntityReference. 16 | /// 17 | /// 18 | public static Entity ToEntity(this EntityReference entityReference) 19 | { 20 | return entityReference.ToEntity(); 21 | } 22 | 23 | /// 24 | /// Gets the entity based on the EntityReference as the specified type. 25 | /// 26 | /// 27 | public static T ToEntity(this EntityReference entityReference) where T : Entity, new() 28 | { 29 | return new T() 30 | { 31 | Id = entityReference.Id, 32 | LogicalName = entityReference.LogicalName, 33 | KeyAttributes = entityReference.KeyAttributes, 34 | RowVersion = entityReference.RowVersion 35 | }; 36 | } 37 | 38 | /// 39 | /// Returns EntityReference string representation 40 | /// 41 | /// 42 | public static string ToTraceString(this EntityReference reference) 43 | { 44 | return $@"EntityReference {{ LogicalName = ""{reference.LogicalName}"", Id = ""{reference.Id:B}"" }}"; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/ExecuteMultipleOperationResponse.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Xrm.Sdk 2 | { 3 | /// 4 | /// Combines ExecuteMultipleResponseItem with its original OrganizationRequest 5 | /// 6 | public sealed class ExecuteMultipleOperationResponse 7 | { 8 | internal ExecuteMultipleResponseItem ExecuteMultipleResponseItem { get; } 9 | 10 | public OrganizationRequest Request { get; } 11 | 12 | public OrganizationResponse Response => ExecuteMultipleResponseItem.Response; 13 | 14 | public OrganizationServiceFault Fault => ExecuteMultipleResponseItem.Fault; 15 | 16 | public bool IsFaulted => Fault != null; 17 | 18 | public ExecuteMultipleOperationResponse(ExecuteMultipleResponseItem executeMultipleResponseItem, OrganizationRequest request) 19 | { 20 | ExecuteMultipleResponseItem = executeMultipleResponseItem; 21 | Request = request; 22 | } 23 | 24 | public T GetRequest() where T : OrganizationRequest 25 | { 26 | return Request as T; 27 | } 28 | 29 | public T GetResponse() where T : OrganizationResponse 30 | { 31 | return Response as T; 32 | } 33 | 34 | public void ThrowIfFaulted() 35 | { 36 | ExecuteMultipleResponseItem.ThrowIfFaulted(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/ExecuteMultipleProgress.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Xrm.Sdk 2 | { 3 | /// 4 | /// Contains progress information for long running data processing operations 5 | /// 6 | public record struct ExecuteMultipleProgress(uint Queried, uint Processed, uint Skipped, uint Errors) 7 | { 8 | public float Progress => Queried != 0 ? ((float)(Processed + Skipped)) / Queried * 100.0F : 100; 9 | 10 | public float ErrorRate => Processed != 0 ? ((float)Errors ) / Processed * 100.0F : 0; 11 | 12 | public float SkippedRate => Queried != 0 ? ((float)Skipped) / Queried * 100.0F : 0; 13 | } 14 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/ExecuteMultipleResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Messages; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace Microsoft.Xrm.Sdk 7 | { 8 | internal static class ExecuteMultipleResponseExtensions 9 | { 10 | //two methods above work in pair with IOrganizationServiceExtensions.Execute(IEnumerable requests) extensions 11 | #region Execute partners 12 | 13 | [Obsolete] 14 | public static OrganizationRequestCollection GetRequests(this ExecuteMultipleResponse response) 15 | { 16 | return response.Results.GetValue("Requests"); 17 | } 18 | 19 | [Obsolete] 20 | public static OrganizationRequest GetRequest(this ExecuteMultipleResponse response, ExecuteMultipleResponseItem item) 21 | { 22 | return response.GetRequests()?[item.RequestIndex]; 23 | } 24 | 25 | #endregion 26 | 27 | /// 28 | /// Returns collection of faulted ExecuteMultipleResponseItems 29 | /// 30 | public static ExecuteMultipleResponseItemCollection GetFaultedResponses(this ExecuteMultipleResponse response) 31 | { 32 | var results = new ExecuteMultipleResponseItemCollection(); 33 | results.AddRange(response.GetFaultedResponsesInternal()); 34 | 35 | return results; 36 | } 37 | 38 | internal static IEnumerable GetFaultedResponsesInternal(this ExecuteMultipleResponse response) 39 | { 40 | return response.Responses.Where(r => r.IsFaulted()); 41 | } 42 | 43 | /// 44 | /// Throws AggregateException that contains faults from related ExecuteMultipleResponseItems 45 | /// 46 | /// 47 | public static void ThrowIfFaulted(this ExecuteMultipleResponse response) 48 | { 49 | if (response.IsFaulted) 50 | { 51 | var faulted = response.GetFaultedResponsesInternal(); 52 | 53 | throw new AggregateException(faulted.Select(f => f.GetFaultException())); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/ExecuteMultipleResponseItemExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.ServiceModel; 2 | 3 | namespace Microsoft.Xrm.Sdk 4 | { 5 | public static class ExecuteMultipleResponseItemExtensions 6 | { 7 | /// 8 | /// Checks if this ExecuteMultipleResponseItem if contains a fault 9 | /// 10 | public static bool IsFaulted(this ExecuteMultipleResponseItem item) 11 | { 12 | return item.Fault != null; 13 | } 14 | 15 | /// 16 | /// Creates FaultException from this ExecuteMultipleResponseItem`s fault 17 | /// 18 | /// 19 | /// 20 | public static FaultException GetFaultException(this ExecuteMultipleResponseItem item) 21 | { 22 | return new FaultException(item.Fault); 23 | } 24 | 25 | /// 26 | /// Throws a FaultException if this ExecuteMultipleResponseItem if contains a fault 27 | /// 28 | /// 29 | public static void ThrowIfFaulted(this ExecuteMultipleResponseItem item) 30 | { 31 | if (item.IsFaulted()) 32 | { 33 | throw new FaultException(item.Fault); 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/FilterExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using D365Extensions; 8 | 9 | namespace Microsoft.Xrm.Sdk.Query 10 | { 11 | /// 12 | /// Extensions for FilterExpression 13 | /// 14 | public static class FilterExpressionExtensions 15 | { 16 | /// 17 | /// Adds a condition to the filter expression setting the entity name, attribute name, condition operator, and value array. 18 | /// 19 | /// Property expressions containing the name of the attribute. 20 | /// Condition operator. 21 | /// The array of values to add. 22 | public static void AddCondition(this FilterExpression filterExpression, Expression> attributeName, ConditionOperator conditionOperator, params object[] values) where T : Entity 23 | { 24 | filterExpression.AddCondition(LogicalName.GetName(), LogicalName.GetName(attributeName), conditionOperator, values); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/IOrganizationServiceAssosiateExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using Microsoft.Xrm.Sdk.Messages; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Microsoft.Xrm.Sdk 7 | { 8 | /// 9 | /// Extensions for IOrganizationService.Associate 10 | /// 11 | public static partial class IOrganizationServiceExtensions 12 | { 13 | /// 14 | /// Associate method override. Takes EntityReference as primary entity input parameter 15 | /// 16 | public static void Associate(this IOrganizationService service, EntityReference primaryEntity, Relationship relationship, EntityReferenceCollection relatedEntities) 17 | { 18 | CheckParam.CheckForNull(primaryEntity, nameof(primaryEntity)); 19 | 20 | /// Associate by id if possible as is supposed to be faster 21 | if (primaryEntity.Id != Guid.Empty) 22 | { 23 | service.Associate(primaryEntity.LogicalName, primaryEntity.Id, relationship, relatedEntities); 24 | } 25 | /// Use alternative key 26 | else 27 | { 28 | service.Execute(new AssociateRequest() 29 | { 30 | Target = primaryEntity, 31 | Relationship = relationship, 32 | RelatedEntities = relatedEntities 33 | }); 34 | } 35 | } 36 | 37 | /// 38 | /// Associate method override. Takes EntityReference as primary entity input parameter and list of EntityReferences as related entities parameter 39 | /// 40 | public static void Associate(this IOrganizationService service, EntityReference primaryEntity, Relationship relationship, IList relatedEntities) 41 | { 42 | service.Associate(primaryEntity, relationship, new EntityReferenceCollection(relatedEntities)); 43 | } 44 | 45 | /// 46 | /// Associate method override. Takes EntityReference as primary entity input parameter 47 | /// 48 | public static void Associate(this IOrganizationService service, EntityReference primaryEntity, String relationshipName, EntityReferenceCollection relatedEntities) 49 | { 50 | service.Associate(primaryEntity.LogicalName, primaryEntity.Id, new Relationship(relationshipName), relatedEntities); 51 | } 52 | 53 | /// 54 | /// Associate method override. Takes EntityReference as primary entity input parameter and list of EntityReferences as related entities parameter 55 | /// 56 | public static void Associate(this IOrganizationService service, EntityReference primaryEntity, String relationshipName, IList relatedEntities) 57 | { 58 | service.Associate(primaryEntity, relationshipName, new EntityReferenceCollection(relatedEntities)); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/IOrganizationServiceDeleteExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using Microsoft.Xrm.Sdk.Messages; 3 | using System; 4 | 5 | namespace Microsoft.Xrm.Sdk 6 | { 7 | /// 8 | /// Extensions for IOrganizationService.Delete 9 | /// 10 | public static partial class IOrganizationServiceExtensions 11 | { 12 | /// 13 | /// Delete method override. Takes EntityReference as input parameter 14 | /// 15 | /// Entity to delete 16 | public static void Delete(this IOrganizationService service, EntityReference reference) 17 | { 18 | CheckParam.CheckForNull(reference, nameof(reference)); 19 | 20 | /// Delete by id if possible as is supposed to be faster 21 | if (reference.Id != Guid.Empty) 22 | { 23 | service.Delete(reference.LogicalName, reference.Id); 24 | } 25 | /// Use alternative key 26 | else 27 | { 28 | service.Delete(reference.LogicalName, reference.KeyAttributes); 29 | } 30 | } 31 | 32 | /// 33 | /// Delete method override. Takes Entity as input parameter 34 | /// 35 | /// Entity to delete 36 | public static void Delete(this IOrganizationService service, Entity entity) 37 | { 38 | CheckParam.CheckForNull(entity, nameof(entity)); 39 | 40 | service.Delete(entity.ToEntityReference(true)); 41 | } 42 | 43 | /// 44 | /// Delete method override. Deletes by Alternative key 45 | /// 46 | /// Entity to delete 47 | public static void Delete(this IOrganizationService service, string logicalName, KeyAttributeCollection keyAttributeCollection) 48 | { 49 | CheckParam.CheckForNull(logicalName, nameof(logicalName)); 50 | CheckParam.CheckForNull(keyAttributeCollection, nameof(keyAttributeCollection)); 51 | 52 | service.Execute(new DeleteRequest() 53 | { 54 | Target = new EntityReference(logicalName, keyAttributeCollection) 55 | }); 56 | } 57 | 58 | /// 59 | /// Delete method override. Deletes by Alternative key 60 | /// 61 | /// Entity to delete 62 | public static void Delete(this IOrganizationService service, string logicalName, string keyName, object keyValue) 63 | { 64 | CheckParam.CheckForNull(keyName, nameof(keyName)); 65 | CheckParam.CheckForNull(keyValue, nameof(keyValue)); 66 | 67 | KeyAttributeCollection keys = new KeyAttributeCollection(); 68 | keys.Add(keyName, keyValue); 69 | 70 | service.Delete(logicalName, keys); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/IOrganizationServiceDisassociateExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using Microsoft.Xrm.Sdk.Messages; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace Microsoft.Xrm.Sdk 7 | { 8 | /// 9 | /// Extensions for IOrganizationService.Disassociate 10 | /// 11 | public static partial class IOrganizationServiceExtensions 12 | { 13 | /// 14 | /// Disassociate method override. Takes EntityReference as primary entity input parameter 15 | /// 16 | public static void Disassociate(this IOrganizationService service, EntityReference primaryEntity, Relationship relationship, EntityReferenceCollection relatedEntities) 17 | { 18 | CheckParam.CheckForNull(primaryEntity, nameof(primaryEntity)); 19 | 20 | /// Disassociate by id if possible as is supposed to be faster 21 | if (primaryEntity.Id != Guid.Empty) 22 | { 23 | service.Disassociate(primaryEntity.LogicalName, primaryEntity.Id, relationship, relatedEntities); 24 | } 25 | /// Use alternative key 26 | else 27 | { 28 | service.Execute(new DisassociateRequest() 29 | { 30 | Target = primaryEntity, 31 | Relationship = relationship, 32 | RelatedEntities = relatedEntities 33 | }); 34 | } 35 | } 36 | 37 | /// 38 | /// Disassociate method override. Takes EntityReference as primary entity input parameter and list of EntityReferences as related entities parameter 39 | /// 40 | public static void Disassociate(this IOrganizationService service, EntityReference primaryEntity, Relationship relationship, IList relatedEntities) 41 | { 42 | service.Disassociate(primaryEntity, relationship, new EntityReferenceCollection(relatedEntities)); 43 | } 44 | 45 | /// 46 | /// Associate method override. Takes EntityReference as primary entity input parameter 47 | /// 48 | public static void Disassociate(this IOrganizationService service, EntityReference primaryEntity, String relationshipName, EntityReferenceCollection relatedEntities) 49 | { 50 | service.Disassociate(primaryEntity.LogicalName, primaryEntity.Id, new Relationship(relationshipName), relatedEntities); 51 | } 52 | 53 | /// 54 | /// Associate method override. Takes EntityReference as primary entity input parameter and list of EntityReferences as related entities parameter 55 | /// 56 | public static void Disassociate(this IOrganizationService service, EntityReference primaryEntity, String relationshipName, IList relatedEntities) 57 | { 58 | service.Disassociate(primaryEntity, relationshipName, new EntityReferenceCollection(relatedEntities)); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/IOrganizationServiceRetrieveMultipleExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Xml.Linq; 6 | 7 | namespace Microsoft.Xrm.Sdk 8 | { 9 | /// 10 | /// Extensions for IOrganizationService.RetrieveMultiple 11 | /// 12 | public static partial class IOrganizationServiceExtensions 13 | { 14 | /// 15 | /// Universal RetrieveMultiple method override. Returns all pages using callback or 'yield' iterator 16 | /// 17 | /// A query that determines the set of record 18 | /// Optional function to be called for each record page 19 | /// Entity set as 'yield' iterator 20 | public static IEnumerable RetrieveMultiple(this IOrganizationService service, QueryBase query, Action callback = null) 21 | { 22 | CheckParam.CheckForNull(query, nameof(query)); 23 | CheckParam.CheckPageNumber(query); 24 | 25 | EntityCollection collection = new EntityCollection 26 | { 27 | MoreRecords = true 28 | }; 29 | 30 | while (collection.MoreRecords) 31 | { 32 | /// Paging start working if Page > 0 33 | query.NextPage(collection.PagingCookie); 34 | 35 | collection = service.RetrieveMultiple(query); 36 | callback?.Invoke(collection); 37 | 38 | foreach (Entity entity in collection.Entities) 39 | { 40 | yield return entity; 41 | } 42 | } 43 | } 44 | 45 | /// 46 | /// RetrieveMultiple method override optimized for FetchExpression. Returns all pages using callback or 'yield' iterator 47 | /// 48 | /// A query that determines the set of record 49 | /// Optional function to be called for each record page 50 | /// Entity set as 'yield' iterator 51 | public static IEnumerable RetrieveMultiple(this IOrganizationService service, FetchExpression query, Action callback = null) 52 | { 53 | CheckParam.CheckForNull(query, nameof(query)); 54 | 55 | /// For performance reasons it's better to load XML once 56 | XDocument document = XDocument.Parse(query.Query); 57 | 58 | CheckParam.CheckPageNumber(query, document); 59 | 60 | EntityCollection collection = new EntityCollection 61 | { 62 | MoreRecords = true 63 | }; 64 | 65 | while (collection.MoreRecords) 66 | { 67 | /// Paging start working if Page > 0 68 | query.NextPage(document, collection.PagingCookie); 69 | 70 | collection = service.RetrieveMultiple(query); 71 | callback?.Invoke(collection); 72 | 73 | foreach (Entity entity in collection.Entities) 74 | { 75 | yield return entity; 76 | } 77 | } 78 | } 79 | 80 | /// 81 | /// RetrieveMultiple method override optimized for FetchExpression. Returns all pages using callback or 'yield' iterator 82 | /// 83 | /// A query in fetch XML 84 | /// Optional function to be called for each record page 85 | /// Entity set as 'yield' iterator 86 | public static IEnumerable RetrieveMultiple(this IOrganizationService service, string fetchXml, Action callback = null) 87 | { 88 | CheckParam.CheckForNull(fetchXml, nameof(fetchXml)); 89 | 90 | return RetrieveMultiple(service, new FetchExpression(fetchXml), callback); 91 | } 92 | 93 | /// 94 | /// Retrieves a collection of records 95 | /// 96 | /// A query in fetch XML 97 | /// EntityCollection 98 | public static EntityCollection RetrieveMultiple(this IOrganizationService service, string fetchXml) 99 | { 100 | CheckParam.CheckForNull(fetchXml, nameof(fetchXml)); 101 | 102 | return service.RetrieveMultiple(new FetchExpression(fetchXml)); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/IOrganizationServiceUpdateExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using Microsoft.Xrm.Sdk.Messages; 3 | using System; 4 | 5 | namespace Microsoft.Xrm.Sdk 6 | { 7 | /// 8 | /// Extensions for IOrganizationService.Update 9 | /// 10 | public static partial class IOrganizationServiceExtensions 11 | { 12 | /// 13 | /// Update method override. Accept ConcurrencyBehavior parameter 14 | /// 15 | /// Entity to delete 16 | public static void Update(this IOrganizationService service, Entity entity, ConcurrencyBehavior concurrencyBehavior) 17 | { 18 | CheckParam.CheckForNull(entity, nameof(entity)); 19 | 20 | service.Execute(new UpdateRequest() 21 | { 22 | Target = entity, 23 | ConcurrencyBehavior = concurrencyBehavior 24 | }); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/IServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.Xrm.Sdk 9 | { 10 | /// 11 | /// Set of extension methods for Microsoft.Xrm.Sdk.IServiceProvider base class. Just shortcut methods to save you few lines of code during plugin development 12 | /// 13 | public static class IServiceProviderExtensions 14 | { 15 | /// 16 | /// Gets IPluginExecutionContext from service provider 17 | /// 18 | /// 19 | public static IPluginExecutionContext GetPluginExecutionContext(this IServiceProvider serviceProvider) 20 | { 21 | return serviceProvider.GetService(typeof(IPluginExecutionContext)) as IPluginExecutionContext; 22 | } 23 | 24 | /// 25 | /// Gets IOrganizationServiceFactory from service provider 26 | /// 27 | /// 28 | public static IOrganizationServiceFactory GetOrganizationServiceFactory(this IServiceProvider serviceProvider) 29 | { 30 | return serviceProvider.GetService(typeof(IOrganizationServiceFactory)) as IOrganizationServiceFactory; 31 | } 32 | 33 | /// 34 | /// Gets ITracingService from service provider 35 | /// For better tracing experience check GetPluginExecutionTraceContext method 36 | /// 37 | /// 38 | /// 39 | public static ITracingService GetTracingService(this IServiceProvider serviceProvider) 40 | { 41 | return serviceProvider.GetService(typeof(ITracingService)) as ITracingService; 42 | } 43 | 44 | /// 45 | /// Gets PluginExecutionTraceContext from service provider 46 | /// 47 | /// PluginExecutionTraceContext is a disposable wrapper around IPluginExecutionContext and ITracingService 48 | /// that simplifies tracing of plugin execution context properties in memory efficient way 49 | public static PluginExecutionTraceContext GetPluginExecutionTraceContext(this IServiceProvider serviceProvider, PluginExecutionTraceContextSettings settings = null) 50 | { 51 | return new PluginExecutionTraceContext(serviceProvider.GetTracingService(), serviceProvider.GetPluginExecutionContext(), settings); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/ITracingServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.Xrm.Sdk 10 | { 11 | /// 12 | /// Set of extension methods for Microsoft.Xrm.Sdk.ITracingService base class. 13 | /// 14 | public static class ITracingServiceExtensions 15 | { 16 | /// 17 | /// Logs basic IPluginExecutionContext trace information such as MessageName, Stage, PrimaryEntityId, etc. 18 | /// 19 | /// IPluginExecutionContext instance 20 | public static void TracePluginContext(this ITracingService tracingService, IPluginExecutionContext context) 21 | { 22 | tracingService.Trace($@"MessageName: ""{context.MessageName}"""); 23 | tracingService.Trace($@"Stage: {context.Stage}"); 24 | tracingService.Trace($@"PrimaryEntityId: ""{context.PrimaryEntityId:B}"""); 25 | tracingService.Trace($@"PrimaryEntityName: ""{context.PrimaryEntityName}"""); 26 | tracingService.Trace($@"UserId: ""{context.UserId:B}"""); 27 | tracingService.Trace($@"InitiatingUserId: ""{context.InitiatingUserId:B}"""); 28 | tracingService.Trace($@"Depth: {context.Depth}"); 29 | tracingService.Trace($@"Mode: {context.Mode}"); 30 | } 31 | 32 | /// 33 | /// Logs IPluginExecutionContext InputParameters content. 34 | /// 35 | /// IPluginExecutionContext instance 36 | 37 | public static void TraceInputParameters(this ITracingService tracingService, IPluginExecutionContext context, string header = "InputParameters") 38 | { 39 | Trace(tracingService, context.InputParameters, header); 40 | } 41 | 42 | /// 43 | /// Logs IPluginExecutionContext OutputParameters content. 44 | /// 45 | /// IPluginExecutionContext instance 46 | public static void TraceOutputParameters(this ITracingService tracingService, IPluginExecutionContext context, string header = "OutputParameters") 47 | { 48 | Trace(tracingService, context.OutputParameters, header); 49 | } 50 | 51 | /// 52 | /// Logs IPluginExecutionContext SharedVariables content. 53 | /// 54 | /// IPluginExecutionContext instance 55 | public static void TraceSharedVariables(this ITracingService tracingService, IPluginExecutionContext context, string header = "SharedVariables") 56 | { 57 | Trace(tracingService, context.SharedVariables, header); 58 | } 59 | 60 | /// 61 | /// Logs IPluginExecutionContext PreEntityImages content. 62 | /// 63 | /// IPluginExecutionContext instance 64 | public static void TracePreEntityImages(this ITracingService tracingService, IPluginExecutionContext context, string header = "PreEntityImages") 65 | { 66 | Trace(tracingService, context.PreEntityImages, header); 67 | } 68 | 69 | /// 70 | /// Logs IPluginExecutionContext PostEntityImages content. 71 | /// 72 | /// IPluginExecutionContext instance 73 | public static void TracePostEntityImages(this ITracingService tracingService, IPluginExecutionContext context, string header = "PostEntityImages") 74 | { 75 | Trace(tracingService, context.PostEntityImages, header); 76 | } 77 | 78 | internal static void Trace(this ITracingService tracingService, ParameterCollection parameters, string header) 79 | { 80 | StringBuilder builder = new StringBuilder(); 81 | builder.AppendDataCollection(parameters, header); 82 | 83 | tracingService.Trace(builder.ToString()); 84 | } 85 | 86 | internal static void Trace(this ITracingService tracingService, EntityImageCollection images, string header) 87 | { 88 | StringBuilder builder = new StringBuilder(); 89 | builder.AppendDataCollection(images, header); 90 | 91 | tracingService.Trace(builder.ToString()); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/KeyAttributeCollection.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.Xrm.Sdk 10 | { 11 | /// 12 | /// Strongly typed version of the KeyAttributeCollection class 13 | /// 14 | public sealed class KeyAttributeCollection : DataCollection>, object> where T : Entity 15 | { 16 | public static implicit operator KeyAttributeCollection(KeyAttributeCollection t) 17 | { 18 | if (t == null) return null; 19 | 20 | var keys = new KeyAttributeCollection(); 21 | 22 | foreach (var kv in t) 23 | { 24 | keys.Add(LogicalName.GetName(kv.Key), kv.Value); 25 | } 26 | 27 | return keys; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/KeyAttributeCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.Xrm.Sdk 10 | { 11 | public static class KeyAttributeCollectionExtensions 12 | { 13 | /// 14 | /// Adds the specified key and value to the dictionary 15 | /// 16 | /// Type of the entity to add column from 17 | /// The property expressions containing the name of the attribute to add 18 | /// Attribute value 19 | public static void Add(this KeyAttributeCollection collection, Expression> key, object value) where T : Entity 20 | { 21 | collection.Add(LogicalName.GetName(key), value); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/LinkEntity.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | 6 | namespace Microsoft.Xrm.Sdk.Query 7 | { 8 | /// 9 | /// Strongly typed version of the LinkEntity class. 10 | /// 11 | public sealed class LinkEntity 12 | where TFrom : Entity 13 | where TTo : Entity 14 | { 15 | /// 16 | /// Initializes a new instance of the LinkEntity class. 17 | /// 18 | public LinkEntity() : this(null, null, JoinOperator.Inner) 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the LinkEntity class setting the required properties. 24 | /// 25 | /// The name of the attribute to link from. 26 | /// The name of the attribute to link to. 27 | /// The join operator. 28 | public LinkEntity( 29 | Expression> linkFromAttributeName, 30 | Expression> linkToAttributeName, 31 | JoinOperator joinOperator) 32 | { 33 | this.LinkFromAttributeName = linkFromAttributeName; 34 | this.LinkToAttributeName = linkToAttributeName; 35 | this.JoinOperator = joinOperator; 36 | this.Columns = new ColumnSet(); 37 | this.LinkCriteria = new FilterExpression(); 38 | this.Orders = new List(); 39 | this.LinkEntities = new List(); 40 | } 41 | 42 | /// 43 | /// Gets or sets the property expressions containing the name of the attribute of the entity that you are linking from. 44 | /// 45 | public Expression> LinkFromAttributeName { get; set; } 46 | 47 | /// 48 | /// Gets or sets the property expressions containing the name of the attribute of the entity that you are linking to. 49 | /// 50 | public Expression> LinkToAttributeName { get; set; } 51 | 52 | /// 53 | /// Gets or sets the join operator. 54 | /// 55 | public JoinOperator JoinOperator { get; set; } 56 | 57 | /// 58 | /// Gets or sets the alias for the entity. 59 | /// 60 | public string EntityAlias { get; set; } 61 | 62 | /// 63 | /// Gets or sets the column set. 64 | /// 65 | public ColumnSet Columns { get; set; } 66 | 67 | /// 68 | /// Gets or sets the complex condition and logical filter expressions that filter the results of the query. 69 | /// 70 | public FilterExpression LinkCriteria { get; set; } 71 | 72 | /// 73 | /// Gets the links between multiple entity types. 74 | /// 75 | public List LinkEntities { get; } 76 | 77 | /// 78 | /// Gets the orders for results of the query. 79 | /// 80 | public List Orders { get; } 81 | 82 | /// 83 | /// Converts LinkEntity to LinkEntity 84 | /// 85 | public static implicit operator LinkEntity(LinkEntity t) 86 | { 87 | if (t == null) return null; 88 | 89 | LinkEntity linkEntity = new LinkEntity() 90 | { 91 | Columns = t.Columns, 92 | EntityAlias = t.EntityAlias, 93 | JoinOperator = t.JoinOperator, 94 | LinkCriteria = t.LinkCriteria, 95 | LinkFromEntityName = LogicalName.GetName(), 96 | LinkFromAttributeName = LogicalName.GetName(t.LinkFromAttributeName), 97 | LinkToEntityName = LogicalName.GetName(), 98 | LinkToAttributeName = LogicalName.GetName(t.LinkToAttributeName), 99 | }; 100 | linkEntity.LinkEntities.AddRange(t.LinkEntities); 101 | linkEntity.Orders.AddRange(t.Orders); 102 | 103 | return linkEntity; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/LinkEntityExtentions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Microsoft.Xrm.Sdk.Query 9 | { 10 | /// 11 | /// Extensions for LinkEntity 12 | /// 13 | public static class LinkEntityExtensions 14 | { 15 | /// 16 | /// Adds a link, setting the link to entity name, the link from attribute name and 17 | /// the link to attribute name. 18 | /// 19 | /// Type of the entity to link from 20 | /// Type of the entity to link to 21 | /// The name of the entity to link to. 22 | /// The property expressions containing the name of the attribute to link from. 23 | /// The property expressions containing the name of the attribute to link to. 24 | /// The join operator. 25 | /// The link entity that was created. 26 | public static LinkEntity AddLink( 27 | this LinkEntity link, 28 | string linkToEntityName, 29 | Expression> linkFromAttributeName, 30 | Expression> linkToAttributeName, 31 | JoinOperator joinOperator) 32 | where TFrom : Entity 33 | where TTo : Entity 34 | { 35 | return link.AddLink( 36 | linkToEntityName, 37 | LogicalName.GetName(linkFromAttributeName), 38 | LogicalName.GetName(linkToAttributeName), 39 | joinOperator); 40 | } 41 | 42 | /// 43 | /// Adds a link, setting the link to entity name, the link from attribute name and 44 | /// the link to attribute name. 45 | /// 46 | /// Type of the entity to link from 47 | /// Type of the entity to link to 48 | /// The name of the entity to link to. 49 | /// The property expressions containing the name of the attribute to link from. 50 | /// The property expressions containing the name of the attribute to link to. 51 | /// The link entity that was created. 52 | public static LinkEntity AddLink( 53 | this LinkEntity link, 54 | string linkToEntityName, 55 | Expression> linkFromAttributeName, 56 | Expression> linkToAttributeName) 57 | where TFrom : Entity 58 | where TTo : Entity 59 | { 60 | return link.AddLink( 61 | linkToEntityName, 62 | LogicalName.GetName(linkFromAttributeName), 63 | LogicalName.GetName(linkToAttributeName)); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/LogicalName.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | using Microsoft.Xrm.Sdk.Client; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using System.Reflection; 9 | 10 | namespace D365Extensions 11 | { 12 | /// 13 | /// Helper class for reading property names from lambda expressions 14 | /// 15 | internal static class LogicalName 16 | { 17 | internal static string[] GetNames(params Expression>[] expressions) 18 | { 19 | var names = new string[expressions.Length]; 20 | 21 | for (int i = 0; i < names.Length; i++) 22 | { 23 | names[i] = GetName(expressions[i]); 24 | } 25 | 26 | return names; 27 | } 28 | 29 | internal static string GetName(Expression> lambda) 30 | { 31 | if (lambda == null) return null; 32 | 33 | Expression expression = lambda.Body; 34 | 35 | // Property, field of method returning value type 36 | if (expression is UnaryExpression unaryExpression) 37 | { 38 | expression = unaryExpression.Operand; 39 | } 40 | 41 | // Reference type property or field 42 | if (expression is MemberExpression memberExpession) 43 | { 44 | MemberInfo member = memberExpession.Member; 45 | 46 | // (Guid) Id attribute is declared in Entity class and is overridden in child EB-classes 47 | // For some reason, lambda is assigned with MemberInfo of Entity instead of inheritor 48 | if (member.DeclaringType != typeof(T) && member.DeclaringType.IsAssignableFrom(typeof(Entity))) 49 | member = typeof(T).GetMember(member.Name).SingleOrDefault(); 50 | 51 | return GetName(member); 52 | } 53 | 54 | throw CheckParam.InvalidExpression(nameof(expression)); 55 | } 56 | 57 | static ConcurrentDictionary memberCache = new ConcurrentDictionary(); 58 | 59 | internal static string GetName(MemberInfo member) 60 | { 61 | CheckParam.CheckForNull(member, nameof(member)); 62 | 63 | if (!memberCache.TryGetValue(member, out string logicalName)) 64 | { 65 | logicalName = member.GetCustomAttribute()?.LogicalName 66 | // fallback if attribute not provided 67 | ?? member.Name.ToLowerInvariant(); 68 | 69 | memberCache.TryAdd(member, logicalName); 70 | } 71 | 72 | return logicalName; 73 | } 74 | 75 | static ConcurrentDictionary typeCache = new ConcurrentDictionary(); 76 | 77 | internal static string GetName() where T : Entity 78 | { 79 | var type = typeof(T); 80 | 81 | if (!typeCache.TryGetValue(type, out string logicalName)) 82 | { 83 | logicalName = type.GetCustomAttribute()?.LogicalName 84 | // fallback if attribute not provided 85 | ?? type.Name.ToLowerInvariant(); 86 | 87 | typeCache.TryAdd(type, logicalName); 88 | } 89 | 90 | return logicalName; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/MoneyExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | 3 | namespace D365Extensions 4 | { 5 | public static class MoneyExtensions 6 | { 7 | /// 8 | /// Returns Money string representation 9 | /// 10 | /// 11 | public static string ToTraceString(this Money money) 12 | { 13 | return $"Money {{ Value = {money.Value} }}"; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/OptionSetValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.Xrm.Sdk 8 | { 9 | /// 10 | /// Strongly typed version of the OptionSetValue class 11 | /// 12 | public class OptionSetValue where T : Enum 13 | { 14 | /// 15 | /// Gets or sets the current value. 16 | /// 17 | public T Value { get; set; } 18 | 19 | public OptionSetValue() 20 | { 21 | } 22 | 23 | public OptionSetValue(T value) 24 | { 25 | Value = value; 26 | } 27 | 28 | public static implicit operator OptionSetValue(OptionSetValue t) 29 | { 30 | if (t == null) return null; 31 | 32 | return new OptionSetValue((int) (object) t.Value); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/OptionSetValueExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | 3 | namespace D365Extensions 4 | { 5 | public static class OptionSetValueExtensions 6 | { 7 | /// 8 | /// Returns OptionSetValue string representation 9 | /// 10 | /// 11 | public static string ToTraceString(this OptionSetValue optionSet) 12 | { 13 | return $"OptionSetValue {{ Value = {optionSet.Value} }}"; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/OrderExpression.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using Microsoft.Xrm.Sdk; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Microsoft.Xrm.Sdk.Query 11 | { 12 | /// 13 | /// Strongly typed version of the OrderExpression class 14 | /// 15 | /// 16 | public sealed class OrderExpression where T: Entity 17 | { 18 | /// 19 | /// Initializes a new instance of the OrderExpression class. 20 | /// 21 | public OrderExpression() 22 | { 23 | } 24 | 25 | /// 26 | /// Initializes a new instance of the OrderExpression class 27 | /// setting the attribute name and the order type. 28 | /// 29 | /// The name of the attribute. 30 | /// The order, ascending or descending. 31 | public OrderExpression(Expression> attributeName, OrderType orderType) 32 | { 33 | AttributeName = attributeName; 34 | OrderType = orderType; 35 | } 36 | 37 | /// 38 | /// Gets or sets the name of the attribute in the order expression. 39 | /// 40 | public Expression> AttributeName { get; set; } 41 | 42 | /// 43 | /// Gets or sets the order, ascending or descending. 44 | /// 45 | public OrderType OrderType { get; set; } 46 | 47 | /// 48 | /// Converts OrderExpression to OrderExpression 49 | /// 50 | public static implicit operator OrderExpression(OrderExpression t) 51 | { 52 | if (t == null) return null; 53 | 54 | return new OrderExpression(LogicalName.GetName(t.AttributeName), t.OrderType); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/OrganizationRequestCollectionEnumerator.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System.Collections.Generic; 3 | 4 | namespace Microsoft.Xrm.Sdk 5 | { 6 | internal static class OrganizationRequestCollectionEnumerator 7 | { 8 | internal static IEnumerable Chunk(this IEnumerable source, int size) 9 | { 10 | CheckParam.CheckForNull(source, nameof(source)); 11 | CheckParam.BiggerThanOne(size, nameof(size)); 12 | 13 | return ChunkIterator(source, size); 14 | } 15 | 16 | private static IEnumerable ChunkIterator(IEnumerable source, int size) 17 | { 18 | using (IEnumerator e = source.GetEnumerator()) 19 | { 20 | while (e.MoveNext()) 21 | { 22 | OrganizationRequestCollection requests = new OrganizationRequestCollection 23 | { 24 | e.Current 25 | }; 26 | 27 | for (int i = 1; i < size; i++) 28 | { 29 | if (!e.MoveNext()) 30 | { 31 | yield return requests; 32 | yield break; 33 | } 34 | 35 | requests.Add(e.Current); 36 | } 37 | 38 | yield return requests; 39 | } 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/OrganizationServiceFaultExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.ServiceModel; 7 | 8 | namespace Microsoft.Xrm.Sdk 9 | { 10 | 11 | /// 12 | /// Converts OrganizationService fault`s ErrorCode to ErrorCodes enumeration value. 13 | /// 14 | /// Returns null if ErrorCode is unknown 15 | /// 16 | public static class OrganizationServiceFaultExtensions 17 | { 18 | public static ErrorCodes? GetErrorCode(this FaultException fault) 19 | { 20 | int code = fault.Detail.ErrorCode; 21 | 22 | return Enum.IsDefined(typeof(ErrorCodes), code) ? (ErrorCodes?)code : null; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/PluginExecutionTraceContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace D365Extensions 7 | { 8 | /// 9 | /// PluginExecutionTraceContext is a disposable wrapper around IPluginExecutionContext and ITracingService 10 | /// that simplifies tracing of plugin execution context properties in memory efficient way 11 | /// 12 | /// You SHOULD notice that plugin execution context will be printed only at the and of plugin execution 13 | /// for performance reasons. If you modify plugin execution context during runtime, e.g. modify Target 14 | /// or add OutputParameters, then MODIFIED execution context will be printed. If you want to trace original 15 | /// values, you should trace them manually or using existing extension methods like TraceInputParameters 16 | /// 17 | public sealed class PluginExecutionTraceContext : ITracingService, IDisposable 18 | { 19 | public IPluginExecutionContext PluginExecutionContext { get; private set; } 20 | 21 | public PluginExecutionTraceContextSettings Settings { get; set; } 22 | 23 | // this one should be private to prevent user from using it directly 24 | internal ITracingService TracingService { get; private set; } 25 | 26 | private readonly StringBuilder builder = new StringBuilder(); 27 | 28 | public PluginExecutionTraceContext(ITracingService tracingService, IPluginExecutionContext pluginContext, PluginExecutionTraceContextSettings settings = null) 29 | { 30 | CheckParam.CheckForNull(tracingService, nameof(tracingService)); 31 | CheckParam.CheckForNull(pluginContext, nameof(pluginContext)); 32 | 33 | this.PluginExecutionContext = pluginContext; 34 | this.TracingService = tracingService; 35 | this.Settings = settings ?? new PluginExecutionTraceContextSettings(); 36 | } 37 | 38 | public void Dispose() 39 | { 40 | // by default we print trace only in case of error 41 | if (Settings.ErrorOnly && Marshal.GetExceptionCode() == 0) 42 | return; 43 | 44 | TracingService.TracePluginContext(PluginExecutionContext); 45 | 46 | if (PluginExecutionContext.InputParameters?.Count > 0 || Settings.ShowEmptyCollections) 47 | TracingService.TraceInputParameters(PluginExecutionContext); 48 | 49 | if (PluginExecutionContext.PreEntityImages?.Count > 0 || Settings.ShowEmptyCollections) 50 | TracingService.TracePreEntityImages(PluginExecutionContext); 51 | 52 | if (PluginExecutionContext.SharedVariables?.Count > 0 || Settings.ShowEmptyCollections) 53 | TracingService.TraceSharedVariables(PluginExecutionContext); 54 | 55 | if (PluginExecutionContext.OutputParameters?.Count > 0 || Settings.ShowEmptyCollections) 56 | TracingService.TraceOutputParameters(PluginExecutionContext); 57 | 58 | if (PluginExecutionContext.PostEntityImages?.Count > 0 || Settings.ShowEmptyCollections) 59 | TracingService.TracePostEntityImages(PluginExecutionContext); 60 | 61 | // once I've seen a bug in online version there ParentContext reference was pointing to current context 62 | if (PluginExecutionContext.ParentContext != null && Settings.IncludeParentContext && PluginExecutionContext != PluginExecutionContext.ParentContext) 63 | { 64 | TracingService.Trace("Parent context:"); 65 | 66 | var parentContext = new PluginExecutionTraceContext(TracingService, PluginExecutionContext.ParentContext, Settings); 67 | parentContext.Dispose(); 68 | } 69 | 70 | // pushing collected logs 71 | if (builder.Length > 0) 72 | { 73 | TracingService.Trace(builder.ToString()); 74 | } 75 | } 76 | 77 | public void Trace(string format, params object[] args) 78 | { 79 | builder.AppendFormat(format, args); 80 | builder.AppendLine(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/PluginExecutionTraceContextSettings.cs: -------------------------------------------------------------------------------- 1 | namespace D365Extensions 2 | { 3 | public sealed class PluginExecutionTraceContextSettings 4 | { 5 | /// 6 | /// Print, empty collections, such as PostEntityImages, or OutputParameters even if they are empty. Default = false 7 | /// 8 | public bool ShowEmptyCollections { get; set; } = false; 9 | 10 | /// 11 | /// Print output only in case of error. Default = true 12 | /// You SHOULD notice that tracing code will be executed even if you turn off tracing in your D365 environment. 13 | /// Meanwhile, serializing plugin execution context that include EntityImages or InputParameters may be memory consuming operation. 14 | /// If this setting is set to "true" tracing will be skipped for plugins that there executed without errors. 15 | /// 16 | public bool ErrorOnly { get; set; } = true; 17 | 18 | /// 19 | /// Print ParentContext. Default = false 20 | /// You SHOULD notice that it is experimental feature. It may cause infinite recursion loop 21 | /// 22 | public bool IncludeParentContext { get; set; } = false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Setting ComVisible to false makes the types in this assembly not visible 6 | // to COM components. If you need to access a type in this assembly from 7 | // COM, set the ComVisible attribute to true on that type. 8 | [assembly: ComVisible(false)] 9 | 10 | // The following GUID is for the ID of the typelib if this project is exposed to COM 11 | [assembly: Guid("0fd54081-45d9-41c1-8732-13034bdbb8e6")] 12 | 13 | #if DEBUG 14 | [assembly: InternalsVisibleTo("D365Extensions.Tests")] 15 | #else 16 | [assembly: InternalsVisibleTo("D365Extensions.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001004d75870d5da62e09344a1dcd5a3e5006ff4c8a6b7c13560988e21b11a29b11bcf0f650eabccb11523898199ce9403c5132e1ae77150a964c0d7462b7aaf3e4acd6cb0c1f85ce4fa5152377c9bd2e2cd9992474c99669614c07d6b85440444885ed7a526f6398bf8d3eb4ad58d6cebe996dccc6e8c2ab07394f51c3fdc3dcacc1")] 17 | #endif 18 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/QueryBaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk.Query; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml; 8 | using System.Xml.Linq; 9 | 10 | namespace Microsoft.Xrm.Sdk.Query 11 | { 12 | //BC: this class should be internal 13 | 14 | /// 15 | /// Extensions for Microsoft.Xrm.Sdk.Query types 16 | /// 17 | public static class QueryExtensions 18 | { 19 | /// 20 | /// Universal method to set Query paging parameters to next page 21 | /// 22 | public static void NextPage(this QueryBase query, string pagingCookie) 23 | { 24 | switch (query) 25 | { 26 | case QueryExpression qe: 27 | qe.NextPage(pagingCookie); 28 | break; 29 | case QueryByAttribute qa: 30 | qa.NextPage(pagingCookie); 31 | break; 32 | case FetchExpression fe: 33 | fe.NextPage(pagingCookie); 34 | break; 35 | } 36 | } 37 | 38 | /// 39 | /// Sets QueryExpression paging parameters to next page 40 | /// 41 | public static void NextPage(this QueryExpression query, string pagingCookie) 42 | { 43 | query.PageInfo.PageNumber++; 44 | query.PageInfo.PagingCookie = pagingCookie; 45 | } 46 | 47 | /// 48 | /// Sets QueryByAttribute paging parameters to next page 49 | /// 50 | public static void NextPage(this QueryByAttribute query, string pagingCookie) 51 | { 52 | if (query.PageInfo == null) query.PageInfo = new PagingInfo(); 53 | 54 | query.PageInfo.PageNumber++; 55 | query.PageInfo.PagingCookie = pagingCookie; 56 | } 57 | 58 | /// 59 | /// Sets FetchExpression paging parameters to next page 60 | /// 61 | public static void NextPage(this FetchExpression query, string pagingCookie) 62 | { 63 | XDocument xDocument = XDocument.Parse(query.Query); 64 | NextPage(query, xDocument, pagingCookie); 65 | } 66 | 67 | /// 68 | /// Internal method for better FetchExpression paging performance 69 | /// 70 | internal static void NextPage(this FetchExpression query, XDocument xDocument, string pagingCookie) 71 | { 72 | /// No paging mean PageInfo.Page = 0 73 | /// for FetchXml it mean no "page" attribute or 0 value 74 | string page = xDocument.Root.Attribute("page")?.Value; 75 | int pageNumber = page != null ? int.Parse(page) : 0; 76 | 77 | xDocument.Root.SetAttributeValue("paging-cookie", pagingCookie); 78 | xDocument.Root.SetAttributeValue("page", ++pageNumber); 79 | 80 | query.Query = xDocument.ToString(); 81 | } 82 | 83 | /// 84 | /// Universal method to get Query page number 85 | /// 86 | public static int GetPageNumber(this QueryBase query) 87 | { 88 | switch (query) 89 | { 90 | case QueryExpression qe: return qe.GetPageNumber(); 91 | case QueryByAttribute qa: return qa.GetPageNumber(); 92 | case FetchExpression fe: return fe.GetPageNumber(); 93 | default: throw new NotSupportedException(); 94 | } 95 | } 96 | 97 | /// 98 | /// Gets QueryExpression page number 99 | /// 100 | public static int GetPageNumber(this QueryExpression query) 101 | { 102 | return query.PageInfo.PageNumber; 103 | } 104 | 105 | /// 106 | /// Gets QueryByAttribute page number 107 | /// 108 | public static int GetPageNumber(this QueryByAttribute query) 109 | { 110 | return query.PageInfo != null ? query.PageInfo.PageNumber : 0; 111 | } 112 | 113 | /// 114 | /// Gets FetchExpression page number 115 | /// 116 | public static int GetPageNumber(this FetchExpression query) 117 | { 118 | return GetPageNumber(query, null); 119 | } 120 | 121 | /// 122 | /// Internal method for better FetchExpression paging performance 123 | /// 124 | internal static int GetPageNumber(this FetchExpression query, XDocument xDoc) 125 | { 126 | XDocument xDocument = xDoc ?? XDocument.Parse(query.Query); 127 | 128 | /// No paging mean PageInfo.Page = 0 129 | /// for FetchXml it mean no "page" attribute or 0 value 130 | string page = xDocument.Root.Attribute("page")?.Value; 131 | return page != null ? int.Parse(page) : 0; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/QueryByAttributeExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.Xrm.Sdk.Query 10 | { 11 | /// 12 | /// Extensions for QueryByAttribute 13 | /// 14 | public static class QueryByAttributeExtensions 15 | { 16 | /// 17 | /// Adds an attribute value to the attributes collection. 18 | /// 19 | /// Type of the entity 20 | /// The property expressions containing the name of the attribute 21 | /// The attribute value. 22 | public static void AddAttribute(this QueryByAttribute query, Expression> attributeName, object value) where T : Entity 23 | { 24 | query.AddAttributeValue(LogicalName.GetName(attributeName), value); 25 | } 26 | 27 | /// 28 | /// Adds an order to the orders collection. 29 | /// 30 | /// Type of the entity 31 | /// The property expressions containing the name of the attribute 32 | /// The order for that attribute. 33 | public static void AddOrder(this QueryByAttribute query, Expression> attributeName, OrderType orderType) where T : Entity 34 | { 35 | query.AddOrder(LogicalName.GetName(attributeName), orderType); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/QueryExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using D365Extensions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.Xrm.Sdk.Query 10 | { 11 | /// 12 | /// Extensions for QueryExpression 13 | /// 14 | public static class QueryExpressionExtensions 15 | { 16 | /// 17 | /// Adds the specified order expression to the query expression. 18 | /// 19 | /// The property expressions containing the name of the attribute 20 | /// The order for that attribute. 21 | public static void AddOrder(this QueryExpression query, Expression> attributeName, OrderType orderType) where T : Entity 22 | { 23 | query.AddOrder(LogicalName.GetName(attributeName), orderType); 24 | } 25 | 26 | /// 27 | /// Adds the specified link to the query expression setting the entity name to link 28 | /// to, the attribute name to link from and the attribute name to link to. 29 | /// Type of the entity to link from 30 | /// Type of the entity to link to 31 | /// The property expressions containing the name of the attribute to link from. 32 | /// The property expressions containing the name of the attribute to link to. 33 | public static LinkEntity AddLink( 34 | this QueryExpression query, 35 | Expression> linkFromAttributeName, 36 | Expression> linkToAttributeName) 37 | where TFrom : Entity 38 | where TTo : Entity 39 | { 40 | return query.AddLink(LogicalName.GetName(), 41 | LogicalName.GetName(linkFromAttributeName), 42 | LogicalName.GetName(linkToAttributeName)); 43 | } 44 | 45 | /// 46 | /// Adds the specified link to the query expression setting the entity name to link 47 | /// to, the attribute name to link from and the attribute name to link to. 48 | /// Type of the entity to link from 49 | /// Type of the entity to link to 50 | /// The property expressions containing the name of the attribute to link from. 51 | /// The property expressions containing the name of the attribute to link to. 52 | /// The join operator. 53 | public static LinkEntity AddLink( 54 | this QueryExpression query, 55 | Expression> linkFromAttributeName, 56 | Expression> linkToAttributeName, JoinOperator joinOperator) 57 | where TFrom : Entity 58 | where TTo : Entity 59 | { 60 | return query.AddLink(LogicalName.GetName(), 61 | LogicalName.GetName(linkFromAttributeName), 62 | LogicalName.GetName(linkToAttributeName), 63 | joinOperator); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/StringBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | using System; 3 | using System.Text; 4 | 5 | namespace D365Extensions 6 | { 7 | internal static class StringBuilderExtensions 8 | { 9 | internal static StringBuilder AppendEntity(this StringBuilder builder, Entity entity) 10 | { 11 | builder.AppendLine($@"Entity {{ LogicalName = ""{entity.LogicalName}"", Id = ""{entity.Id:B}"" }}"); 12 | builder.AppendDataCollection(entity.Attributes, "Attributes"); 13 | 14 | return builder; 15 | } 16 | 17 | internal static StringBuilder AppendEntityCollection(this StringBuilder builder, EntityCollection collection) 18 | { 19 | builder.AppendLine($@"EntityCollection {{ EntityName = ""{collection.EntityName}"" }}"); 20 | builder.Append($"Entities: {{ Count = {collection.Entities.Count} }} "); 21 | 22 | foreach (var entity in collection.Entities) 23 | { 24 | builder.AppendLine(); 25 | builder.AppendEntity(entity); 26 | } 27 | 28 | return builder; 29 | } 30 | 31 | internal static StringBuilder AppendDataCollection(this StringBuilder builder, DataCollection collection, string header) 32 | { 33 | builder.Append($"{header}: {collection.GetType().Name} {{ Count = {collection.Count} }}"); 34 | 35 | foreach (var keyValue in collection) 36 | { 37 | builder.AppendLine(); 38 | builder.Append($@"""{keyValue.Key}"": ").AppendObject(keyValue.Value); 39 | } 40 | 41 | return builder; 42 | } 43 | 44 | internal static StringBuilder AppendObject(this StringBuilder builder, object value) 45 | { 46 | switch (value) 47 | { 48 | case EntityReference reference: 49 | return builder.Append(reference.ToTraceString()); 50 | 51 | case Money money: 52 | return builder.Append(money.ToTraceString()); 53 | 54 | case OptionSetValue optionSet: 55 | return builder.Append(optionSet.ToTraceString()); 56 | 57 | case Entity entity: 58 | return builder.AppendEntity(entity); 59 | 60 | case EntityCollection entityCollection: 61 | return builder.AppendEntityCollection(entityCollection); 62 | 63 | case string str: 64 | return builder.Append($@"""{str}"""); 65 | 66 | case Guid guid: 67 | return builder.Append($@"""{guid:B}"""); 68 | 69 | default: 70 | return builder.Append(value); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/scripts/GenerateErrorCodesEnum.ps1: -------------------------------------------------------------------------------- 1 | $url = "https://raw.githubusercontent.com/MicrosoftDocs/powerapps-docs/main/powerapps-docs/developer/data-platform/includes/data-service-error-codes.md" 2 | 3 | $credits = @" 4 | //------------------------------------------------------------------------------ 5 | // 6 | // This code was generated by a tool. Please refer to original documentation from Microsoft: 7 | // $url 8 | // 9 | // Changes to this file may cause incorrect behavior and will be lost if 10 | // the code is regenerated. 11 | // 12 | //------------------------------------------------------------------------------ 13 | "@ 14 | 15 | $nameSpace = "Microsoft.Xrm.Sdk" 16 | $enum = "ErrorCodes" 17 | $tab = " " 18 | $outfile = "..\ErrorCodes.cs" 19 | 20 | #get errors description from docs 21 | $resp = Invoke-WebRequest -Uri $Url 22 | $rawContent = $resp.Content 23 | 24 | #generate enum values from description 25 | $searchExp = "\|``(?.+)````(?.+)``\|Name:\s+\*\*(?.+)\*\*Message:\s+``(?.+)``\|.?" 26 | $replaceExp = "///`r`n///`${hex} - `${comment}`r`n///`r`n`${name} = `${code}," 27 | 28 | $content = $rawContent -replace $SearchExp, $ReplaceExp 29 | 30 | #write generated code to file 31 | $writer = [System.IO.StreamWriter]::new($outfile) 32 | 33 | $writer.WriteLine($credits) 34 | $writer.WriteLine("namespace $nameSpace") 35 | $writer.WriteLine("{") 36 | $writer.WriteLine("${tab}public enum $enum") 37 | $writer.WriteLine("$tab{") 38 | 39 | $reader = [System.IO.StringReader]::new($content) 40 | 41 | #skip table header 42 | $line= $reader.ReadLine() 43 | $line= $reader.ReadLine() 44 | 45 | while ($line -ne $null) { 46 | $line = $reader.ReadLine() 47 | 48 | if([string]::IsNullOrEmpty($line)) 49 | { 50 | continue 51 | } 52 | 53 | $writer.WriteLine("$tab$tab$line") 54 | } 55 | 56 | $writer.WriteLine("$tab}") 57 | $writer.Write("}") 58 | $writer.Close() 59 | 60 | trap { 61 | $writer.Close() 62 | "writer closed" 63 | break 64 | } -------------------------------------------------------------------------------- /D365Extensions/D365Extensions/СheckParam.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xrm.Sdk; 2 | using Microsoft.Xrm.Sdk.Query; 3 | using System; 4 | using System.Xml.Linq; 5 | 6 | namespace D365Extensions 7 | { 8 | /// 9 | /// Helper class for throwing argument exceptions 10 | /// 11 | internal static class CheckParam 12 | { 13 | internal static void CheckForNull(object parameter, string name) 14 | { 15 | if (parameter == null) 16 | { 17 | throw new ArgumentNullException(name); 18 | } 19 | } 20 | 21 | internal static void BiggerThanOne(int parameter, string name) 22 | { 23 | if (parameter < 1) 24 | { 25 | throw new ArgumentOutOfRangeException(name); 26 | } 27 | } 28 | 29 | internal static ArgumentException InvalidExpression(string name) 30 | { 31 | return new ArgumentException("Invalid expression", name); 32 | } 33 | 34 | internal const string InvalidPageNumberMessage = @"Query page number is bigger than one. It may mean that you are trying to get second iterator from IEnumerable returned from RetrieveMultiple extension overload. For instance you may be using FirstOrDefault(Predicate) or similar in a loop. It is not supported because this method is yielding results WITHOUT allocating each page in memory. Most likely it will lead to wrong results as second iterator will start from the same page there first one has stopped. Reseting page counter is either not an option as this will result in querying same data from the system multiple times. If you need iterate over this data multiple times you are responsible for allocation it in memory yourself."; 35 | 36 | internal static void CheckPageNumber(QueryBase query) 37 | { 38 | if (query.GetPageNumber() > 0) 39 | { 40 | throw new ArgumentException(InvalidPageNumberMessage); 41 | } 42 | } 43 | 44 | internal static void CheckPageNumber(FetchExpression fetch, XDocument document) 45 | { 46 | if (fetch.GetPageNumber(document) > 0) 47 | { 48 | throw new ArgumentException(InvalidPageNumberMessage); 49 | } 50 | } 51 | 52 | internal const string InvalidCollectionMessage = "This method supports only ActivityParty collections"; 53 | 54 | internal static void IsActivityPartyCollection(EntityCollection collection) 55 | { 56 | if (collection.EntityName != "activityparty") 57 | throw new ArgumentException(InvalidCollectionMessage); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 FixRM 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NuGet version (D365Extensions)](https://img.shields.io/nuget/v/D365Extensions.svg?style=flat-square)](https://www.nuget.org/packages/D365Extensions/) [![Build Status](https://dev.azure.com/fixrm/FixRM/_apis/build/status/D365Extensions%20YAML?branchName=master)](https://dev.azure.com/fixrm/FixRM/_build/latest?definitionId=20&branchName=master) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) 2 | 3 | # D365Extensions 4 | A collection of Extension methods for Microsoft Dynamics CRM/D365 SDK base classes 5 | 6 | # Setup 7 | All extension methods are declared in the same namespace as related SDK types. No additional `using` statements required. 8 | 9 | # Usage 10 | This library is assumed to be used for plugin development. As D365 for CE currently doesn't support assembly dependencies, you have to merge it with your primary plugin assembly. We recommend using this tool: 11 | 12 | [ILRepack.Lib.MSBuild.Task](https://github.com/ravibpatel/ILRepack.Lib.MSBuild.Task) 13 | 14 | ILRepack use the same technique as ILMerge but it is build on newer versions of Mono instruments so it is more fast and efficient. Please refer to link above for documentation. 15 | 16 | To configure this task your should add `ILRepack.targets` file to you project. File contents should be looking like the following: 17 | ```XML 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | ``` 35 | You should use `KeyFile` parameter as your plugin assembly should be signed. We also recommend use `LibraryPath` parameter as shown to avoid merge problems with dependent SDK assemblies. You shouldn't overrite your assembly with merged one as has some side effects. For instance, it may complicate developing of unit tests if test project and plugin library has common dependencies. In this case you can get runtime errors saying that some types are ambiguous. 36 | 37 | >**!!! Never merge SDK assemblies in your code. It will cause runtime errors !!!** 38 | 39 | # What's new 40 | 41 | [Semantic Versioning](https://semver.org) is used since v1.1.0. For version history please refer to [CHANGELOG.md](CHANGELOG.md) 42 | 43 | # Extensions 44 | 45 | ## [Entity Extensions](../../wiki/Entity-Extensions) 46 | Set of extension methods for Microsoft.Xrm.Sdk.Entity base class. Simplifies dealing with Aliased and Formated values as well as working with Attributes collection. 47 | 48 | ## [IOrganizationService Extensions](../../wiki/IOrganizationService-Extensions) 49 | Set of extension methods for IOrganizationService base class. Basically these are simple overrides of existing methods which take EntityReference or Entity instead of separate `Id` and `LogicalName` parameters. 50 | 51 | ## [IPluginExecutionContext Extensions](../../wiki/IPluginExecutionContext-Extensions) 52 | Set of extension methods for Microsoft.Xrm.Sdk.IPluginExecutionContext base class. Most of this helpers are shortcuts for existing properties but provides additional checks or type casts. Unlike Entity class extensions most of the following extensions are not exception safe! It is done so because you most likely want to get an error if plugin is registered for a wrong message or you have a typo in parameter name. 53 | 54 | ## [CodeActivityContext Extensions](../../wiki/CodeActivityContext-Extensions) 55 | Set of extension methods for System.Activities.CodeActivityContext base class. Shortcut methods for getting D365 related services from workflow execution context. 56 | 57 | ## [IServiceProvider Extensions](../../wiki/IServiceProvider-Extensions) 58 | Set of extension methods for Microsoft.Xrm.Sdk.IServiceProvider base class. Just shortcut methods to save you few lines of code during plugin development. 59 | 60 | ## [EntityReference Extensions](../../wiki/EntityReference-Extensions) 61 | Set of extension methods for Microsoft.Xrm.Sdk.EntityReference base class. At the moment just two simple but sometimes useful type conversion methods. 62 | 63 | ## [Query Extensions](../../wiki/Query-Extensions) 64 | Set of extension methods to support some expression-style/LINQ techniques while using QueryExpression/QueryByAttribute classes. 65 | 66 | # Contributing 67 | Please fill free to create issue if you find a bug or have an idea. PR's are welcomed as well! :) Help wanted in the following areas: 68 | + Unit tests. Most of extensions are just wrappers/overrides of SDK classes but as list of extension grows method call chain grows as well. It seems like it's a time to unit check all methods 69 | + Code documentation. As it turns out XML code documentation and wiki documentation are very different. Help with updating code doc is appreciated 70 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # D365Extensions build pipeline 2 | variables: 3 | semver: 2.5.0 4 | solution: '**/*.sln' 5 | buildPlatform: 'Any CPU' 6 | buildConfiguration: 'Release' 7 | 8 | # Build Number 9 | name: $(semver)$(Rev:.r) 10 | 11 | # Should trigger master, PR's or tags 12 | trigger: 13 | batch: true 14 | branches: 15 | include: 16 | - master 17 | # - refs/tags/* 18 | 19 | pool: 20 | vmImage: 'windows-latest' 21 | 22 | steps: 23 | - task: NuGetToolInstaller@0 24 | displayName: Use NuGet 5.10.0 25 | inputs: 26 | versionSpec: '5.10.0' 27 | 28 | - task: NuGetCommand@2 29 | displayName: NuGet restore 30 | inputs: 31 | restoreSolution: '$(solution)' 32 | 33 | - task: DownloadSecureFile@1 34 | displayName: Download secure file 35 | inputs: 36 | secureFile: ae991075-eb04-43de-afb0-c180d1f0e314 37 | retryCount: 5 38 | 39 | - task: CopyFiles@2 40 | displayName: 'Copy FixRM.snk to: D365Extensions/D365Extensions' 41 | inputs: 42 | SourceFolder: $(Agent.TempDirectory) 43 | Contents: FixRM.snk 44 | TargetFolder: D365Extensions/D365Extensions 45 | 46 | - task: CopyFiles@2 47 | displayName: 'Copy FixRM.snk to: D365Extensions/D365Extensions.Tests' 48 | inputs: 49 | SourceFolder: $(Agent.TempDirectory) 50 | Contents: FixRM.snk 51 | TargetFolder: D365Extensions/D365Extensions.Tests 52 | 53 | - task: VSBuild@1 54 | displayName: Build solution **\*.sln 55 | inputs: 56 | solution: '$(solution)' 57 | platform: '$(buildPlatform)' 58 | configuration: '$(buildConfiguration)' 59 | 60 | - task: VSTest@2 61 | displayName: VsTest - testAssemblies 62 | inputs: 63 | testAssemblyVer2: | 64 | **\$(BuildConfiguration)\*\*test*.dll 65 | !**\*TestAdapter.dll 66 | !**\obj\** 67 | codeCoverageEnabled: true 68 | platform: '$(buildPlatform)' 69 | configuration: '$(buildConfiguration)' 70 | diagnosticsEnabled: True 71 | 72 | - task: NuGetCommand@2 73 | displayName: NuGet pack 74 | inputs: 75 | command: pack 76 | searchPatternPack: '**/*.csproj;!**\*.Tests.csproj;!**\*.Benchmark.csproj' 77 | versioningScheme: byEnvVar 78 | versionEnvVar: semver 79 | condition: ne(variables['Build.Reason'], 'PullRequest') 80 | 81 | - task: PublishBuildArtifacts@1 82 | displayName: 'Publish Artifact: drop' 83 | inputs: 84 | PathtoPublish: $(build.artifactstagingdirectory) 85 | TargetPath: '\\my\share\$(Build.DefinitionName)\$(Build.BuildNumber)' 86 | condition: ne(variables['Build.Reason'], 'PullRequest') 87 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; -------------------------------------------------------------------------------- /fixrmlogo_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FixRM/D365Extensions/2ac1af87c433b32b827fa1f7b75deed43a6254fd/fixrmlogo_64.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d365extensions", 3 | "author": "Artem Grunin", 4 | "version": "2.5.0", 5 | "description": "build and versioning scripts", 6 | "devDependencies": { 7 | "@commitlint/cli": "^13.2.1", 8 | "@commitlint/config-conventional": "^13.2.0", 9 | "husky": "^7.0.2", 10 | "standard-version": "^9.3.1" 11 | }, 12 | "scripts": { 13 | "release": "standard-version" 14 | }, 15 | "husky": { 16 | "hooks": { 17 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /scripts/azure-pipeline-updater.js: -------------------------------------------------------------------------------- 1 | // azure-pipeline-updater.js 2 | 3 | const regex = /semver:\s+(\d+\.\d+\.\d+)/; 4 | 5 | module.exports.readVersion = function (contents) { 6 | return contents.match(regex)[1]; 7 | }; 8 | 9 | module.exports.writeVersion = function (contents, version) { 10 | return contents.replace(regex, `semver: ${version}`); 11 | }; --------------------------------------------------------------------------------