Skip to content

Commit 3f61ca8

Browse files
committed
Added conditional operations sample
1 parent 852c44a commit 3f61ca8

File tree

3 files changed

+302
-3
lines changed

3 files changed

+302
-3
lines changed
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
---
2+
title: "Conditional Operation sample (C#) (Common Data Service)| Microsoft Docs"
3+
description: "This sample shows how to perform conditional message operations when accessing records of the Common Data Service."
4+
ms.custom: ""
5+
ms.date: 07/16/2020
6+
ms.service: powerapps
7+
applies_to:
8+
- "Dynamics 365 (online)"
9+
author: "JimDaly"
10+
ms.author: "pehecke"
11+
ms.reviewer: "pehecke"
12+
search.audienceType:
13+
- developer
14+
search.app:
15+
- PowerApps
16+
- D365CE
17+
---
18+
# Conditional Operations sample (C#)
19+
20+
This sample shows how to perform conditional message operations when accessing entity instance records of the Common Data Service. The sample uses the Common Data Service Web API and the CDSWebApiService class.
21+
22+
Messages using a conditional statement, such as "If-None-Match", in the message header are sent to Common Data Service.
23+
24+
> [!NOTE]
25+
> This sample implements the Common Data Service operations and console output detailed in [Web API Conditional Operations Sample](../web-api-conditional-operations-sample.md) and uses the methods available in the [CDSWebApiService](cdswebapiservice.md) class for message processing, performance enhancements, and error management.
26+
27+
## Prerequisites
28+
29+
The following is required to build and run the sample:
30+
31+
- Microsoft Visual Studio 2019.
32+
- Access to Common Data Service with privileges to perform the operations described above.
33+
34+
## How to run this sample
35+
36+
1. Go to the [PowerApps-Samples](https://github.com/microsoft/PowerApps-Samples/) repository and either clone or download the compressed samples repository. If you downloaded the compressed file, extract its contents into a local folder.
37+
38+
1. Navigate to the cds/webapi/C#/[ConditionalOperations](https://github.com/microsoft/PowerApps-Samples/tree/master/cds/webapi/C%23/ConditionalOperations) folder and load the solution file into Visual Studio.
39+
40+
1. Edit the App.config file that is shared by several of the samples and set appropriate values for the Common Data Service environment you intend to use: connectionString `Url`, `UserPrincipalName`, and `Password`. You only need to perform this step once for all samples that share this file.
41+
42+
1. Press F5 to build and run the program in debug mode.
43+
44+
## Code listing
45+
46+
This sample depends on the assembly built from in the CDSWebApiService project. For information on the methods this class provides see [CDSWebApiService class](cdswebapiservice.md).
47+
48+
The following is the code from the Program.cs file:
49+
50+
```csharp
51+
using Newtonsoft.Json;
52+
using Newtonsoft.Json.Linq;
53+
using System;
54+
using System.Collections.Generic;
55+
using System.Configuration;
56+
using System.Linq;
57+
using System.Net;
58+
59+
namespace PowerApps.Samples
60+
{
61+
/// <summary>
62+
/// This program demonstrates use of conditional operations with the
63+
/// Common Data Service Web API.
64+
/// </summary>
65+
class Program
66+
{
67+
//Get environment configuration data from the connection string in the App.config file.
68+
static readonly string connectionString =
69+
ConfigurationManager.ConnectionStrings["Connect"].ConnectionString;
70+
static readonly ServiceConfig config = new ServiceConfig(connectionString);
71+
72+
static void Main()
73+
{
74+
// Save the URIs for entity records created in this sample. so they
75+
// can be deleted later.
76+
List<Uri> entityUris = new List<Uri>();
77+
78+
try
79+
{
80+
// Use the wrapper class that handles message processing, error handling, and more.
81+
using (CDSWebApiService svc = new CDSWebApiService(config))
82+
{
83+
Console.WriteLine("--Starting conditional operations demonstration--\n");
84+
85+
#region Create required records
86+
// Create an account record
87+
var account1 = new JObject {
88+
{ "name", "Contoso Ltd" },
89+
{ "telephone1", "555-0000" }, //Phone number value increments with each update attempt
90+
{ "revenue", 5000000},
91+
{ "description", "Parent company of Contoso Pharmaceuticals, etc."} };
92+
93+
Uri account1Uri = svc.PostCreate("accounts", account1);
94+
entityUris.Add(account1Uri); // Track any created records
95+
96+
// Retrieve the account record that was just created.
97+
string queryOptions = "?$select=name,revenue,telephone1,description";
98+
var retrievedaccount1 = svc.Get(account1Uri.ToString() + queryOptions);
99+
100+
// Store the ETag value from the retrieved record
101+
string initialAcctETagVal = retrievedaccount1["@odata.etag"].ToString();
102+
103+
Console.WriteLine("Created and retrieved the initial account, shown below:");
104+
Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented));
105+
#endregion Create required records
106+
107+
#region Conditional GET
108+
Console.WriteLine("\n** Conditional GET demonstration **");
109+
110+
// Attempt to retrieve the account record using a conditional GET defined by a message header with
111+
// the current ETag value.
112+
try
113+
{
114+
retrievedaccount1 = svc.Get(
115+
path: account1Uri.ToString() + queryOptions,
116+
headers: new Dictionary<string, List<string>> {
117+
{ "If-None-Match", new List<string> {initialAcctETagVal}}}
118+
);
119+
120+
// Not expected; the returned response contains content.
121+
Console.WriteLine("Instance retrieved using ETag: {0}", initialAcctETagVal);
122+
Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented));
123+
}
124+
catch (AggregateException ae) // Message was not successful
125+
{
126+
ae.Handle((x) =>
127+
{
128+
if (x is ServiceException) // This we know how to handle.
129+
{
130+
var e = x as ServiceException;
131+
if (e.StatusCode == (int)HttpStatusCode.NotModified) // Expected result
132+
{
133+
Console.WriteLine("Account record retrieved using ETag: {0}", initialAcctETagVal);
134+
Console.WriteLine("Expected outcome: Entity was not modified so nothing was returned.");
135+
return true;
136+
}
137+
}
138+
return false; // Let anything else stop the application.
139+
});
140+
}
141+
142+
// Modify the account instance by updating the telephone1 attribute
143+
svc.Put(account1Uri, "telephone1", "555-0001");
144+
Console.WriteLine("\n\bAccount telephone number updated to '555-0001'.\n");
145+
146+
// Re-attempt to retrieve using conditional GET defined by a message header with
147+
// the current ETag value.
148+
try
149+
{
150+
retrievedaccount1 = svc.Get(
151+
path: account1Uri.ToString() + queryOptions,
152+
headers: new Dictionary<string, List<string>> {
153+
{ "If-None-Match", new List<string> {initialAcctETagVal}}}
154+
);
155+
156+
// Expected result
157+
Console.WriteLine("Modified account record retrieved using ETag: {0}", initialAcctETagVal);
158+
Console.WriteLine("Notice the update ETag value and telephone number");
159+
}
160+
catch (ServiceException e)
161+
{
162+
if (e.StatusCode == (int)HttpStatusCode.NotModified) // Not expected
163+
{
164+
Console.WriteLine("Unexpected outcome: Entity was modified so something should be returned.");
165+
}
166+
else { throw e; }
167+
}
168+
169+
// Save the updated ETag value
170+
var updatedAcctETagVal = retrievedaccount1["@odata.etag"].ToString();
171+
172+
// Display ty updated record
173+
Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented));
174+
#endregion Conditional GET
175+
176+
#region Optimistic concurrency on delete and update
177+
Console.WriteLine("\n** Optimistic concurrency demonstration **");
178+
179+
// Attempt to delete original account (if matches original ETag value).
180+
// If you replace "initialAcctETagVal" with "updatedAcctETagVal", the delete will
181+
// succeed. However, we want the delete to fail for now to demonstrate use of the ETag.
182+
Console.WriteLine("Attempting to delete the account using the original ETag value");
183+
184+
try
185+
{
186+
svc.Delete(
187+
uri: account1Uri,
188+
headers: new Dictionary<string, List<string>> {
189+
{ "If-Match", new List<string> {initialAcctETagVal}}}
190+
);
191+
192+
// Not expected; this code should not execute.
193+
Console.WriteLine("Account deleted");
194+
}
195+
catch (ServiceException e)
196+
{
197+
if (e.StatusCode == (int)HttpStatusCode.PreconditionFailed) // Expected result
198+
{
199+
Console.WriteLine("Expected Error: The version of the account record no" +
200+
" longer matches the original ETag.");
201+
Console.WriteLine("\tAccount not deleted using ETag '{0}', status code: '{1}'.",
202+
initialAcctETagVal, e.StatusCode);
203+
}
204+
else { throw e; }
205+
}
206+
207+
Console.WriteLine("Attempting to update the account using the original ETag value");
208+
JObject accountUpdate = new JObject() {
209+
{ "telephone1", "555-0002" },
210+
{ "revenue", 6000000 }
211+
};
212+
213+
try
214+
{
215+
svc.Patch(
216+
uri: account1Uri,
217+
body: accountUpdate,
218+
headers: new Dictionary<string, List<string>> {
219+
{ "If-Match", new List<string> {initialAcctETagVal}}}
220+
);
221+
222+
// Not expected; this code should not execute.
223+
Console.WriteLine("Account updated using original ETag {0}", initialAcctETagVal);
224+
}
225+
catch (ServiceException e)
226+
{
227+
if (e.StatusCode == (int)HttpStatusCode.PreconditionFailed) // Expected error
228+
{
229+
Console.WriteLine("Expected Error: The version of the existing record doesn't "
230+
+ "match the ETag property provided.");
231+
Console.WriteLine("\tAccount not updated using ETag '{0}', status code: '{1}'.",
232+
initialAcctETagVal, (int)e.StatusCode);
233+
}
234+
else { throw e; }
235+
}
236+
237+
// Reattempt update if matches current ETag value.
238+
accountUpdate["telephone1"] = "555-0003";
239+
Console.WriteLine("Attempting to update the account using the current ETag value");
240+
try
241+
{
242+
svc.Patch(
243+
uri: account1Uri,
244+
body: accountUpdate,
245+
headers: new Dictionary<string, List<string>> {
246+
{ "If-Match", new List<string> { updatedAcctETagVal }} }
247+
);
248+
249+
// Expected program flow; this code should execute.
250+
Console.WriteLine("\nAccount successfully updated using ETag: {0}.",
251+
updatedAcctETagVal);
252+
}
253+
catch (ServiceException e)
254+
{
255+
if (e.StatusCode == (int)HttpStatusCode.PreconditionFailed) // Not expected
256+
{
257+
Console.WriteLine("Unexpected status code: '{0}'", (int)e.StatusCode);
258+
}
259+
else { throw e; }
260+
}
261+
262+
// Retrieve and output current account state.
263+
retrievedaccount1 = svc.Get(account1Uri.ToString() + queryOptions);
264+
265+
Console.WriteLine("\nBelow is the final state of the account");
266+
Console.WriteLine(retrievedaccount1.ToString(Formatting.Indented));
267+
#endregion Optimistic concurrency on delete and update
268+
269+
#region Delete created records
270+
271+
// Delete (or keep) all the created entity records.
272+
Console.Write("\nDo you want these entity records deleted? (y/n) [y]: ");
273+
String answer = Console.ReadLine().Trim();
274+
275+
if (!(answer.StartsWith("y") || answer.StartsWith("Y") || answer == string.Empty))
276+
entityUris.Clear();
277+
278+
foreach (Uri entityUrl in entityUris) svc.Delete(entityUrl);
279+
280+
#endregion Delete created records
281+
}
282+
}
283+
catch (ServiceException e)
284+
{
285+
Console.WriteLine("Message send response: status code {0}, {1}",
286+
e.StatusCode, e.ReasonPhrase);
287+
}
288+
}
289+
}
290+
}
291+
```
292+
293+
### See also
294+
295+
[Perform conditional operations using the Web API](../perform-conditional-operations-using-web-api.md)
296+
[Web API Conditional Operations Sample](../web-api-conditional-operations-sample.md)
297+
[Use the Common Data Service Web API](../overview.md)

powerapps-docs/developer/common-data-service/webapi/samples/cdswebapiservice-query-data.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ The following is required to build and run the sample:
4444

4545
1. Navigate to the cds/webapi/C#/[QueryData](https://github.com/microsoft/PowerApps-Samples/tree/master/cds/webapi/C%23/QueryData) folder and load the solution file into Visual Studio.
4646

47-
1. Edit the App.config file that is shared by several of the samples and set appropriate values for the Common Data Service environment you intend to use: connectionString `Url`, `UserPrincipalName`, and `Password`.
47+
1. Edit the App.config file that is shared by several of the samples and set appropriate values for the Common Data Service environment you intend to use: connectionString `Url`, `UserPrincipalName`, and `Password`. You only need to perform this step once for all samples that share this file.
4848

4949
1. Press F5 to build and run the program in debug mode.
5050

@@ -832,6 +832,6 @@ namespace PowerApps.Samples
832832

833833
### See also
834834

835-
[Query Data using the Web API](../query-data-web-ap.md)
835+
[Query Data using the Web API](../query-data-web-api.md)
836836
[Web API Query Data Sample](../web-api-query-data-sample.md)
837837
[Use the Common Data Service Web API](../overview.md)

powerapps-docs/developer/common-data-service/webapi/samples/cdswebapiservice.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,4 +332,6 @@ The following C# samples use this class:
332332

333333
- [Basic Operations Sample (C#)](cdswebapiservice-basic-operations.md)
334334
- [Parallel Operations Sample (C#)](cdswebapiservice-parallel-operations.md)
335-
- [Async Parallel Operations Sample (C#)](cdswebapiservice-async-parallel-operations.md)
335+
- [Async Parallel Operations Sample (C#)](cdswebapiservice-async-parallel-operations.md)
336+
- [Conditional Operations sample (C#)](cdswebapiservice-conditional-operations.md)
337+
- [Query Data sample (C#)](cdswebapiservice-query-data.md)

0 commit comments

Comments
 (0)