diff --git a/dataverse/orgsvc/.claude/settings.local.json b/dataverse/orgsvc/.claude/settings.local.json
new file mode 100644
index 00000000..4fbae84a
--- /dev/null
+++ b/dataverse/orgsvc/.claude/settings.local.json
@@ -0,0 +1,17 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(dir:*)",
+ "Bash(dir /s /b \"C:\\\\GitHub\\\\microsoft\\\\PowerApps-Samples\\\\dataverse\\\\orgsvc\\\\CSharp-NETCore\")",
+ "Bash(findstr:*)",
+ "Bash(ls:*)",
+ "Bash(git checkout:*)",
+ "Bash(git add:*)",
+ "Bash(git commit:*)",
+ "Bash(pwsh:*)",
+ "Bash(dotnet sln:*)",
+ "Bash(dotnet new:*)",
+ "Bash(dotnet build:*)"
+ ]
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/BookAppointment/BookAppointment.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/BookAppointment/BookAppointment.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/BookAppointment/BookAppointment.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/BookAppointment/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/BookAppointment/Program.cs
new file mode 100644
index 00000000..7136d45a
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/BookAppointment/Program.cs
@@ -0,0 +1,164 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to book or schedule an appointment using the BookRequest message.
+ ///
+ ///
+ /// This sample shows how to create and book an appointment using the BookRequest message,
+ /// which validates that the appointment can be scheduled and creates it in the system.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ // No setup required for this sample
+ }
+
+ ///
+ /// Demonstrates how to book an appointment using BookRequest
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Booking an appointment...");
+
+ // Get the current user information
+ var userRequest = new WhoAmIRequest();
+ var userResponse = (WhoAmIResponse)service.Execute(userRequest);
+
+ // Create the ActivityParty instance.
+ var party = new Entity("activityparty")
+ {
+ ["partyid"] = new EntityReference("systemuser", userResponse.UserId)
+ };
+
+ // Create the appointment instance.
+ var appointment = new Entity("appointment")
+ {
+ ["subject"] = "Test Appointment",
+ ["description"] = "Test Appointment created using the BookRequest Message.",
+ ["scheduledstart"] = DateTime.Now.AddHours(1),
+ ["scheduledend"] = DateTime.Now.AddHours(2),
+ ["location"] = "Office",
+ ["requiredattendees"] = new EntityCollection(new List { party }),
+ ["organizer"] = new EntityCollection(new List { party })
+ };
+
+ // Use the Book request message.
+ var book = new BookRequest
+ {
+ Target = appointment
+ };
+ var booked = (BookResponse)service.Execute(book);
+ Guid appointmentId = booked.ValidationResult.ActivityId;
+
+ // Add to entityStore for cleanup
+ entityStore.Add(new EntityReference("appointment", appointmentId));
+
+ // Verify that the appointment has been scheduled.
+ if (appointmentId != Guid.Empty)
+ {
+ Console.WriteLine("Successfully booked appointment: {0}", appointment["subject"]);
+ Console.WriteLine("Appointment ID: {0}", appointmentId);
+ }
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/BookAppointment/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/BookAppointment/README.md
new file mode 100644
index 00000000..96f442c4
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/BookAppointment/README.md
@@ -0,0 +1,69 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates how to book or schedule an appointment using the BookRequest message."
+---
+
+# Book an Appointment
+
+Demonstrates how to book or schedule an appointment using the BookRequest message.
+
+## What this sample does
+
+The `BookRequest` message is used to book or schedule an appointment. This validates that the appointment can be scheduled according to the calendars of the required attendees and creates the appointment in the system.
+
+## How this sample works
+
+### Setup
+
+This sample requires no setup. It uses the current user as both organizer and attendee.
+
+### Run
+
+The sample demonstrates the following steps:
+
+1. Gets the current user information using WhoAmIRequest
+2. Creates an ActivityParty for the current user
+3. Creates an appointment entity with:
+ - Subject and description
+ - Scheduled start and end times
+ - Location
+ - Required attendees and organizer
+4. Uses BookRequest to validate and create the appointment
+5. Verifies the appointment was successfully booked
+
+### Cleanup
+
+The sample deletes the created appointment record by default.
+
+## Demonstrates
+
+- Using `WhoAmIRequest` to get current user information
+- Creating an `ActivityParty` entity to represent appointment participants
+- Using `BookRequest` message to book an appointment
+- Working with appointment scheduling attributes
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Booking an appointment...
+Successfully booked appointment: Test Appointment
+Appointment ID: 12345678-1234-1234-1234-123456789012
+
+Deleting 1 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[BookRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.bookrequest)
+[Appointment entities](https://learn.microsoft.com/power-apps/developer/data-platform/appointment-entities)
+[Activity entities](https://learn.microsoft.com/power-apps/developer/data-platform/activity-entities)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/BulkEmail/BulkEmail.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/BulkEmail/BulkEmail.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/BulkEmail/BulkEmail.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/BulkEmail/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/BulkEmail/Program.cs
new file mode 100644
index 00000000..6c52c660
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/BulkEmail/Program.cs
@@ -0,0 +1,270 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to send bulk email and monitor the async operation results.
+ ///
+ ///
+ /// This sample shows how to send bulk email using the SendBulkMailRequest message
+ /// and monitor the results by retrieving records from the asyncoperation table.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private const int ARBITRARY_MAX_POLLING_TIME = 60;
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating contact records...");
+
+ var emailContact1 = new Entity("contact")
+ {
+ ["firstname"] = "Adam",
+ ["lastname"] = "Carter",
+ ["emailaddress1"] = "someone@example.com"
+ };
+
+ // Create the first contact
+ Guid contact1Id = service.Create(emailContact1);
+ entityStore.Add(new EntityReference("contact", contact1Id));
+ Console.WriteLine("Contact 1 created.");
+
+ var emailContact2 = new Entity("contact")
+ {
+ ["firstname"] = "Adina",
+ ["lastname"] = "Hagege",
+ ["emailaddress1"] = "someone@example.com"
+ };
+
+ // Create the second contact
+ Guid contact2Id = service.Create(emailContact2);
+ entityStore.Add(new EntityReference("contact", contact2Id));
+ Console.WriteLine("Contact 2 created.");
+ }
+
+ ///
+ /// Demonstrates how to send bulk email and monitor the async operation
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nCreating and sending bulk email...");
+
+ // Get a system user to use as the sender
+ var emailSenderRequest = new WhoAmIRequest();
+ var emailSenderResponse =
+ service.Execute(emailSenderRequest) as WhoAmIResponse;
+
+ // Set tracking ID for bulk mail request
+ Guid trackingId = Guid.NewGuid();
+
+ // Get the contact IDs from entityStore
+ var contactIds = entityStore.Select(e => e.Id).ToList();
+
+ var bulkMailRequest = new SendBulkMailRequest()
+ {
+ // Create a query expression for the bulk operation to retrieve
+ // the contacts in the email list
+ Query = new QueryExpression()
+ {
+ EntityName = "contact",
+ ColumnSet = new ColumnSet(new string[] { "contactid" }),
+ Criteria = new FilterExpression()
+ {
+ Conditions =
+ {
+ new ConditionExpression("contactid", ConditionOperator.In, contactIds.ToArray())
+ }
+ }
+ },
+ // Set the Sender
+ Sender = new EntityReference("systemuser", emailSenderResponse.UserId),
+ // Use a built-in Microsoft Dynamics CRM email template
+ // NOTE: The email template's "template type" must match the type of
+ // customers in the email list. Our list contains contacts, so our
+ // template must be for contacts.
+ TemplateId = new Guid("07B94C1D-C85F-492F-B120-F0A743C540E6"),
+ RequestId = trackingId
+ };
+
+ // Execute the async bulk email request
+ var resp = (SendBulkMailResponse)
+ service.Execute(bulkMailRequest);
+
+ Console.WriteLine("Sent bulk email.");
+
+ // Monitor the SendBulkEmail operation
+ Console.WriteLine("\nStarting monitoring process...");
+
+ // Retrieve the bulk email async operation using our tracking ID
+ var bulkQuery = new QueryByAttribute()
+ {
+ EntityName = "asyncoperation",
+ ColumnSet = new ColumnSet(new string[] { "requestid", "statecode" }),
+ Attributes = { "requestid" },
+ Values = { trackingId }
+ };
+
+ // Retrieve the bulk email async operation
+ EntityCollection aResponse = service.RetrieveMultiple(bulkQuery);
+
+ Console.WriteLine("Retrieved bulk email async operation.");
+
+ // Monitor the async operation via polling
+ int secondsTicker = ARBITRARY_MAX_POLLING_TIME;
+ Entity createdBulkMailOperation = null;
+
+ Console.WriteLine("Checking operation's state for {0} seconds.\n", ARBITRARY_MAX_POLLING_TIME);
+
+ while (secondsTicker > 0)
+ {
+ // Make sure the async operation was retrieved
+ if (aResponse.Entities.Count > 0)
+ {
+ // Grab the one bulk operation that has been created
+ createdBulkMailOperation = aResponse.Entities[0];
+
+ // Check the operation's state
+ var stateCode = createdBulkMailOperation.GetAttributeValue("statecode");
+ if (stateCode.Value != 3) // 3 = Completed
+ {
+ // The operation has not yet completed
+ // Wait a second for the status to change
+ System.Threading.Thread.Sleep(1000);
+ secondsTicker--;
+
+ // Retrieve a fresh version of the bulk email operation
+ aResponse = service.RetrieveMultiple(bulkQuery);
+ }
+ else
+ {
+ // Stop polling because the operation's state is now complete
+ secondsTicker = 0;
+ }
+ }
+ else
+ {
+ // Wait a second for the async operation to activate
+ System.Threading.Thread.Sleep(1000);
+ secondsTicker--;
+
+ // Retrieve the entity again
+ aResponse = service.RetrieveMultiple(bulkQuery);
+ }
+ }
+
+ // Check success
+ // Validate async operation succeeded
+ if (createdBulkMailOperation != null)
+ {
+ var stateCode = createdBulkMailOperation.GetAttributeValue("statecode");
+ if (stateCode.Value == 3) // 3 = Completed
+ {
+ Console.WriteLine("Operation completed successfully.");
+ Console.WriteLine("\nWhen the bulk email operation has completed, all sent emails will");
+ Console.WriteLine("have a status of 'Pending Send' and will be picked up by your email router.");
+ }
+ else
+ {
+ Console.WriteLine("Operation not completed yet.");
+ }
+ }
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/BulkEmail/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/BulkEmail/README.md
new file mode 100644
index 00000000..40bdb0ab
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/BulkEmail/README.md
@@ -0,0 +1,86 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates how to send bulk email and monitor the async operation results."
+---
+
+# Send Bulk Email and Monitor Results
+
+Demonstrates how to send bulk email using the SendBulkMailRequest message and monitor the results by retrieving records from the asyncoperation table.
+
+## What this sample does
+
+The `SendBulkMailRequest` message is used to send bulk emails to multiple recipients. This sample shows how to:
+- Create test contact records
+- Send bulk email to the contacts using a template
+- Monitor the async operation to track when the email send operation completes
+
+## How this sample works
+
+### Setup
+
+The sample creates two contact records with email addresses to serve as recipients for the bulk email.
+
+### Run
+
+The sample demonstrates the following steps:
+
+1. Gets the current user to use as the email sender (using WhoAmIRequest)
+2. Sets a tracking ID for the bulk email request
+3. Creates a QueryExpression to retrieve the contacts for the email list
+4. Executes SendBulkMailRequest with:
+ - The query for recipients
+ - The sender reference
+ - A built-in email template ID
+ - The tracking ID for monitoring
+5. Monitors the async operation by polling the asyncoperation table every second for up to 60 seconds
+6. Reports when the operation completes successfully
+
+### Cleanup
+
+The sample deletes the created contact records by default.
+
+## Demonstrates
+
+- Using `SendBulkMailRequest` to send emails to multiple recipients
+- Using a template for bulk email
+- Monitoring async operations by polling the asyncoperation table
+- Using QueryByAttribute to retrieve async operations by tracking ID
+- Checking async operation state to determine completion
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating contact records...
+Contact 1 created.
+Contact 2 created.
+
+Creating and sending bulk email...
+Sent bulk email.
+
+Starting monitoring process...
+Retrieved bulk email async operation.
+Checking operation's state for 60 seconds.
+
+Operation completed successfully.
+
+When the bulk email operation has completed, all sent emails will
+have a status of 'Pending Send' and will be picked up by your email router.
+
+Deleting 2 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[SendBulkMailRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.sendbulkmailrequest)
+[Email activity entities](https://learn.microsoft.com/power-apps/developer/data-platform/email-activity-entities)
+[Asynchronous operation states](https://learn.microsoft.com/power-apps/developer/data-platform/asynchronous-service)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDEmailAttachments/CRUDEmailAttachments.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDEmailAttachments/CRUDEmailAttachments.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDEmailAttachments/CRUDEmailAttachments.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDEmailAttachments/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDEmailAttachments/Program.cs
new file mode 100644
index 00000000..ae08950c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDEmailAttachments/Program.cs
@@ -0,0 +1,212 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+using System.Text;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to create, retrieve, update, and delete email attachments.
+ ///
+ ///
+ /// This sample shows how to perform CRUD operations on email attachments
+ /// (activitymimeattachment) associated with an email activity.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Create an email activity
+ var email = new Entity("email")
+ {
+ ["subject"] = "This is an example email",
+ ["activityid"] = Guid.NewGuid()
+ };
+
+ Guid emailId = service.Create(email);
+ entityStore.Add(new EntityReference("email", emailId));
+ Console.WriteLine("An e-mail activity is created.");
+ }
+
+ ///
+ /// Demonstrates CRUD operations on email attachments
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nPerforming CRUD operations on email attachments...");
+
+ // Get the email ID from entityStore
+ var emailRef = entityStore.FirstOrDefault(e => e.LogicalName == "email");
+ if (emailRef == null)
+ {
+ Console.WriteLine("No email found.");
+ return;
+ }
+
+ // Create three e-mail attachments
+ var attachmentIds = new Guid[3];
+ for (int i = 0; i < 3; i++)
+ {
+ var sampleAttachment = new Entity("activitymimeattachment")
+ {
+ ["objectid"] = emailRef,
+ ["objecttypecode"] = "email",
+ ["subject"] = String.Format("Sample Attachment {0}", i),
+ ["body"] = Convert.ToBase64String(
+ new ASCIIEncoding().GetBytes("Example Attachment")),
+ ["filename"] = String.Format("ExampleAttachment{0}.txt", i)
+ };
+
+ attachmentIds[i] = service.Create(sampleAttachment);
+ entityStore.Add(new EntityReference("activitymimeattachment", attachmentIds[i]));
+ }
+
+ Console.WriteLine("Created three e-mail attachments for the e-mail activity.");
+
+ // Retrieve an attachment including its id, subject, filename and body
+ var singleAttachment = service.Retrieve(
+ "activitymimeattachment",
+ attachmentIds[0],
+ new ColumnSet("activitymimeattachmentid", "subject", "filename", "body"));
+
+ Console.WriteLine("Retrieved an email attachment, {0}.",
+ singleAttachment.GetAttributeValue("filename"));
+
+ // Update the attachment
+ singleAttachment["filename"] = "ExampleAttachmentUpdated.txt";
+ service.Update(singleAttachment);
+
+ Console.WriteLine("Updated the retrieved e-mail attachment to {0}.",
+ singleAttachment["filename"]);
+
+ // Retrieve all attachments associated with the email activity
+ var attachmentQuery = new QueryExpression
+ {
+ EntityName = "activitymimeattachment",
+ ColumnSet = new ColumnSet("activitymimeattachmentid"),
+ Criteria = new FilterExpression
+ {
+ Conditions =
+ {
+ new ConditionExpression
+ {
+ AttributeName = "objectid",
+ Operator = ConditionOperator.Equal,
+ Values = { emailRef.Id }
+ },
+ new ConditionExpression
+ {
+ AttributeName = "objecttypecode",
+ Operator = ConditionOperator.Equal,
+ Values = { "email" }
+ }
+ }
+ }
+ };
+
+ EntityCollection results = service.RetrieveMultiple(attachmentQuery);
+
+ Console.WriteLine("Retrieved all the e-mail attachments. Total count: {0}", results.Entities.Count);
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+
+ // Delete in reverse order to delete attachments before email
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDEmailAttachments/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDEmailAttachments/README.md
new file mode 100644
index 00000000..e88c0338
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDEmailAttachments/README.md
@@ -0,0 +1,50 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "This sample shows how to create, retrieve, update, and delete email attachments in Microsoft Dataverse."
+---
+
+# Create, retrieve, update, and delete an email attachment
+
+This sample shows how to create, retrieve, update, and delete email attachments using the following methods:
+
+- [IOrganizationService.Create](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.create)
+- [IOrganizationService.Retrieve](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.retrieve)
+- [IOrganizationService.Update](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.update)
+- [IOrganizationService.Delete](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.delete)
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `IOrganizationService` methods are intended to be used in a scenario where they provide programmatic access to the metadata and data for an organization.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates an email activity that is required for the sample.
+
+### Demonstrate
+
+1. Creates three email attachments (activitymimeattachment entities) with the following properties:
+ - objectid: Reference to the email activity
+ - objecttypecode: Set to "email"
+ - subject: Attachment subject/description
+ - body: Base64-encoded attachment content
+ - filename: Name of the attachment file
+2. Retrieves a single attachment including its id, subject, filename, and body using the Retrieve method.
+3. Updates the filename of the retrieved attachment from "ExampleAttachment0.txt" to "ExampleAttachmentUpdated.txt".
+4. Retrieves all attachments associated with the email activity using QueryExpression with filters on objectid and objecttypecode.
+
+### Clean up
+
+Display an option to delete the records created in the [Setup](#setup). The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDRecurringAppointment/CRUDRecurringAppointment.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDRecurringAppointment/CRUDRecurringAppointment.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDRecurringAppointment/CRUDRecurringAppointment.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDRecurringAppointment/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDRecurringAppointment/Program.cs
new file mode 100644
index 00000000..f577e4ac
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDRecurringAppointment/Program.cs
@@ -0,0 +1,201 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to create, retrieve, update, and delete a recurring appointment series.
+ ///
+ ///
+ /// This sample shows how to perform CRUD operations on a recurring appointment series
+ /// using the IOrganizationService.Create, Retrieve, Update, and Delete methods.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ // No setup required for this sample
+ }
+
+ ///
+ /// Demonstrates CRUD operations on a recurring appointment series
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Creating recurring appointment...");
+
+ // Define recurrence pattern values
+ int RecurrencePatternTypeWeekly = 1;
+ int DayOfWeekThursday = 0x10;
+ int RecurrenceRulePatternEndTypeOccurrences = 2;
+
+ // Create a recurring appointment
+ var newRecurringAppointment = new Entity("recurringappointmentmaster")
+ {
+ ["subject"] = "Sample Recurring Appointment",
+ ["starttime"] = DateTime.Now.AddHours(1),
+ ["endtime"] = DateTime.Now.AddHours(2),
+ ["recurrencepatterntype"] = new OptionSetValue(RecurrencePatternTypeWeekly),
+ ["interval"] = 1,
+ ["daysofweekmask"] = DayOfWeekThursday,
+ ["patternstartdate"] = DateTime.Today,
+ ["occurrences"] = 10,
+ ["patternendtype"] = new OptionSetValue(RecurrenceRulePatternEndTypeOccurrences)
+ };
+
+ Guid recurringAppointmentMasterId = service.Create(newRecurringAppointment);
+ entityStore.Add(new EntityReference("recurringappointmentmaster", recurringAppointmentMasterId));
+
+ Console.WriteLine("Created {0}.", newRecurringAppointment["subject"]);
+
+ // Retrieve the newly created recurring appointment
+ Console.WriteLine("\nRetrieving recurring appointment...");
+ var recurringAppointmentQuery = new QueryExpression
+ {
+ EntityName = "recurringappointmentmaster",
+ ColumnSet = new ColumnSet("subject", "interval", "occurrences"),
+ Criteria = new FilterExpression
+ {
+ Conditions =
+ {
+ new ConditionExpression
+ {
+ AttributeName = "subject",
+ Operator = ConditionOperator.Equal,
+ Values = { "Sample Recurring Appointment" }
+ },
+ new ConditionExpression
+ {
+ AttributeName = "interval",
+ Operator = ConditionOperator.Equal,
+ Values = { 1 }
+ }
+ }
+ },
+ PageInfo = new PagingInfo
+ {
+ Count = 1,
+ PageNumber = 1
+ }
+ };
+
+ EntityCollection results = service.RetrieveMultiple(recurringAppointmentQuery);
+ Entity retrievedRecurringAppointment = results.Entities.FirstOrDefault();
+
+ if (retrievedRecurringAppointment != null)
+ {
+ Console.WriteLine("Retrieved the recurring appointment.");
+
+ // Update the recurring appointment
+ Console.WriteLine("\nUpdating recurring appointment...");
+ // Update the subject, number of occurrences to 5, and appointment interval to 2
+ retrievedRecurringAppointment["subject"] = "Updated Recurring Appointment";
+ retrievedRecurringAppointment["occurrences"] = 5;
+ retrievedRecurringAppointment["interval"] = 2;
+ service.Update(retrievedRecurringAppointment);
+
+ Console.WriteLine("Updated the subject, occurrences, and interval of the recurring appointment.");
+ }
+ else
+ {
+ Console.WriteLine("Could not retrieve the recurring appointment.");
+ }
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDRecurringAppointment/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDRecurringAppointment/README.md
new file mode 100644
index 00000000..77496604
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CRUDRecurringAppointment/README.md
@@ -0,0 +1,38 @@
+# Create, retrieve, update, and delete a recurring appointment
+
+This sample shows how to create, retrieve, update, and delete a recurring appointment series. This sample uses the following common methods:
+
+- [IOrganizationService.Create](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.create)
+- [IOrganizationService.Retrieve](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.retrieve)
+- [IOrganizationService.Update](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.update)
+- [IOrganizationService.Delete](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.delete)
+
+## How to run this sample
+
+1. Navigate to the Activities folder: `cd CSharp-NETCore/Activities/`
+2. Edit `appsettings.json` with your Dataverse environment connection string
+3. Build: `dotnet build CRUDRecurringAppointment`
+4. Run: `dotnet run --project CRUDRecurringAppointment`
+
+## What this sample does
+
+The `IOrganizationService` methods are used to perform CRUD operations on a recurring appointment series.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+No setup required for this sample.
+
+### Demonstrate
+
+1. Define values for recurrence pattern types, days of the week, and recurrence rule pattern end type.
+2. The `Create` method creates a recurring appointment with weekly recurrence.
+3. The `QueryExpression` method retrieves the newly created recurring appointment.
+4. The `Update` method updates the subject, number of occurrences to 5, and appointment interval to 2 for the retrieved recurring appointment.
+
+### Clean up
+
+Display an option to delete the records created during execution. The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertFaxToTask/ConvertFaxToTask.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertFaxToTask/ConvertFaxToTask.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertFaxToTask/ConvertFaxToTask.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertFaxToTask/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertFaxToTask/Program.cs
new file mode 100644
index 00000000..b1f1e85c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertFaxToTask/Program.cs
@@ -0,0 +1,175 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to convert a Fax activity to a Task activity.
+ ///
+ ///
+ /// This sample shows how to retrieve a Fax activity and create a follow-up Task
+ /// based on the fax information.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Get the current user
+ var userRequest = new WhoAmIRequest();
+ var userResponse = (WhoAmIResponse)service.Execute(userRequest);
+ Guid userId = userResponse.UserId;
+
+ // Create the activity party for sending and receiving the fax
+ var party = new Entity("activityparty")
+ {
+ ["partyid"] = new EntityReference("systemuser", userId)
+ };
+
+ // Create the fax
+ var fax = new Entity("fax")
+ {
+ ["subject"] = "Sample Fax",
+ ["from"] = new EntityCollection(new List { party }),
+ ["to"] = new EntityCollection(new List { party })
+ };
+
+ Guid faxId = service.Create(fax);
+ entityStore.Add(new EntityReference("fax", faxId));
+ Console.WriteLine("Created a fax: 'Sample Fax'.");
+ }
+
+ ///
+ /// Demonstrates converting a fax to a task
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nConverting fax to task...");
+
+ // Get the fax ID from the entityStore
+ var faxRef = entityStore.FirstOrDefault(e => e.LogicalName == "fax");
+ if (faxRef == null)
+ {
+ Console.WriteLine("No fax found to convert.");
+ return;
+ }
+
+ // Retrieve the fax
+ var retrievedFax = service.Retrieve("fax", faxRef.Id, new ColumnSet(true));
+
+ // Create a task based on the fax
+ var task = new Entity("task")
+ {
+ ["subject"] = "Follow Up: " + retrievedFax.GetAttributeValue("subject"),
+ ["scheduledend"] = retrievedFax.GetAttributeValue("createdon").AddDays(7)
+ };
+
+ Guid taskId = service.Create(task);
+ entityStore.Add(new EntityReference("task", taskId));
+
+ // Verify that the task has been created
+ if (taskId != Guid.Empty)
+ {
+ Console.WriteLine("Created a task for the fax: '{0}'.", task["subject"]);
+ }
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertFaxToTask/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertFaxToTask/README.md
new file mode 100644
index 00000000..d0f2da33
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertFaxToTask/README.md
@@ -0,0 +1,29 @@
+# Convert Fax to Task
+
+This sample shows how to convert a **Fax** to a **Task**.
+
+## How to run this sample
+
+See [How to run this sample](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+This sample demonstrates how to retrieve a fax activity and create a follow-up task based on the fax information.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates a fax activity with the current user as both sender and recipient.
+
+### Demonstrate
+
+1. Retrieves the fax activity created in [Setup](#setup).
+2. Creates a task with a subject "Follow Up: [Fax Subject]" and scheduled end date 7 days after the fax creation date.
+3. Verifies that the task has been created successfully.
+
+### Clean up
+
+Displays an option to delete all the data created in the sample. The deletion is optional in case you want to examine the data created by the sample. You can manually delete the data to achieve the same results.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertToRecurring/ConvertToRecurring.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertToRecurring/ConvertToRecurring.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertToRecurring/ConvertToRecurring.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertToRecurring/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertToRecurring/Program.cs
new file mode 100644
index 00000000..2ca23f74
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertToRecurring/Program.cs
@@ -0,0 +1,216 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to convert an appointment to a recurring appointment series.
+ ///
+ ///
+ /// This sample shows how to use the AddRecurrenceRequest message to convert an existing
+ /// appointment into a recurring appointment master with a defined recurrence pattern.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Create a sample appointment
+ var appointment = new Entity("appointment")
+ {
+ ["subject"] = "Sample Appointment",
+ ["location"] = "Office",
+ ["scheduledstart"] = DateTime.Now.AddHours(1),
+ ["scheduledend"] = DateTime.Now.AddHours(2)
+ };
+
+ Guid appointmentId = service.Create(appointment);
+ entityStore.Add(new EntityReference("appointment", appointmentId));
+ Console.WriteLine("Created Sample Appointment");
+ }
+
+ ///
+ /// Demonstrates converting an appointment to a recurring appointment
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nConverting appointment to recurring appointment...");
+
+ // Get the appointment ID from entityStore
+ var appointmentRef = entityStore.FirstOrDefault(e => e.LogicalName == "appointment");
+ if (appointmentRef == null)
+ {
+ Console.WriteLine("No appointment found to convert.");
+ return;
+ }
+
+ // Define recurrence pattern types
+ var RecurrencePatternTypes = new
+ {
+ Daily = 0,
+ Weekly = 1,
+ Monthly = 2,
+ Yearly = 3
+ };
+
+ // Define days of the week
+ var DayOfWeek = new
+ {
+ Sunday = 0x01,
+ Monday = 0x02,
+ Tuesday = 0x04,
+ Wednesday = 0x08,
+ Thursday = 0x10,
+ Friday = 0x20,
+ Saturday = 0x40
+ };
+
+ // Define recurrence rule pattern end type
+ var RecurrenceRulePatternEndType = new
+ {
+ NoEndDate = 1,
+ Occurrences = 2,
+ PatternEndDate = 3
+ };
+
+ // Create a recurring appointment master object with recurrence information
+ var newRecurringAppointmentInfo = new Entity("recurringappointmentmaster")
+ {
+ ["starttime"] = DateTime.Now.AddHours(2),
+ ["endtime"] = DateTime.Now.AddHours(3),
+ ["recurrencepatterntype"] = new OptionSetValue(RecurrencePatternTypes.Weekly),
+ ["interval"] = 1,
+ ["daysofweekmask"] = DayOfWeek.Thursday,
+ ["patternstartdate"] = DateTime.Today,
+ ["patternendtype"] = new OptionSetValue(RecurrenceRulePatternEndType.Occurrences),
+ ["occurrences"] = 5
+ };
+
+ // Use the AddRecurrence message to convert the existing appointment
+ // to a recurring appointment master
+ var recurringInfoRequest = new AddRecurrenceRequest
+ {
+ Target = newRecurringAppointmentInfo,
+ AppointmentId = appointmentRef.Id
+ };
+
+ var recurringInfoResponse = (AddRecurrenceResponse)service.Execute(recurringInfoRequest);
+ Guid recurringAppointmentMasterId = recurringInfoResponse.id;
+
+ // Add the recurring appointment master to entityStore for cleanup
+ // Remove the original appointment reference as it's been deleted
+ entityStore.Remove(appointmentRef);
+ entityStore.Add(new EntityReference("recurringappointmentmaster", recurringAppointmentMasterId));
+
+ // Verify that the newly created recurring appointment master has the same subject
+ var retrievedMasterAppointment = service.Retrieve(
+ "recurringappointmentmaster",
+ recurringAppointmentMasterId,
+ new ColumnSet("subject"));
+
+ if (retrievedMasterAppointment.GetAttributeValue("subject") == "Sample Appointment")
+ {
+ Console.WriteLine("Sample Appointment is converted to a recurring appointment.");
+ Console.WriteLine("Recurring Appointment Master ID: {0}", recurringAppointmentMasterId);
+ }
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertToRecurring/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertToRecurring/README.md
new file mode 100644
index 00000000..8f130647
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/ConvertToRecurring/README.md
@@ -0,0 +1,33 @@
+# Convert an appointment to a recurring appointment
+
+This sample shows how to convert an appointment to a recurring appointment series using the [AddRecurrenceRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.addrecurrencerequest) message.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `AddRecurrenceRequest` message is intended to be used in a scenario where it contains the data that is needed to add recurrence information to an existing appointment.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+Creates a sample appointment that is later converted into a recurring appointment.
+
+### Demonstrate
+
+1. Specifies the recurrence information that needs to be added to the appointment created in the [Setup](#setup).
+2. Defines the possible recurrence pattern values (Daily, Weekly, Monthly, Yearly).
+3. Defines the possible values for days of the week.
+4. Defines the possible values for the recurrence rule pattern end type (NoEndDate, Occurrences, PatternEndDate).
+5. Creates a `RecurringAppointmentMaster` entity with the recurrence information set to weekly on Thursdays for 5 occurrences.
+6. Uses the `AddRecurrenceRequest` message to convert the created appointment into a recurring appointment.
+7. Verifies that the recurring appointment master was created successfully with the same subject as the original appointment.
+
+### Clean up
+
+Display an option to delete the sample data created in [Setup](#setup). If you opt **Y**, it deletes all the records created. The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the sample data to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CreateEmailUsingTemplate/CreateEmailUsingTemplate.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/CreateEmailUsingTemplate/CreateEmailUsingTemplate.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CreateEmailUsingTemplate/CreateEmailUsingTemplate.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CreateEmailUsingTemplate/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/CreateEmailUsingTemplate/Program.cs
new file mode 100644
index 00000000..bc84e98c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CreateEmailUsingTemplate/Program.cs
@@ -0,0 +1,205 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using System.Xml.Serialization;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to create an email using a template.
+ ///
+ ///
+ /// This sample shows how to instantiate an email record from a template using the
+ /// InstantiateTemplateRequest message and serialize the result to XML.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Create an account
+ var account = new Entity("account")
+ {
+ ["name"] = "Fourth Coffee"
+ };
+ Guid accountId = service.Create(account);
+ entityStore.Add(new EntityReference("account", accountId));
+ Console.WriteLine("Created a sample account: Fourth Coffee.");
+
+ // Define the body and subject of the email template in XML format
+ string bodyXml =
+ ""
+ + ""
+ + ""
+ + "";
+
+ string subjectXml =
+ ""
+ + ""
+ + ""
+ + "";
+
+ string presentationXml =
+ "";
+
+ string subjectPresentationXml =
+ "";
+
+ // Create an e-mail template
+ var template = new Entity("template")
+ {
+ ["title"] = "Sample E-mail Template for Account",
+ ["body"] = bodyXml,
+ ["subject"] = subjectXml,
+ ["presentationxml"] = presentationXml,
+ ["subjectpresentationxml"] = subjectPresentationXml,
+ ["templatetypecode"] = "account",
+ ["languagecode"] = 1033, // For US English
+ ["ispersonal"] = false
+ };
+
+ Guid templateId = service.Create(template);
+ entityStore.Add(new EntityReference("template", templateId));
+ Console.WriteLine("Created Sample E-mail Template for Account.");
+ }
+
+ ///
+ /// Demonstrates creating an email using a template
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nCreating email using template...");
+
+ // Get the template and account IDs from entityStore
+ var templateRef = entityStore.FirstOrDefault(e => e.LogicalName == "template");
+ var accountRef = entityStore.FirstOrDefault(e => e.LogicalName == "account");
+
+ if (templateRef == null || accountRef == null)
+ {
+ Console.WriteLine("Required records not found.");
+ return;
+ }
+
+ // Use the InstantiateTemplate message to create an e-mail message using a template
+ var instTemplateReq = new InstantiateTemplateRequest
+ {
+ TemplateId = templateRef.Id,
+ ObjectId = accountRef.Id,
+ ObjectType = "account"
+ };
+
+ var instTemplateResp = (InstantiateTemplateResponse)service.Execute(instTemplateReq);
+
+ // Serialize the email message to XML and save to a file
+ var serializer = new XmlSerializer(typeof(InstantiateTemplateResponse));
+ string filename = "email-message.xml";
+ using (var writer = new StreamWriter(filename))
+ {
+ serializer.Serialize(writer, instTemplateResp);
+ }
+
+ Console.WriteLine("Created e-mail using the template.");
+ Console.WriteLine("Email message serialized to: {0}", filename);
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CreateEmailUsingTemplate/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/CreateEmailUsingTemplate/README.md
new file mode 100644
index 00000000..8ca2be6c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CreateEmailUsingTemplate/README.md
@@ -0,0 +1,32 @@
+# Create an email using a template
+
+This topic shows how to instantiate an email record by using the [InstantiateTemplateRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.instantiatetemplaterequest) message.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `InstantiateTemplateRequest` message is intended to be used in a scenario where it contains the parameters that are needed to create an email message from a template (email template).
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates an account record to be used with the email template.
+2. Creates an email template for accounts with predefined body and subject XML content.
+ - The template includes XSL transformations for the body and subject
+ - The template is configured for accounts and US English (language code 1033)
+
+### Demonstrate
+
+1. Uses the `InstantiateTemplateRequest` message to create an email message using the template.
+ - Specifies the template ID, target account ID, and object type (account)
+2. Serializes the email message response to XML and saves it to a file named "email-message.xml".
+
+### Clean up
+
+Display an option to delete the records created in the [Setup](#setup). The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CustomActivity/CustomActivity.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/CustomActivity/CustomActivity.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CustomActivity/CustomActivity.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CustomActivity/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/CustomActivity/Program.cs
new file mode 100644
index 00000000..d453669a
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CustomActivity/Program.cs
@@ -0,0 +1,203 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Metadata;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to create a custom activity entity.
+ ///
+ ///
+ /// This sample shows how to create a custom activity entity using CreateEntityRequest
+ /// and add custom attributes using CreateAttributeRequest.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static string customEntityName = string.Empty;
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ // No setup required for this sample
+ }
+
+ ///
+ /// Demonstrates creating a custom activity entity
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Creating custom activity entity...");
+
+ // The custom prefix would typically be passed in as an argument or
+ // determined by the publisher of the custom solution.
+ string prefix = "new_";
+ customEntityName = prefix + "sampleentity";
+
+ // Create the custom activity entity
+ var request = new CreateEntityRequest
+ {
+ HasNotes = true,
+ HasActivities = false,
+ PrimaryAttribute = new StringAttributeMetadata
+ {
+ SchemaName = "Subject",
+ RequiredLevel = new AttributeRequiredLevelManagedProperty(AttributeRequiredLevel.None),
+ MaxLength = 100,
+ DisplayName = new Label("Subject", 1033)
+ },
+ Entity = new EntityMetadata
+ {
+ IsActivity = true,
+ SchemaName = customEntityName,
+ DisplayName = new Label("Sample Entity", 1033),
+ DisplayCollectionName = new Label("Sample Entity", 1033),
+ OwnershipType = OwnershipTypes.UserOwned,
+ IsAvailableOffline = true,
+ }
+ };
+
+ service.Execute(request);
+ Console.WriteLine("Created custom activity entity: {0}", customEntityName);
+
+ // Add custom attributes to the custom activity entity
+ Console.WriteLine("\nAdding custom attributes...");
+
+ // Add FontFamily attribute
+ var fontFamilyAttributeRequest = new CreateAttributeRequest
+ {
+ EntityName = customEntityName,
+ Attribute = new StringAttributeMetadata
+ {
+ SchemaName = prefix + "fontfamily",
+ DisplayName = new Label("Font Family", 1033),
+ MaxLength = 100
+ }
+ };
+ service.Execute(fontFamilyAttributeRequest);
+ Console.WriteLine("Added FontFamily attribute.");
+
+ // Add FontColor attribute
+ var fontColorAttributeRequest = new CreateAttributeRequest
+ {
+ EntityName = customEntityName,
+ Attribute = new StringAttributeMetadata
+ {
+ SchemaName = prefix + "fontcolor",
+ DisplayName = new Label("Font Color", 1033),
+ MaxLength = 50
+ }
+ };
+ service.Execute(fontColorAttributeRequest);
+ Console.WriteLine("Added FontColor attribute.");
+
+ // Add FontSize attribute
+ var fontSizeAttributeRequest = new CreateAttributeRequest
+ {
+ EntityName = customEntityName,
+ Attribute = new IntegerAttributeMetadata
+ {
+ SchemaName = prefix + "fontSize",
+ DisplayName = new Label("Font Size", 1033)
+ }
+ };
+ service.Execute(fontSizeAttributeRequest);
+ Console.WriteLine("Added FontSize attribute.");
+
+ Console.WriteLine("\nThe custom activity has been created.");
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && !string.IsNullOrEmpty(customEntityName))
+ {
+ Console.WriteLine("\nDeleting custom entity...");
+ var deleteRequest = new DeleteEntityRequest
+ {
+ LogicalName = customEntityName
+ };
+ service.Execute(deleteRequest);
+ Console.WriteLine("Entity has been deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/CustomActivity/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/CustomActivity/README.md
new file mode 100644
index 00000000..4bc7a75c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/CustomActivity/README.md
@@ -0,0 +1,31 @@
+# Create a custom activity
+
+The following code example demonstrates how to create a custom activity using [CreateEntityRequest](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.messages.createentityrequest) and [CreateAttributeRequest](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.messages.createattributerequest).
+
+## How to run this sample
+
+1. Navigate to the Activities folder: `cd CSharp-NETCore/Activities/`
+2. Edit `appsettings.json` with your Dataverse environment connection string
+3. Build: `dotnet build CustomActivity`
+4. Run: `dotnet run --project CustomActivity`
+
+## What this sample does
+
+The `CreateEntityRequest` message and `CreateAttributeRequest` message is intended to be used in a scenario to create custom activity.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+No setup required for this sample.
+
+### Demonstrate
+
+1. Creates the custom activity table using the `CreateEntityRequest` message.
+2. Creates custom attributes (FontFamily, FontColor, FontSize) to the custom activity table using `CreateAttributeRequest` message.
+
+### Clean up
+
+Display an option to delete the custom entity created during execution. The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/EmailTemplate/EmailTemplate.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/EmailTemplate/EmailTemplate.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/EmailTemplate/EmailTemplate.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/EmailTemplate/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/EmailTemplate/Program.cs
new file mode 100644
index 00000000..812dcaab
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/EmailTemplate/Program.cs
@@ -0,0 +1,198 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using System.Xml.Serialization;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to create an email using an email template.
+ ///
+ ///
+ /// This sample shows how to instantiate an email record using the
+ /// InstantiateTemplateRequest message with a template.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Create an account
+ var account = new Entity("account")
+ {
+ ["name"] = "Fourth Coffee"
+ };
+ Guid accountId = service.Create(account);
+ entityStore.Add(new EntityReference("account", accountId));
+ Console.WriteLine("Created a sample account: {0}.", account["name"]);
+
+ // Define the body and subject of the email template in XML format
+ string bodyXml =
+ ""
+ + ""
+ + ""
+ + "";
+
+ string subjectXml =
+ ""
+ + ""
+ + ""
+ + "";
+
+ string presentationXml =
+ "";
+
+ string subjectPresentationXml =
+ "";
+
+ // Create an email template
+ var template = new Entity("template")
+ {
+ ["title"] = "Sample E-mail Template for Account",
+ ["body"] = bodyXml,
+ ["subject"] = subjectXml,
+ ["presentationxml"] = presentationXml,
+ ["subjectpresentationxml"] = subjectPresentationXml,
+ ["templatetypecode"] = "account",
+ ["languagecode"] = 1033, // For US English
+ ["ispersonal"] = false
+ };
+
+ Guid templateId = service.Create(template);
+ entityStore.Add(new EntityReference("template", templateId));
+ Console.WriteLine("Created {0}.", template["title"]);
+ }
+
+ ///
+ /// Demonstrates creating an email from a template
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nCreating email from template...");
+
+ // Get the IDs from entityStore (account and template)
+ Guid accountId = entityStore.First(e => e.LogicalName == "account").Id;
+ Guid templateId = entityStore.First(e => e.LogicalName == "template").Id;
+
+ // Use the InstantiateTemplate message to create an email message using a template
+ var instTemplateReq = new InstantiateTemplateRequest
+ {
+ TemplateId = templateId,
+ ObjectId = accountId,
+ ObjectType = "account"
+ };
+ var instTemplateResp = (InstantiateTemplateResponse)service.Execute(instTemplateReq);
+
+ // Serialize the email message to XML, and save to a file
+ var serializer = new XmlSerializer(typeof(InstantiateTemplateResponse));
+ string filename = "email-message.xml";
+ using (StreamWriter writer = new StreamWriter(filename))
+ {
+ serializer.Serialize(writer, instTemplateResp);
+ }
+ Console.WriteLine("Created e-mail using the template.");
+ Console.WriteLine("Email message serialized to: {0}", filename);
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ // Delete in reverse order (template first, then account)
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/EmailTemplate/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/EmailTemplate/README.md
new file mode 100644
index 00000000..4511214d
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/EmailTemplate/README.md
@@ -0,0 +1,33 @@
+# Create an email using a template
+
+This sample shows how to instantiate an email record by using [InstantiateTemplateRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.instantiatetemplaterequest) message.
+
+## How to run this sample
+
+1. Navigate to the Activities folder: `cd CSharp-NETCore/Activities/`
+2. Edit `appsettings.json` with your Dataverse environment connection string
+3. Build: `dotnet build EmailTemplate`
+4. Run: `dotnet run --project EmailTemplate`
+
+## What this sample does
+
+The `InstantiateTemplateRequest` message is intended to be used in a scenario where it instantiates an email record.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates an account record.
+2. Defines the body and subject of the email template in XML format.
+3. Creates an email template.
+
+### Demonstrate
+
+1. The `InstantiateTemplateRequest` message is used to create an email message using a template.
+2. Serialize the email message to XML and save to a file.
+
+### Clean up
+
+Display an option to delete the records created in Setup. The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/EndRecurringAppointment/EndRecurringAppointment.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/EndRecurringAppointment/EndRecurringAppointment.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/EndRecurringAppointment/EndRecurringAppointment.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/EndRecurringAppointment/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/EndRecurringAppointment/Program.cs
new file mode 100644
index 00000000..bf19373f
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/EndRecurringAppointment/Program.cs
@@ -0,0 +1,175 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to end a recurring appointment series.
+ ///
+ ///
+ /// This sample shows how to use the DeleteOpenInstancesRequest message to end a
+ /// recurring appointment series to the last occurring past instance date.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Define recurrence pattern values
+ int RecurrencePatternTypeWeekly = 1;
+ int DayOfWeekThursday = 0x10;
+ int RecurrenceRulePatternEndTypeOccurrences = 2;
+
+ // Create a new recurring appointment
+ var newRecurringAppointment = new Entity("recurringappointmentmaster")
+ {
+ ["subject"] = "Sample Recurring Appointment",
+ ["starttime"] = DateTime.Now.AddHours(1),
+ ["endtime"] = DateTime.Now.AddHours(2),
+ ["recurrencepatterntype"] = new OptionSetValue(RecurrencePatternTypeWeekly),
+ ["interval"] = 1,
+ ["daysofweekmask"] = DayOfWeekThursday,
+ ["patternstartdate"] = DateTime.Today,
+ ["occurrences"] = 5,
+ ["patternendtype"] = new OptionSetValue(RecurrenceRulePatternEndTypeOccurrences)
+ };
+
+ Guid recurringAppointmentMasterId = service.Create(newRecurringAppointment);
+ entityStore.Add(new EntityReference("recurringappointmentmaster", recurringAppointmentMasterId));
+ Console.WriteLine("Created {0} with {1} occurrences.", newRecurringAppointment["subject"], newRecurringAppointment["occurrences"]);
+ }
+
+ ///
+ /// Demonstrates ending a recurring appointment series
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nEnding recurring appointment series...");
+
+ // Get the recurring appointment ID from entityStore
+ Guid recurringAppointmentMasterId = entityStore.First(e => e.LogicalName == "recurringappointmentmaster").Id;
+
+ // Retrieve the recurring appointment series
+ var retrievedRecurringAppointmentSeries = service.Retrieve(
+ "recurringappointmentmaster",
+ recurringAppointmentMasterId,
+ new ColumnSet(true));
+
+ // Use the DeleteOpenInstances message to end the series to the
+ // last occurring past instance date w.r.t. the series end date
+ // (i.e., 20 days from today). Effectively, that means that the
+ // series will end after the third instance (day 14) as this
+ // instance is the last occurring past instance w.r.t the specified
+ // series end date (20 days from today).
+ // Also specify that the state of past instances (w.r.t. the series
+ // end date) be set to 'completed'.
+ var endAppointmentSeries = new DeleteOpenInstancesRequest
+ {
+ Target = retrievedRecurringAppointmentSeries,
+ SeriesEndDate = DateTime.Today.AddDays(20),
+ StateOfPastInstances = 2 // AppointmentState.Completed
+ };
+ service.Execute(endAppointmentSeries);
+
+ Console.WriteLine("The recurring appointment series has been ended after the third occurrence.");
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/EndRecurringAppointment/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/EndRecurringAppointment/README.md
new file mode 100644
index 00000000..c19f0f55
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/EndRecurringAppointment/README.md
@@ -0,0 +1,32 @@
+# End a recurring appointment series
+
+The following sample shows how to end a recurring appointment series by using the [DeleteOpenInstancesRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.deleteopeninstancesrequest) message.
+
+## How to run this sample
+
+1. Navigate to the Activities folder: `cd CSharp-NETCore/Activities/`
+2. Edit `appsettings.json` with your Dataverse environment connection string
+3. Build: `dotnet build EndRecurringAppointment`
+4. Run: `dotnet run --project EndRecurringAppointment`
+
+## What this sample does
+
+The `DeleteOpenInstanceRequest` message is intended to be used in a scenario where it contains the data that is needed to delete instances of a recurring appointment master that have an "Open" state.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Defines the recurrence pattern values, possible values for days, and recurrence rule pattern end type values.
+2. Creates a new recurring appointment that is required for the sample.
+
+### Demonstrate
+
+1. Retrieves the recurring appointment series that was created in Setup.
+2. The `DeleteOpenInstanceRequest` message ends the recurring appointment series to the last occurring past instance date w.r.t. the series end date.
+
+### Clean up
+
+Display an option to delete the sample data created in Setup. The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/PromoteEmail/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/PromoteEmail/Program.cs
new file mode 100644
index 00000000..1f083df2
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/PromoteEmail/Program.cs
@@ -0,0 +1,186 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to promote an email message to create an email activity.
+ ///
+ ///
+ /// This sample shows how to create an email activity instance from an email message
+ /// using the DeliverPromoteEmailRequest message.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ // No setup required for this sample
+ }
+
+ ///
+ /// Demonstrates promoting an email message
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Creating contact and promoting email...");
+
+ // Create a contact to send an email to (To: field)
+ var emailContact = new Entity("contact")
+ {
+ ["firstname"] = "Lisa",
+ ["lastname"] = "Andrews",
+ ["emailaddress1"] = "lisa@contoso.com"
+ };
+ Guid contactId = service.Create(emailContact);
+ entityStore.Add(new EntityReference("contact", contactId));
+ Console.WriteLine("Created a sample contact.");
+
+ // Get a system user to send the email (From: field)
+ var systemUserRequest = new WhoAmIRequest();
+ var systemUserResponse = (WhoAmIResponse)service.Execute(systemUserRequest);
+
+ var cols = new ColumnSet("internalemailaddress");
+ var emailSender = service.Retrieve("systemuser", systemUserResponse.UserId, cols);
+ string fromEmail = emailSender.GetAttributeValue("internalemailaddress");
+
+ // Create the request
+ var deliverEmailRequest = new DeliverPromoteEmailRequest
+ {
+ Subject = "SDK Sample Email",
+ To = emailContact.GetAttributeValue("emailaddress1"),
+ From = fromEmail,
+ Bcc = string.Empty,
+ Cc = string.Empty,
+ Importance = "high",
+ Body = "This message will create an email activity.",
+ SubmittedBy = string.Empty,
+ ReceivedOn = DateTime.Now
+ };
+
+ // We won't attach a file to the email, but the Attachments property is required
+ deliverEmailRequest.Attachments = new EntityCollection(new Entity[0]);
+ deliverEmailRequest.Attachments.EntityName = "activitymimeattachment";
+
+ // Execute the request
+ var deliverEmailResponse = (DeliverPromoteEmailResponse)service.Execute(deliverEmailRequest);
+
+ // Verify the success
+ // Define possible values for email status
+ int EmailStatusSent = 3;
+
+ // Query for the delivered email, and verify the status code is "Sent"
+ var deliveredMailColumns = new ColumnSet("statuscode");
+ var deliveredEmail = service.Retrieve("email", deliverEmailResponse.EmailId, deliveredMailColumns);
+
+ entityStore.Add(new EntityReference("email", deliverEmailResponse.EmailId));
+
+ var statusCode = deliveredEmail.GetAttributeValue("statuscode");
+ if (statusCode != null && statusCode.Value == EmailStatusSent)
+ {
+ Console.WriteLine("Successfully created and delivered the e-mail message.");
+ }
+ else
+ {
+ Console.WriteLine("Email was created but status is not 'Sent'.");
+ }
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ // Delete in reverse order (email first, then contact)
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/PromoteEmail/PromoteEmail.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/PromoteEmail/PromoteEmail.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/PromoteEmail/PromoteEmail.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/PromoteEmail/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/PromoteEmail/README.md
new file mode 100644
index 00000000..bf860175
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/PromoteEmail/README.md
@@ -0,0 +1,33 @@
+# Promote an email message
+
+This sample shows how to create an email activity instance from the specified email message in Dynamics 365 by using the [DeliverPromoteEmailRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.deliverpromoteemailrequest) message.
+
+## How to run this sample
+
+1. Navigate to the Activities folder: `cd CSharp-NETCore/Activities/`
+2. Edit `appsettings.json` with your Dataverse environment connection string
+3. Build: `dotnet build PromoteEmail`
+4. Run: `dotnet run --project PromoteEmail`
+
+## What this sample does
+
+The `DeliverPromoteEmailRequest` message is intended to be used in a scenario where it contains data that is needed to create an email activity record from the specified email message.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+No setup required for this sample.
+
+### Demonstrate
+
+1. Creates a contact to send an email to (To: column).
+2. The `WhoAmIRequest` retrieves the system user to send the email (From: column).
+3. The `DeliverPromoteEmailRequest` message creates the request and also executes it.
+4. Verify the success by querying the delivered email and verifying the status code is `sent`.
+
+### Clean up
+
+Display an option to delete the records created during execution. The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/README.md
new file mode 100644
index 00000000..351382c0
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/README.md
@@ -0,0 +1,62 @@
+# Activities
+
+Samples demonstrating how to work with activity entities in Dataverse including emails, appointments, tasks, faxes, and custom activities.
+
+These samples show how to create, retrieve, update, and delete various types of activities, work with recurring appointments, manage email templates and attachments, and collaborate using activity feeds.
+
+More information: [Activity entities](https://learn.microsoft.com/power-apps/developer/data-platform/activity-entities)
+
+## Samples
+
+|Sample folder|Description|Build target|
+|---|---|---|
+|BookAppointment|Book or schedule an appointment|.NET 6|
+|BulkEmail|Send bulk email messages|.NET 6|
+|CollaborateWithActivityFeeds|Use activity feeds for collaboration|.NET 6|
+|ConvertFaxToTask|Convert a fax activity to a task|.NET 6|
+|ConvertToRecurring|Convert an appointment to a recurring appointment|.NET 6|
+|CreateEmailUsingTemplate|Create email from a template|.NET 6|
+|CRUDEmailAttachments|Create, retrieve, update, delete email attachments|.NET 6|
+|CRUDRecurringAppointment|CRUD operations on recurring appointments|.NET 6|
+|CustomActivity|Work with custom activity entities|.NET 6|
+|EmailTemplate|Create and use email templates|.NET 6|
+|EndRecurringAppointment|End a recurring appointment series|.NET 6|
+|PromoteEmail|Promote an email to create related records|.NET 6|
+|RecurringAppointment|Create and manage recurring appointments|.NET 6|
+|RetrieveEmailAttach|Retrieve email attachments|.NET 6|
+|SendEmail|Send an email message|.NET 6|
+|SendEmailUsingTemp|Send email using a template|.NET 6|
+|ValidateAppointment|Validate appointment scheduling|.NET 6|
+
+## Prerequisites
+
+- Visual Studio 2022 or later
+- .NET 6.0 SDK or later
+- Access to a Dataverse environment
+
+## How to run samples
+
+1. Clone the PowerApps-Samples repository
+2. Navigate to `dataverse/orgsvc/CSharp-NETCore/Activities/`
+3. Open the desired sample folder
+4. Edit the `appsettings.json` file (located in the Activities folder) with your environment connection details:
+ ```json
+ {
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+ }
+ ```
+5. Build and run the sample:
+ ```bash
+ cd SampleFolder
+ dotnet run
+ ```
+
+## See also
+
+[Activity entities](https://learn.microsoft.com/power-apps/developer/data-platform/activity-entities)
+[Email activity entities](https://learn.microsoft.com/power-apps/developer/data-platform/email-activity-entities)
+[Task, fax, phone call, and letter activity entities](https://learn.microsoft.com/power-apps/developer/data-platform/task-fax-phone-call-letter-activity-entities)
+[Appointment entities](https://learn.microsoft.com/power-apps/developer/data-platform/appointment-entities)
+[Recurring appointment entities](https://learn.microsoft.com/power-apps/developer/data-platform/recurring-appointment-entities)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/RecurringAppointment/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/RecurringAppointment/Program.cs
new file mode 100644
index 00000000..ef397732
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/RecurringAppointment/Program.cs
@@ -0,0 +1,222 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to reschedule and cancel appointment instances in a recurring appointment series.
+ ///
+ ///
+ /// This sample shows how to reschedule and cancel appointment instances in a recurring appointment series
+ /// using the RescheduleRequest message and SetStateRequest message.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Create a new recurring appointment
+ var newRecurringAppointment = new Entity("recurringappointmentmaster")
+ {
+ ["subject"] = "Sample Recurring Appointment",
+ ["starttime"] = DateTime.Now.AddHours(1),
+ ["endtime"] = DateTime.Now.AddHours(2),
+ ["recurrencepatterntype"] = new OptionSetValue(1), // Weekly
+ ["interval"] = 1,
+ ["daysofweekmask"] = 16, // Thursday
+ ["patternstartdate"] = DateTime.Today,
+ ["occurrences"] = 5,
+ ["patternendtype"] = new OptionSetValue(2) // Occurrences
+ };
+
+ Guid recurringAppointmentMasterId = service.Create(newRecurringAppointment);
+ entityStore.Add(new EntityReference("recurringappointmentmaster", recurringAppointmentMasterId));
+ Console.WriteLine("Created recurring appointment: {0}", newRecurringAppointment["subject"]);
+ }
+
+ ///
+ /// Demonstrates how to reschedule and cancel instances in a recurring appointment series
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nDemonstrating recurring appointment operations...");
+
+ // Get the recurring appointment master ID
+ Guid recurringAppointmentMasterId = entityStore[0].Id;
+
+ // Retrieve the individual appointment instance that falls on or after
+ // 10 days from today. Basically this will be the second instance in the
+ // recurring appointment series.
+ var instanceQuery = new QueryExpression
+ {
+ EntityName = "appointment",
+ ColumnSet = new ColumnSet("activityid", "scheduledstart", "scheduledend"),
+ Criteria = new FilterExpression
+ {
+ Conditions =
+ {
+ new ConditionExpression
+ {
+ AttributeName = "seriesid",
+ Operator = ConditionOperator.Equal,
+ Values = { recurringAppointmentMasterId }
+ },
+ new ConditionExpression
+ {
+ AttributeName = "scheduledstart",
+ Operator = ConditionOperator.OnOrAfter,
+ Values = { DateTime.Today.AddDays(10) }
+ }
+ }
+ }
+ };
+
+ EntityCollection individualAppointments = service.RetrieveMultiple(instanceQuery);
+
+ if (individualAppointments.Entities.Count > 0)
+ {
+ #region Reschedule an instance of recurring appointment
+
+ // Update the scheduled start and end dates of the appointment
+ // to reschedule it.
+ var firstAppointment = individualAppointments.Entities[0];
+ var updateAppointment = new Entity("appointment")
+ {
+ Id = firstAppointment.Id,
+ ["scheduledstart"] = ((DateTime)firstAppointment["scheduledstart"]).AddHours(1),
+ ["scheduledend"] = ((DateTime)firstAppointment["scheduledend"]).AddHours(2)
+ };
+
+ var reschedule = new RescheduleRequest
+ {
+ Target = updateAppointment
+ };
+
+ service.Execute(reschedule);
+ Console.WriteLine("Rescheduled the second instance of the recurring appointment.");
+
+ #endregion Reschedule an instance of recurring appointment
+
+ #region Cancel an instance of recurring appointment
+
+ // Cancel the last instance of the appointment. The status of this appointment
+ // instance is set to 'Canceled'. You can view this appointment instance under
+ // the 'All Activities' view.
+ var lastAppointment = individualAppointments.Entities.Last();
+ var appointmentRequest = new SetStateRequest
+ {
+ State = new OptionSetValue(3), // Canceled
+ Status = new OptionSetValue(4),
+ EntityMoniker = new EntityReference("appointment", lastAppointment.Id)
+ };
+
+ service.Execute(appointmentRequest);
+ Console.WriteLine("Canceled the last instance of the recurring appointment.");
+
+ #endregion Cancel an instance of recurring appointment
+ }
+ else
+ {
+ Console.WriteLine("No appointment instances found to reschedule or cancel.");
+ }
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/RecurringAppointment/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/RecurringAppointment/README.md
new file mode 100644
index 00000000..f8bf739e
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/RecurringAppointment/README.md
@@ -0,0 +1,31 @@
+# Reschedule and cancel a recurring appointment
+
+This sample demonstrates how to reschedule and cancel appointment instances in a recurring appointment series using the [RescheduleRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.reschedulerequest) message.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `RescheduleRequest` message is intended to be used in a scenario where it contains the data that is needed to reschedule an appointment, recurring appointment, or service appointment (service activity).
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Defines the possible recurrence pattern values and possible values for days of the week.
+2. Defines the possible values for recurrence rule pattern end type.
+3. Creates a new recurring appointment with a weekly pattern.
+
+### Demonstrate
+
+1. The `QueryExpression` message queries the individual appointment instance that falls on or after 10 days from today. Basically this will be the second instance in the recurring appointment series.
+2. The `RescheduleRequest` message updates the scheduled start and end dates of the appointment.
+3. The `SetStateRequest` message cancels the last instance of the appointment. The status of this appointment instance is set to `canceled`. You can view this appointment instance under the `All Activities` view.
+
+### Clean up
+
+Display an option to delete the sample data created in [Setup](#setup). The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/RecurringAppointment/RecurringAppointment.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/RecurringAppointment/RecurringAppointment.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/RecurringAppointment/RecurringAppointment.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/RetrieveEmailAttach/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/RetrieveEmailAttach/Program.cs
new file mode 100644
index 00000000..4c30eade
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/RetrieveEmailAttach/Program.cs
@@ -0,0 +1,202 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to retrieve email attachments associated with an email template.
+ ///
+ ///
+ /// This sample shows how to retrieve email attachments associated with an email template
+ /// by using the RetrieveMultiple method.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Define the email template to create
+ var emailTemplate = new Entity("template")
+ {
+ ["title"] = "An example email template",
+ ["subject"] = "This is an example email.",
+ ["ispersonal"] = false,
+ ["templatetypecode"] = "lead",
+ ["languagecode"] = 1033 // US English
+ };
+
+ Guid emailTemplateId = service.Create(emailTemplate);
+ entityStore.Add(new EntityReference("template", emailTemplateId));
+
+ // Create attachments for the template
+ for (int i = 0; i < 3; i++)
+ {
+ var attachment = new Entity("activitymimeattachment")
+ {
+ ["subject"] = $"Attachment {i}",
+ ["filename"] = $"ExampleAttachment{i}.txt",
+ ["body"] = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("Some Text")),
+ ["objectid"] = new EntityReference("template", emailTemplateId),
+ ["objecttypecode"] = "template"
+ };
+
+ Guid attachmentId = service.Create(attachment);
+ entityStore.Add(new EntityReference("activitymimeattachment", attachmentId));
+ }
+
+ Console.WriteLine("An email template and {0} attachments were created.", entityStore.Count - 1);
+ }
+
+ ///
+ /// Demonstrates how to retrieve email attachments for a template
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nRetrieving email attachments...");
+
+ // Get the template ID from entityStore
+ Guid emailTemplateId = entityStore[0].Id;
+
+ // Create a query to retrieve attachments
+ var query = new QueryExpression
+ {
+ EntityName = "activitymimeattachment",
+ ColumnSet = new ColumnSet("filename"),
+
+ // Define the conditions for each attachment
+ Criteria = new FilterExpression
+ {
+ FilterOperator = LogicalOperator.And,
+ Conditions =
+ {
+ // The ObjectTypeCode must be specified, or else the query
+ // defaults to "email" instead of "template"
+ new ConditionExpression
+ {
+ AttributeName = "objecttypecode",
+ Operator = ConditionOperator.Equal,
+ Values = { "template" }
+ },
+ // Specify which template we need
+ new ConditionExpression
+ {
+ AttributeName = "objectid",
+ Operator = ConditionOperator.Equal,
+ Values = { emailTemplateId }
+ }
+ }
+ }
+ };
+
+ // Write out the filename of each attachment retrieved
+ EntityCollection attachments = service.RetrieveMultiple(query);
+ foreach (var attachment in attachments.Entities)
+ {
+ Console.WriteLine("Retrieved attachment {0}", attachment["filename"]);
+ }
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+
+ // Delete attachments first
+ for (int i = entityStore.Count - 1; i >= 1; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+
+ // Delete template last
+ service.Delete(entityStore[0].LogicalName, entityStore[0].Id);
+
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/RetrieveEmailAttach/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/RetrieveEmailAttach/README.md
new file mode 100644
index 00000000..fffa02ac
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/RetrieveEmailAttach/README.md
@@ -0,0 +1,28 @@
+# Retrieve email attachments for an email template
+
+This sample shows how to retrieve email attachments associated with an email template by using the [IOrganizationService.RetrieveMultiple](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.retrievemultiple) method.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `IOrganizationService.RetrieveMultiple` method is intended to be used in a scenario where it retrieves a collection of records.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates a sample email template using the `template` entity.
+2. Creates three sample attachments associated with the template using the `activitymimeattachment` entity.
+
+### Demonstrate
+
+The `QueryExpression` retrieves all the attachments associated with the email template by filtering on the `objecttypecode` and `objectid` attributes.
+
+### Clean up
+
+Display an option to delete the sample data that is created in [Setup](#setup). The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/RetrieveEmailAttach/RetrieveEmailAttach.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/RetrieveEmailAttach/RetrieveEmailAttach.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/RetrieveEmailAttach/RetrieveEmailAttach.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmail/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmail/Program.cs
new file mode 100644
index 00000000..9726feba
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmail/Program.cs
@@ -0,0 +1,179 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to send an email using the SendEmailRequest message.
+ ///
+ ///
+ /// This sample shows how to send an email message using the SendEmailRequest message.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Create a contact to send an email to (To: field)
+ var emailContact = new Entity("contact")
+ {
+ ["firstname"] = "Nancy",
+ ["lastname"] = "Anderson",
+ ["emailaddress1"] = "nancy@contoso.com"
+ };
+ Guid contactId = service.Create(emailContact);
+ entityStore.Add(new EntityReference("contact", contactId));
+ Console.WriteLine("Created a contact: {0} {1}", emailContact["firstname"], emailContact["lastname"]);
+
+ // Get a system user to send the email (From: field)
+ var systemUserRequest = new WhoAmIRequest();
+ var systemUserResponse = (WhoAmIResponse)service.Execute(systemUserRequest);
+ Guid userId = systemUserResponse.UserId;
+
+ // Create the 'From:' activity party for the email
+ var fromParty = new Entity("activityparty")
+ {
+ ["partyid"] = new EntityReference("systemuser", userId)
+ };
+
+ // Create the 'To:' activity party for the email
+ var toParty = new Entity("activityparty")
+ {
+ ["partyid"] = new EntityReference("contact", contactId)
+ };
+ Console.WriteLine("Created activity parties.");
+
+ // Create an e-mail message
+ var email = new Entity("email")
+ {
+ ["to"] = new EntityCollection(new List { toParty }),
+ ["from"] = new EntityCollection(new List { fromParty }),
+ ["subject"] = "SDK Sample e-mail",
+ ["description"] = "SDK Sample for SendEmail Message.",
+ ["directioncode"] = true
+ };
+ Guid emailId = service.Create(email);
+ entityStore.Add(new EntityReference("email", emailId));
+ Console.WriteLine("Created {0}.", email["subject"]);
+ }
+
+ ///
+ /// Demonstrates how to send an email
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nSending email...");
+
+ // Get the email ID from entityStore
+ Guid emailId = entityStore[1].Id;
+
+ // Use the SendEmail message to send an e-mail message
+ var sendEmailreq = new SendEmailRequest
+ {
+ EmailId = emailId,
+ TrackingToken = "",
+ IssueSend = true
+ };
+
+ service.Execute(sendEmailreq);
+ Console.WriteLine("Sent the e-mail message.");
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmail/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmail/README.md
new file mode 100644
index 00000000..b4938d47
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmail/README.md
@@ -0,0 +1,30 @@
+# Sample: Send an email
+
+This sample shows how to send an email using [SendEmailRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.sendemailrequest) message.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `SendEmailRequest` message is intended to be used in a scenario where it contains data that is needed to send an email message.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates a contact to send an email to (To: column).
+2. The `WhoAmIRequest` method gets the current user information to send the email (From: column).
+3. The `activityparty` entity creates To and From activity party for the email.
+4. Creates an email message using the `email` entity.
+
+### Demonstrate
+
+The `SendEmailRequest` method sends an email message created in the [Setup](#setup).
+
+### Clean up
+
+Display an option to delete the records created in the [Setup](#setup). The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmail/SendEmail.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmail/SendEmail.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmail/SendEmail.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmailUsingTemp/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmailUsingTemp/Program.cs
new file mode 100644
index 00000000..8b83c909
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmailUsingTemp/Program.cs
@@ -0,0 +1,213 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to send an email message by using a template.
+ ///
+ ///
+ /// This sample shows how to send an email message by using a template using the
+ /// SendEmailFromTemplateRequest message.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Create a contact record to send an email to (To: field)
+ var emailContact = new Entity("contact")
+ {
+ ["firstname"] = "David",
+ ["lastname"] = "Pelton",
+ ["emailaddress1"] = "david@contoso.com",
+ ["donotemail"] = false
+ };
+ Guid contactId = service.Create(emailContact);
+ entityStore.Add(new EntityReference("contact", contactId));
+ Console.WriteLine("Created a sample contact.");
+ }
+
+ ///
+ /// Demonstrates how to send an email using a template
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nSending email using template...");
+
+ // Get the contact ID from entityStore
+ Guid contactId = entityStore[0].Id;
+
+ // Get a system user to send the email (From: field)
+ var systemUserRequest = new WhoAmIRequest();
+ var systemUserResponse = (WhoAmIResponse)service.Execute(systemUserRequest);
+ Guid userId = systemUserResponse.UserId;
+
+ // Create the 'From:' activity party for the email
+ var fromParty = new Entity("activityparty")
+ {
+ ["partyid"] = new EntityReference("systemuser", userId)
+ };
+
+ // Create the 'To:' activity party for the email
+ var toParty = new Entity("activityparty")
+ {
+ ["partyid"] = new EntityReference("contact", contactId)
+ };
+
+ Console.WriteLine("Created activity parties.");
+
+ // Create an e-mail message
+ var email = new Entity("email")
+ {
+ ["to"] = new EntityCollection(new List { toParty }),
+ ["from"] = new EntityCollection(new List { fromParty }),
+ ["subject"] = "SDK Sample e-mail",
+ ["description"] = "SDK Sample for SendEmailFromTemplate Message.",
+ ["directioncode"] = true
+ };
+
+ // Create a query expression to get one of Email Template of type "contact"
+ var queryBuildInTemplates = new QueryExpression
+ {
+ EntityName = "template",
+ ColumnSet = new ColumnSet("templateid", "templatetypecode"),
+ Criteria = new FilterExpression()
+ };
+ queryBuildInTemplates.Criteria.AddCondition("templatetypecode",
+ ConditionOperator.Equal, "contact");
+ EntityCollection templateEntityCollection = service.RetrieveMultiple(queryBuildInTemplates);
+
+ Guid templateId = Guid.Empty;
+ if (templateEntityCollection.Entities.Count > 0)
+ {
+ templateId = templateEntityCollection.Entities[0].Id;
+ }
+ else
+ {
+ throw new ArgumentException("Standard Email Templates are missing");
+ }
+
+ // Create the request
+ var emailUsingTemplateReq = new SendEmailFromTemplateRequest
+ {
+ Target = email,
+
+ // Use a built-in Email Template of type "contact"
+ TemplateId = templateId,
+
+ // The regarding Id is required, and must be of the same type as the Email Template
+ RegardingId = contactId,
+ RegardingType = "contact"
+ };
+
+ var emailUsingTemplateResp = (SendEmailFromTemplateResponse)service.Execute(emailUsingTemplateReq);
+
+ // Verify that the e-mail has been created
+ Guid emailId = emailUsingTemplateResp.Id;
+ if (!emailId.Equals(Guid.Empty))
+ {
+ entityStore.Add(new EntityReference("email", emailId));
+ Console.WriteLine("Successfully sent an e-mail message using the template.");
+ }
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmailUsingTemp/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmailUsingTemp/README.md
new file mode 100644
index 00000000..7a40a370
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmailUsingTemp/README.md
@@ -0,0 +1,31 @@
+# Send an email using a template
+
+This sample shows how to send an email message by using a template using the [SendEmailFromTemplateRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.sendemailfromtemplaterequest) message.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `SendEmailFromTemplateRequest` message is intended to be used in a scenario where it contains data that is needed to send an email message using a template.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates a contact record to send an email to (To: column).
+
+### Demonstrate
+
+1. The `WhoAmIRequest` gets the current user information to send the email (From: column).
+2. The `activityparty` entity creates the From: and To: activity party for the email.
+3. Creates an email message using the `email` entity.
+4. The `QueryExpression` queries to get one of the email template of type `Contact`.
+5. The `SendEmailFromTemplateRequest` sends an email message by using a template.
+
+### Clean up
+
+Display an option to delete the sample data that is created in [Setup](#setup). The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmailUsingTemp/SendEmailUsingTemp.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmailUsingTemp/SendEmailUsingTemp.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/SendEmailUsingTemp/SendEmailUsingTemp.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/ValidateAppointment/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Activities/ValidateAppointment/Program.cs
new file mode 100644
index 00000000..9174dd10
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/ValidateAppointment/Program.cs
@@ -0,0 +1,182 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to validate an appointment.
+ ///
+ ///
+ /// This sample shows how to validate an appointment using the ValidateRequest message.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Create a contact
+ var newContact = new Entity("contact")
+ {
+ ["firstname"] = "Lisa",
+ ["lastname"] = "Andrews",
+ ["emailaddress1"] = "lisa@contoso.com"
+ };
+ Guid contactId = service.Create(newContact);
+ entityStore.Add(new EntityReference("contact", contactId));
+ Console.WriteLine("Created contact: {0} {1}", newContact["firstname"], newContact["lastname"]);
+
+ // Create an activity party
+ var requiredAttendee = new Entity("activityparty")
+ {
+ ["partyid"] = new EntityReference("contact", contactId)
+ };
+
+ // Create an appointment
+ var newAppointment = new Entity("appointment")
+ {
+ ["subject"] = "Test Appointment",
+ ["description"] = "Test Appointment",
+ ["scheduledstart"] = DateTime.Now.AddHours(1),
+ ["scheduledend"] = DateTime.Now.AddHours(2),
+ ["location"] = "Office",
+ ["requiredattendees"] = new EntityCollection(new List { requiredAttendee })
+ };
+ Guid appointmentId = service.Create(newAppointment);
+ entityStore.Add(new EntityReference("appointment", appointmentId));
+ Console.WriteLine("Created {0}.", newAppointment["subject"]);
+ }
+
+ ///
+ /// Demonstrates how to validate an appointment
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nValidating appointment...");
+
+ // Get the appointment ID from entityStore
+ Guid appointmentId = entityStore[1].Id;
+
+ // Retrieve the appointment to be validated
+ var cols = new ColumnSet("scheduledstart", "scheduledend", "statecode", "statuscode");
+ Entity retrievedAppointment = service.Retrieve("appointment", appointmentId, cols);
+
+ // Use the Validate message
+ var validatedReq = new ValidateRequest
+ {
+ Activities = new EntityCollection
+ {
+ EntityName = "appointment",
+ Entities = { retrievedAppointment }
+ }
+ };
+ validatedReq.Activities.MoreRecords = false;
+ validatedReq.Activities.PagingCookie = "";
+
+ var validateResp = (ValidateResponse)service.Execute(validatedReq);
+
+ // Verify success
+ if ((validateResp.Result != null) && (validateResp.Result.Length > 0))
+ {
+ Console.WriteLine("Validated the appointment.");
+ }
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/ValidateAppointment/README.md b/dataverse/orgsvc/CSharp-NETCore/Activities/ValidateAppointment/README.md
new file mode 100644
index 00000000..0aef51d2
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/ValidateAppointment/README.md
@@ -0,0 +1,30 @@
+# Validate an appointment
+
+This sample shows how to validate an appointment using the [ValidateRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.validaterequest) message.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `ValidateRequest` message is intended to be used in the scenario where it contains data that is needed to verify that an appointment or service appointment (service activity) has valid available resources for the activity, duration, and site, as appropriate.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates sample contact record.
+2. Creates activity party for the contact.
+3. Creates sample appointment.
+
+### Demonstrate
+
+1. Retrieves the appointment to be validated.
+2. The `ValidateRequest` message validates the appointment created in the [Setup](#setup).
+
+### Clean up
+
+Display an option to delete the records in [Setup](#setup). The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/ValidateAppointment/ValidateAppointment.csproj b/dataverse/orgsvc/CSharp-NETCore/Activities/ValidateAppointment/ValidateAppointment.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/ValidateAppointment/ValidateAppointment.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Activities/appsettings.json b/dataverse/orgsvc/CSharp-NETCore/Activities/appsettings.json
new file mode 100644
index 00000000..037aca85
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Activities/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Audit/Audit.slnx b/dataverse/orgsvc/CSharp-NETCore/Audit/Audit.slnx
new file mode 100644
index 00000000..45e3883b
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Audit/Audit.slnx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Audit/AuditEntityData/AuditEntityData.csproj b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditEntityData/AuditEntityData.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditEntityData/AuditEntityData.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Audit/AuditEntityData/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditEntityData/Program.cs
new file mode 100644
index 00000000..86ea6817
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditEntityData/Program.cs
@@ -0,0 +1,365 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Metadata;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to enable auditing on an entity and retrieve the change history
+ ///
+ ///
+ /// This sample shows how to:
+ /// - Enable auditing on the organization and account entity
+ /// - Create and update an account record to generate audit history
+ /// - Retrieve record change history
+ /// - Retrieve attribute change history
+ /// - Retrieve audit details
+ ///
+ /// Prerequisites:
+ /// - System Administrator or System Customizer role
+ /// - Auditing feature available in your Dataverse environment
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static bool organizationAuditingFlag;
+ private static bool accountAuditingFlag;
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data by enabling auditing and creating an account
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Enabling auditing on the organization and account entities...");
+
+ // Enable auditing on the organization
+ Guid orgId = ((WhoAmIResponse)service.Execute(new WhoAmIRequest())).OrganizationId;
+
+ // Retrieve the organization's record to get current audit setting
+ var retrievedOrg = service.Retrieve("organization", orgId, new ColumnSet("isauditenabled"));
+
+ // Cache the value to restore it later
+ organizationAuditingFlag = retrievedOrg.GetAttributeValue("isauditenabled");
+
+ // Enable auditing on the organization
+ var orgToUpdate = new Entity("organization")
+ {
+ Id = orgId,
+ ["isauditenabled"] = true
+ };
+ service.Update(orgToUpdate);
+
+ // Enable auditing on account entities
+ accountAuditingFlag = EnableEntityAuditing(service, "account", true);
+
+ // Create an account
+ Console.WriteLine("Creating an account...");
+ var newAccount = new Entity("account")
+ {
+ ["name"] = "Example Account"
+ };
+ Guid accountId = service.Create(newAccount);
+ entityStore.Add(new EntityReference("account", accountId));
+
+ Console.WriteLine("Updating the account...");
+ var accountToUpdate = new Entity("account")
+ {
+ Id = accountId,
+ ["accountnumber"] = "1-A",
+ ["accountcategorycode"] = new OptionSetValue(1), // Preferred Customer
+ ["telephone1"] = "555-555-5555"
+ };
+ service.Update(accountToUpdate);
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ ///
+ /// Demonstrates auditing operations including retrieving change history
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Guid accountId = entityStore[0].Id;
+
+ // Retrieve the record change history
+ Console.WriteLine("Retrieving the account change history...");
+ var changeRequest = new RetrieveRecordChangeHistoryRequest
+ {
+ Target = new EntityReference("account", accountId)
+ };
+
+ var changeResponse = (RetrieveRecordChangeHistoryResponse)service.Execute(changeRequest);
+ var details = changeResponse.AuditDetailCollection;
+
+ foreach (AuditDetail detail in details.AuditDetails)
+ {
+ DisplayAuditDetails(service, detail);
+ }
+
+ // Update the Telephone1 attribute to generate more audit history
+ Console.WriteLine("Updating the Telephone1 field in the Account entity...");
+ var accountToUpdate = new Entity("account")
+ {
+ Id = accountId,
+ ["telephone1"] = "123-555-5555"
+ };
+ service.Update(accountToUpdate);
+
+ // Retrieve the attribute change history
+ Console.WriteLine("Retrieving the attribute change history for Telephone1...");
+ var attributeChangeHistoryRequest = new RetrieveAttributeChangeHistoryRequest
+ {
+ Target = new EntityReference("account", accountId),
+ AttributeLogicalName = "telephone1"
+ };
+
+ var attributeChangeHistoryResponse =
+ (RetrieveAttributeChangeHistoryResponse)service.Execute(attributeChangeHistoryRequest);
+
+ // Display the attribute change history
+ var attributeDetails = attributeChangeHistoryResponse.AuditDetailCollection;
+
+ foreach (var detail in attributeDetails.AuditDetails)
+ {
+ DisplayAuditDetails(service, detail);
+ }
+
+ // Retrieve audit details for a specific audit record
+ if (attributeDetails.AuditDetails.Count > 0)
+ {
+ Guid auditSampleId = attributeDetails.AuditDetails[0].AuditRecord.Id;
+
+ Console.WriteLine("Retrieving audit details for an audit record...");
+ var auditDetailsRequest = new RetrieveAuditDetailsRequest
+ {
+ AuditId = auditSampleId
+ };
+
+ var auditDetailsResponse = (RetrieveAuditDetailsResponse)service.Execute(auditDetailsRequest);
+ DisplayAuditDetails(service, auditDetailsResponse.AuditDetail);
+ }
+
+ Console.WriteLine("Audit operations complete.");
+ }
+
+ ///
+ /// Cleans up sample data and restores audit settings
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Restoring audit settings...");
+
+ // Restore organization auditing setting
+ Guid orgId = ((WhoAmIResponse)service.Execute(new WhoAmIRequest())).OrganizationId;
+ var orgToUpdate = new Entity("organization")
+ {
+ Id = orgId,
+ ["isauditenabled"] = organizationAuditingFlag
+ };
+ service.Update(orgToUpdate);
+
+ // Restore account entity auditing setting
+ EnableEntityAuditing(service, "account", accountAuditingFlag);
+
+ Console.WriteLine("Audit settings restored.");
+
+ // Delete created records
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ ///
+ /// Enable or disable auditing on an entity
+ ///
+ /// The service client
+ /// The logical name of the entity
+ /// True to enable auditing, false to disable
+ /// The previous value of the IsAuditEnabled attribute
+ private static bool EnableEntityAuditing(ServiceClient service, string entityLogicalName, bool flag)
+ {
+ // Retrieve the entity metadata
+ var entityRequest = new RetrieveEntityRequest
+ {
+ LogicalName = entityLogicalName,
+ EntityFilters = EntityFilters.Entity
+ };
+
+ var entityResponse = (RetrieveEntityResponse)service.Execute(entityRequest);
+
+ // Enable or disable auditing on the entity
+ EntityMetadata entityMetadata = entityResponse.EntityMetadata;
+ bool oldValue = entityMetadata.IsAuditEnabled?.Value ?? false;
+ entityMetadata.IsAuditEnabled = new BooleanManagedProperty(flag);
+
+ var updateEntityRequest = new UpdateEntityRequest { Entity = entityMetadata };
+ service.Execute(updateEntityRequest);
+
+ return oldValue;
+ }
+
+ ///
+ /// Displays audit change history details on the console
+ ///
+ /// The service client
+ /// The audit detail to display
+ private static void DisplayAuditDetails(ServiceClient service, AuditDetail detail)
+ {
+ var record = detail.AuditRecord;
+
+ Console.WriteLine($"\nAudit record created on: {record.GetAttributeValue("createdon").ToLocalTime()}");
+
+ var objectId = record.GetAttributeValue("objectid");
+ string action = record.FormattedValues.ContainsKey("action") ? record.FormattedValues["action"] : "N/A";
+ string operation = record.FormattedValues.ContainsKey("operation") ? record.FormattedValues["operation"] : "N/A";
+
+ Console.WriteLine($"Entity: {objectId?.LogicalName}, Action: {action}, Operation: {operation}");
+
+ var userId = record.GetAttributeValue("userid");
+ Console.WriteLine($"Operation performed by {userId?.Name ?? userId?.Id.ToString() ?? "Unknown"}");
+
+ // Show additional details for AttributeAuditDetail
+ if (detail is AttributeAuditDetail attributeDetail)
+ {
+ string oldValue = "(no value)", newValue = "(no value)";
+
+ // Display the old and new attribute values
+ if (attributeDetail.NewValue != null)
+ {
+ foreach (var attribute in attributeDetail.NewValue.Attributes)
+ {
+ if (attributeDetail.OldValue?.Contains(attribute.Key) == true)
+ {
+ oldValue = GetTypedValueAsString(attributeDetail.OldValue[attribute.Key]);
+ }
+
+ newValue = GetTypedValueAsString(attributeDetail.NewValue[attribute.Key]);
+
+ Console.WriteLine($"Attribute: {attribute.Key}, old value: {oldValue}, new value: {newValue}");
+ }
+ }
+
+ if (attributeDetail.OldValue != null)
+ {
+ foreach (var attribute in attributeDetail.OldValue.Attributes)
+ {
+ if (attributeDetail.NewValue?.Contains(attribute.Key) != true)
+ {
+ newValue = "(no value)";
+ oldValue = GetTypedValueAsString(attributeDetail.OldValue[attribute.Key]);
+
+ Console.WriteLine($"Attribute: {attribute.Key}, old value: {oldValue}, new value: {newValue}");
+ }
+ }
+ }
+ }
+
+ Console.WriteLine();
+ }
+
+ ///
+ /// Returns a string representation of an attribute value
+ ///
+ /// The attribute value
+ /// String representation of the value
+ private static string GetTypedValueAsString(object? typedValue)
+ {
+ if (typedValue == null) return "(null)";
+
+ return typedValue switch
+ {
+ OptionSetValue o => o.Value.ToString(),
+ EntityReference e => $"LogicalName:{e.LogicalName},Id:{e.Id},Name:{e.Name}",
+ _ => typedValue.ToString() ?? "(null)"
+ };
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Audit/AuditEntityData/README.md b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditEntityData/README.md
new file mode 100644
index 00000000..d11f86ac
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditEntityData/README.md
@@ -0,0 +1,112 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates how to enable auditing on an entity and retrieve the change history"
+---
+
+# AuditEntityData
+
+Demonstrates how to enable auditing on an entity and retrieve the change history of records and attributes in Microsoft Dataverse.
+
+## What this sample does
+
+This sample shows how to:
+- Enable auditing at the organization level
+- Enable auditing for a specific entity (account)
+- Create and update records to generate audit history
+- Retrieve and display record change history
+- Retrieve and display attribute change history
+- Retrieve detailed audit information for specific audit records
+
+## How this sample works
+
+### Setup
+
+The sample performs the following setup operations:
+1. Retrieves the current organization auditing setting and caches it for restoration
+2. Enables auditing at the organization level
+3. Enables auditing on the account entity
+4. Creates a new account record
+5. Updates the account with additional attributes (account number, category code, phone number)
+
+### Run
+
+The main demonstration includes:
+1. Retrieving the complete change history for the account record
+2. Displaying audit details including who made changes and when
+3. Updating the account's telephone number
+4. Retrieving the attribute-specific change history for the telephone1 field
+5. Displaying old and new values for changed attributes
+6. Retrieving detailed audit information for a specific audit record
+
+### Cleanup
+
+The cleanup process:
+1. Restores the original organization auditing setting
+2. Restores the original account entity auditing setting
+3. Deletes the created account record
+
+## Demonstrates
+
+This sample demonstrates the following SDK messages and patterns:
+- **WhoAmIRequest/Response**: Get the current organization ID
+- **RetrieveEntityRequest/Response**: Retrieve entity metadata to check audit settings
+- **UpdateEntityRequest**: Enable/disable entity-level auditing
+- **RetrieveRecordChangeHistoryRequest/Response**: Get complete audit history for a record
+- **RetrieveAttributeChangeHistoryRequest/Response**: Get audit history for a specific attribute
+- **RetrieveAuditDetailsRequest/Response**: Get detailed information about a specific audit entry
+- **AuditDetail**: Base class for audit detail types
+- **AttributeAuditDetail**: Contains old and new values for attribute changes
+- **BooleanManagedProperty**: Used to set managed properties like IsAuditEnabled
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Enabling auditing on the organization and account entities...
+Creating an account...
+Updating the account...
+Setup complete.
+
+Retrieving the account change history...
+
+Audit record created on: 2/6/2026 11:50:23 AM
+Entity: account, Action: Update, Operation: Update
+Operation performed by System Administrator
+Attribute: accountnumber, old value: (no value), new value: 1-A
+Attribute: accountcategorycode, old value: (no value), new value: 1
+Attribute: telephone1, old value: (no value), new value: 555-555-5555
+
+Updating the Telephone1 field in the Account entity...
+Retrieving the attribute change history for Telephone1...
+
+Audit record created on: 2/6/2026 11:50:24 AM
+Entity: account, Action: Update, Operation: Update
+Operation performed by System Administrator
+Attribute: telephone1, old value: 555-555-5555, new value: 123-555-5555
+
+Retrieving audit details for an audit record...
+
+Audit record created on: 2/6/2026 11:50:24 AM
+Entity: account, Action: Update, Operation: Update
+Operation performed by System Administrator
+Attribute: telephone1, old value: 555-555-5555, new value: 123-555-5555
+
+Audit operations complete.
+Restoring audit settings...
+Audit settings restored.
+Deleting 1 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Auditing overview](https://learn.microsoft.com/power-apps/developer/data-platform/auditing-overview)
+[Retrieve and delete the history of audited data changes](https://learn.microsoft.com/power-apps/developer/data-platform/auditing/retrieve-audit-data)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Audit/AuditUserAccess/AuditUserAccess.csproj b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditUserAccess/AuditUserAccess.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditUserAccess/AuditUserAccess.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Audit/AuditUserAccess/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditUserAccess/Program.cs
new file mode 100644
index 00000000..e337afd1
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditUserAccess/Program.cs
@@ -0,0 +1,357 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Metadata;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to enable and retrieve audit records for user access
+ ///
+ ///
+ /// This sample shows how to:
+ /// - Enable user access auditing at the organization level
+ /// - Create and update records to generate activity
+ /// - Retrieve audit records that track user access
+ /// - Display user access audit information
+ ///
+ /// Prerequisites:
+ /// - System Administrator role
+ /// - Auditing feature available in your Dataverse environment
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static bool organizationAuditingFlag;
+ private static bool userAccessAuditingFlag;
+ private static Guid systemUserId;
+ private static DateTime sampleStartTime;
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data by enabling auditing and creating an account
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ // Record the start time for filtering audit records later
+ sampleStartTime = DateTime.UtcNow;
+
+ Console.WriteLine("Enabling auditing on the organization and for user access...");
+
+ // Get the current user and organization IDs
+ var whoAmIReq = new WhoAmIRequest();
+ var whoAmIRes = (WhoAmIResponse)service.Execute(whoAmIReq);
+ Guid orgId = whoAmIRes.OrganizationId;
+ systemUserId = whoAmIRes.UserId;
+
+ // Retrieve the organization's record
+ var org = service.Retrieve("organization", orgId,
+ new ColumnSet("organizationid", "isauditenabled", "isuseraccessauditenabled", "useraccessauditinginterval"));
+
+ // Cache current settings
+ organizationAuditingFlag = org.GetAttributeValue("isauditenabled");
+ userAccessAuditingFlag = org.GetAttributeValue("isuseraccessauditenabled");
+
+ // Enable auditing if not already enabled
+ if (!organizationAuditingFlag || !userAccessAuditingFlag)
+ {
+ var orgToUpdate = new Entity("organization")
+ {
+ Id = orgId,
+ ["isauditenabled"] = true,
+ ["isuseraccessauditenabled"] = true
+ };
+ service.Update(orgToUpdate);
+
+ Console.WriteLine("Enabled auditing for the organization and for user access.");
+ int? interval = org.GetAttributeValue("useraccessauditinginterval");
+ Console.WriteLine($"Auditing interval is set to {interval ?? 0} hours.");
+ }
+ else
+ {
+ Console.WriteLine("Auditing was already enabled, so no auditing settings were changed.");
+ }
+
+ // Enable auditing on the account entity
+ bool accountAuditingFlag = EnableEntityAuditing(service, "account", true);
+
+ // Create an account
+ Console.WriteLine("Creating an account...");
+ var newAccount = new Entity("account")
+ {
+ ["name"] = "Example Account"
+ };
+ Guid accountId = service.Create(newAccount);
+ entityStore.Add(new EntityReference("account", accountId));
+
+ // Update the account to generate audit activity
+ Console.WriteLine("Updating the account...");
+ var accountToUpdate = new Entity("account")
+ {
+ Id = accountId,
+ ["accountnumber"] = "1-A",
+ ["accountcategorycode"] = new OptionSetValue(1), // Preferred Customer
+ ["telephone1"] = "555-555-5555"
+ };
+ service.Update(accountToUpdate);
+
+ Console.WriteLine("Setup complete. Account created and updated to generate audit records.");
+ Console.WriteLine();
+ }
+
+ ///
+ /// Demonstrates retrieving and displaying user access audit records
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Retrieving user access audit records...");
+ Console.WriteLine();
+
+ // Create query to retrieve user access audit records
+ var query = new QueryExpression("audit")
+ {
+ ColumnSet = new ColumnSet(true),
+ Criteria = new FilterExpression(LogicalOperator.And)
+ };
+
+ // Filter for user access audit actions
+ query.Criteria.AddCondition("action", ConditionOperator.In,
+ 64, // UserAccessAuditStarted
+ 65, // UserAccessAuditStopped
+ 66, // UserAccessviaWebServices
+ 67 // UserAccessviaWeb
+ );
+
+ // Only retrieve records created during this sample run
+ query.Criteria.AddCondition("createdon", ConditionOperator.GreaterEqual, sampleStartTime);
+
+ // Optional: Filter to only show current user's records
+ var filterAuditsRetrievedByUser = true;
+ if (filterAuditsRetrievedByUser)
+ {
+ var userFilter = new FilterExpression(LogicalOperator.Or);
+ userFilter.AddCondition("userid", ConditionOperator.Equal, systemUserId);
+ userFilter.AddCondition("useridname", ConditionOperator.Equal, "SYSTEM");
+ query.Criteria.AddFilter(userFilter);
+ }
+
+ // Execute the query
+ var results = service.RetrieveMultiple(query);
+
+ if (results.Entities.Count == 0)
+ {
+ Console.WriteLine("No user access audit records found for this session.");
+ Console.WriteLine("Note: User access audit records may take time to appear based on the");
+ Console.WriteLine("configured auditing interval (typically several hours).");
+ }
+ else
+ {
+ Console.WriteLine($"Retrieved {results.Entities.Count} audit record(s):");
+ Console.WriteLine();
+
+ foreach (Entity audit in results.Entities)
+ {
+ var action = audit.GetAttributeValue("action");
+ var userId = audit.GetAttributeValue("userid");
+ var createdOn = audit.GetAttributeValue("createdon");
+ var operation = audit.GetAttributeValue("operation");
+ var objectId = audit.GetAttributeValue("objectid");
+
+ Console.WriteLine($" Action: {GetAuditActionName(action?.Value)},");
+ Console.WriteLine($" User: {userId?.Name ?? "Unknown"},");
+ Console.WriteLine($" Created On: {createdOn.ToLocalTime()},");
+ Console.WriteLine($" Operation: {GetAuditOperationName(operation?.Value)}");
+
+ // Display the name of the related object
+ if (objectId != null && !string.IsNullOrEmpty(objectId.Name))
+ {
+ Console.WriteLine($" Related Record: {objectId.Name}");
+ }
+
+ Console.WriteLine();
+ }
+ }
+
+ Console.WriteLine("User access audit retrieval complete.");
+ }
+
+ ///
+ /// Cleans up sample data and restores audit settings
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Restoring audit settings...");
+
+ // Restore organization auditing settings if they were changed
+ if (!organizationAuditingFlag || !userAccessAuditingFlag)
+ {
+ var whoAmIReq = new WhoAmIRequest();
+ var whoAmIRes = (WhoAmIResponse)service.Execute(whoAmIReq);
+ Guid orgId = whoAmIRes.OrganizationId;
+
+ var orgToUpdate = new Entity("organization")
+ {
+ Id = orgId,
+ ["isauditenabled"] = organizationAuditingFlag,
+ ["isuseraccessauditenabled"] = userAccessAuditingFlag
+ };
+ service.Update(orgToUpdate);
+
+ Console.WriteLine("Reverted organization and user access auditing to their previous values.");
+ }
+ else
+ {
+ Console.WriteLine("Auditing was enabled before the sample began, so no auditing settings were reverted.");
+ }
+
+ // Restore account entity auditing
+ EnableEntityAuditing(service, "account", false);
+
+ // Delete created records
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ ///
+ /// Enable or disable auditing on an entity
+ ///
+ private static bool EnableEntityAuditing(ServiceClient service, string entityLogicalName, bool flag)
+ {
+ var entityRequest = new RetrieveEntityRequest
+ {
+ LogicalName = entityLogicalName,
+ EntityFilters = EntityFilters.Attributes
+ };
+
+ var entityResponse = (RetrieveEntityResponse)service.Execute(entityRequest);
+ EntityMetadata entityMetadata = entityResponse.EntityMetadata;
+
+ bool oldValue = entityMetadata.IsAuditEnabled?.Value ?? false;
+ entityMetadata.IsAuditEnabled = new BooleanManagedProperty(flag);
+
+ var updateEntityRequest = new UpdateEntityRequest { Entity = entityMetadata };
+ service.Execute(updateEntityRequest);
+
+ return oldValue;
+ }
+
+ ///
+ /// Gets a friendly name for audit action codes
+ ///
+ private static string GetAuditActionName(int? actionCode)
+ {
+ return actionCode switch
+ {
+ 1 => "Create",
+ 2 => "Update",
+ 3 => "Delete",
+ 64 => "User Access Audit Started",
+ 65 => "User Access Audit Stopped",
+ 66 => "User Access via Web Services",
+ 67 => "User Access via Web",
+ _ => $"Unknown ({actionCode})"
+ };
+ }
+
+ ///
+ /// Gets a friendly name for audit operation codes
+ ///
+ private static string GetAuditOperationName(int? operationCode)
+ {
+ return operationCode switch
+ {
+ 1 => "Create",
+ 2 => "Update",
+ 3 => "Delete",
+ 4 => "Access",
+ _ => $"Unknown ({operationCode})"
+ };
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Audit/AuditUserAccess/README.md b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditUserAccess/README.md
new file mode 100644
index 00000000..e1dd79b4
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Audit/AuditUserAccess/README.md
@@ -0,0 +1,128 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates how to enable and retrieve audit records for user access"
+---
+
+# AuditUserAccess
+
+Demonstrates how to enable user access auditing and retrieve audit records that track when users access Microsoft Dataverse.
+
+## What this sample does
+
+This sample shows how to:
+- Enable user access auditing at the organization level
+- Configure auditing settings for tracking user activity
+- Create and update records to generate user activity
+- Query and retrieve user access audit records
+- Display audit information including user actions and timestamps
+
+User access auditing tracks when users log in to Dataverse and access the system through web services or the web interface.
+
+## How this sample works
+
+### Setup
+
+The sample performs the following setup operations:
+1. Records the sample start time for filtering audit records
+2. Retrieves the current organization auditing and user access auditing settings
+3. Enables organization-level auditing if not already enabled
+4. Enables user access auditing if not already enabled
+5. Displays the configured user access auditing interval
+6. Enables auditing on the account entity
+7. Creates a new account record
+8. Updates the account to generate audit activity
+
+### Run
+
+The main demonstration includes:
+1. Building a query to retrieve user access audit records
+2. Filtering for specific audit actions:
+ - UserAccessAuditStarted (64)
+ - UserAccessAuditStopped (65)
+ - UserAccessviaWebServices (66)
+ - UserAccessviaWeb (67)
+3. Filtering records to only those created during the sample execution
+4. Optionally filtering to only show the current user's access records
+5. Displaying audit details including:
+ - Action type
+ - User who performed the action
+ - Timestamp
+ - Operation type
+ - Related record information
+
+**Important Note**: User access audit records may not appear immediately. Dataverse typically batches and creates these records based on the configured auditing interval (often several hours).
+
+### Cleanup
+
+The cleanup process:
+1. Restores the original organization auditing setting
+2. Restores the original user access auditing setting
+3. Disables auditing on the account entity
+4. Deletes the created account record
+
+## Demonstrates
+
+This sample demonstrates the following SDK messages and patterns:
+- **WhoAmIRequest/Response**: Get the current user and organization IDs
+- **QueryExpression**: Building complex queries with multiple criteria
+- **FilterExpression**: Filtering audit records by action type and time
+- **ConditionOperator.In**: Querying for multiple specific values
+- **RetrieveEntityRequest/Response**: Retrieve entity metadata
+- **UpdateEntityRequest**: Enable/disable entity-level auditing
+- **BooleanManagedProperty**: Set managed properties like IsAuditEnabled
+- Working with audit action codes (64-67 for user access)
+- Filtering audit records by creation date
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Enabling auditing on the organization and for user access...
+Enabled auditing for the organization and for user access.
+Auditing interval is set to 4 hours.
+Creating an account...
+Updating the account...
+Setup complete. Account created and updated to generate audit records.
+
+Retrieving user access audit records...
+
+No user access audit records found for this session.
+Note: User access audit records may take time to appear based on the
+configured auditing interval (typically several hours).
+
+User access audit retrieval complete.
+Restoring audit settings...
+Reverted organization and user access auditing to their previous values.
+Deleting 1 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+**Note**: If user access audit records exist, they would display like:
+```
+Retrieved 2 audit record(s):
+
+ Action: User Access via Web Services,
+ User: System Administrator,
+ Created On: 2/6/2026 11:55:00 AM,
+ Operation: Access
+ Related Record: System Administrator
+
+ Action: User Access Audit Started,
+ User: System Administrator,
+ Created On: 2/6/2026 11:50:00 AM,
+ Operation: Access
+```
+
+## See also
+
+[Auditing overview](https://learn.microsoft.com/power-apps/developer/data-platform/auditing-overview)
+[Configure auditing](https://learn.microsoft.com/power-apps/developer/data-platform/auditing/configure)
+[User access auditing](https://learn.microsoft.com/power-apps/developer/data-platform/auditing/configure#user-access-auditing)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Audit/README.md b/dataverse/orgsvc/CSharp-NETCore/Audit/README.md
new file mode 100644
index 00000000..1c005239
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Audit/README.md
@@ -0,0 +1,56 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates auditing capabilities in Dataverse including entity data auditing and user access auditing"
+---
+
+# Audit
+Demonstrates auditing capabilities in Dataverse including entity data auditing and user access auditing
+
+More information: [Audit](https://learn.microsoft.com/power-apps/developer/data-platform/auditing-overview)
+
+## Samples
+
+This folder contains the following samples:
+
+|Sample folder|Description|Build target|
+|---|---|---|
+|[AuditEntityData](AuditEntityData)|Demonstrates how to enable auditing on an entity and retrieve the change history of records and attributes|.NET 6|
+|[AuditUserAccess](AuditUserAccess)|Demonstrates how to enable user access auditing and retrieve audit records that track when users access Dataverse|.NET 6|
+
+## Prerequisites
+
+- Microsoft Visual Studio 2022
+- Access to Dataverse with appropriate privileges for the operations demonstrated
+
+## How to run samples
+
+1. Clone or download the PowerApps-Samples repository
+2. Navigate to `/dataverse/orgsvc/CSharp-NETCore/Audit/`
+3. Open `Audit.sln` in Visual Studio 2022
+4. Edit the `appsettings.json` file in the category folder root with your Dataverse environment details:
+ - Set `Url` to your Dataverse environment URL
+ - Set `Username` to your user account
+5. Build and run the desired sample project
+
+## appsettings.json
+
+Each sample in this category references the shared `appsettings.json` file in the category root folder. The connection string format is:
+
+```json
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
+```
+
+You can also set the `DATAVERSE_APPSETTINGS` environment variable to point to a custom appsettings.json file location if you prefer to keep your connection string outside the repository.
+
+## See also
+
+[SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/overview)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Audit/appsettings.json b/dataverse/orgsvc/CSharp-NETCore/Audit/appsettings.json
new file mode 100644
index 00000000..037aca85
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Audit/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/CRUD-Dynamic-Entity/CRUD-Dynamic-Entity.csproj b/dataverse/orgsvc/CSharp-NETCore/CRUD/CRUD-Dynamic-Entity/CRUD-Dynamic-Entity.csproj
new file mode 100644
index 00000000..56ab4f18
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/CRUD-Dynamic-Entity/CRUD-Dynamic-Entity.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ PowerPlatform_Dataverse_CodeSamples
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/CRUD-Dynamic-Entity/Program.cs b/dataverse/orgsvc/CSharp-NETCore/CRUD/CRUD-Dynamic-Entity/Program.cs
new file mode 100644
index 00000000..e5e36f26
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/CRUD-Dynamic-Entity/Program.cs
@@ -0,0 +1,266 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform_Dataverse_CodeSamples
+{
+ ///
+ /// Demonstrates basic Create, Retrieve, Update, and Delete operations
+ /// using dynamic (late-bound) entities.
+ ///
+ ///
+ /// This sample shows how to work with Dataverse entities using late-bound syntax,
+ /// where entity types and attributes are specified as strings rather than
+ /// strongly-typed classes.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ /// You will be prompted in the default browser to enter a password.
+ ///
+ ///
+ ///
+ ///
+ ///
+ class Program
+ {
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ static IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ static Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ // Entity name and reference collection.
+ Dictionary entityStore;
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ try
+ {
+ // Pre-create any table rows that the Run() method requires.
+ Setup(serviceClient, out entityStore);
+
+ // Execute the main logic of this program.
+ Run(serviceClient, entityStore);
+
+ // Pause program execution before resource cleanup.
+ Console.WriteLine();
+ Console.WriteLine("Press any key to undo environment data changes.");
+ Console.ReadKey();
+
+ // In Dataverse, delete any created table rows and then dispose the service connection.
+ Cleanup(serviceClient, entityStore);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ serviceClient.Dispose();
+ }
+ }
+
+ ///
+ /// Executes the code being demonstrated by this program.
+ ///
+ /// Authenticated web service connection.
+ /// Entity name and reference collection.
+ /// True if successful; otherwise false.
+ static public bool Run(IOrganizationService service,
+ Dictionary entityStore)
+ {
+ try
+ {
+ // Instantiate an account object using late-bound Entity class.
+ // Note: With late-bound entities, the entity logical name is passed as a string.
+ Entity newAccount = new Entity("account");
+
+ // Set the required attributes. For account, only the name is required.
+ // Note: Attributes are accessed using string indexer syntax.
+ newAccount["name"] = "Fourth Coffee";
+
+ // Set any other attribute values.
+ newAccount["address2_postalcode"] = "98074";
+
+ Console.WriteLine("Creating account entity named '{0}'...", newAccount["name"]);
+
+ // Create an account record named Fourth Coffee.
+ Guid accountid = service.Create(newAccount);
+
+ // Store the created entity for cleanup.
+ entityStore.Add("account", new EntityReference("account", accountid));
+
+ Console.WriteLine("Created {0} entity named {1}.", newAccount.LogicalName, newAccount["name"]);
+ Console.WriteLine();
+
+ // Create a column set to define which attributes should be retrieved.
+ ColumnSet attributes = new ColumnSet("name", "ownerid");
+
+ Console.WriteLine("Retrieving account...");
+
+ // Retrieve the account and its name and ownerid attributes.
+ newAccount = service.Retrieve(newAccount.LogicalName, accountid, attributes);
+
+ Console.WriteLine("Retrieved entity:");
+ Console.WriteLine(" Name: {0}", newAccount.GetAttributeValue("name"));
+ Console.WriteLine(" Owner ID: {0}", newAccount.GetAttributeValue("ownerid").Id);
+ Console.WriteLine();
+
+ /*
+ IMPORTANT:
+ Do not update an entity using a retrieved entity instance.
+ Always instantiate a new Entity and
+ set the primary key value to match the entity you want to update.
+ Only set the attribute values you are changing.
+ */
+
+ Console.WriteLine("Updating account...");
+
+ Entity accountToUpdate = new Entity("account");
+ accountToUpdate["accountid"] = newAccount.Id;
+
+ // Update the address 1 postal code attribute.
+ accountToUpdate["address1_postalcode"] = "98052";
+
+ // The address 2 postal code was set accidentally, so set it to null.
+ accountToUpdate["address2_postalcode"] = null;
+
+ // Shows use of Money.
+ accountToUpdate["revenue"] = new Money(5000000);
+
+ // Shows use of boolean.
+ accountToUpdate["creditonhold"] = false;
+
+ // Perform the update.
+ service.Update(accountToUpdate);
+
+ Console.WriteLine("Updated entity:");
+ Console.WriteLine(" Address 1 Postal Code: 98052");
+ Console.WriteLine(" Address 2 Postal Code: null (cleared)");
+ Console.WriteLine(" Revenue: $5,000,000");
+ Console.WriteLine(" Credit On Hold: false");
+ Console.WriteLine();
+
+ Console.WriteLine("Sample completed successfully.");
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Run(): an exception occurred: \n\t" + ex.Message);
+ return false;
+ }
+ }
+
+ ///
+ /// Creates any pre-existing entity records required by the Run() method.
+ ///
+ /// Authenticated web service connection.
+ /// Entity name and reference collection.
+ static public void Setup(IOrganizationService service, out Dictionary entityStore)
+ {
+ // Used to track any entities created by this program.
+ entityStore = new Dictionary();
+
+ Console.WriteLine("Setup complete - no pre-existing entities required.");
+ Console.WriteLine();
+ }
+
+ ///
+ /// Delete any entity records (table rows) created by this program.
+ ///
+ /// Authenticated web service connection.
+ /// Entity name and reference collection.
+ static public void Cleanup(ServiceClient service,
+ Dictionary entityStore)
+ {
+ // Do some checking of the passed parameter values.
+ if (service == null || service.IsReady == false)
+ {
+ Console.WriteLine(
+ "Cleanup(): web service connection not available, cleanup aborted.");
+ return;
+ }
+
+ if (entityStore == null)
+ {
+ Console.WriteLine("Cleanup(): entityStore is null, cleanup aborted.");
+ Console.WriteLine("Cleanup(): run Setup() prior to Cleanup().");
+ return;
+ }
+
+ Console.WriteLine();
+ Console.WriteLine("Cleaning up entities...");
+
+ // Collect the keys of entities to be deleted.
+ var keysToDelete = new List(entityStore.Keys);
+ keysToDelete.Reverse();
+
+ // Delete in Dataverse each entity in the entity store.
+ foreach (var key in keysToDelete)
+ {
+ var entref = entityStore[key];
+ try
+ {
+ service.Delete(entref.LogicalName, entref.Id);
+ Console.WriteLine("Deleted {0} entity.", key);
+ entityStore.Remove(key);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(
+ "Cleanup(): exception deleting {0}\n\t{1}", key, ex.Message);
+ continue;
+ }
+ }
+
+ // Output a list of entities that could not be deleted.
+ if (entityStore.Count > 0)
+ {
+ Console.WriteLine(
+ "Cleanup(): the following entities could not be deleted:");
+
+ foreach (var item in entityStore)
+ {
+ Console.WriteLine("Cleanup(): name={0}, " +
+ "logical name={1}, ID={2}", item.Key, item.Value.LogicalName, item.Value.Id);
+ }
+ }
+ else
+ {
+ Console.WriteLine("Cleanup complete.");
+ }
+ }
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/CRUD-Dynamic-Entity/README.md b/dataverse/orgsvc/CSharp-NETCore/CRUD/CRUD-Dynamic-Entity/README.md
new file mode 100644
index 00000000..d92b8607
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/CRUD-Dynamic-Entity/README.md
@@ -0,0 +1,138 @@
+# CRUD operations using dynamic (late-bound) entities
+
+This sample demonstrates basic Create, Retrieve, Update, and Delete (CRUD) operations using dynamic (late-bound) entities in Microsoft Dataverse.
+
+## What this sample does
+
+This sample shows how to:
+
+- **Create** an account entity using late-bound syntax
+- **Retrieve** the account with specific columns
+- **Update** the account with new attribute values
+- **Delete** the account to clean up
+
+The sample emphasizes the use of late-bound entities, where entity types and attributes are specified as strings rather than strongly-typed classes.
+
+## How this sample works
+
+### Setup
+
+The Setup method initializes the entity store dictionary. This sample doesn't require any pre-existing data.
+
+### Demonstrate
+
+1. **Create**: Creates a new account entity using `Entity("account")` and sets attributes using string indexer syntax:
+ - Sets the required `name` attribute to "Fourth Coffee"
+ - Sets optional `address2_postalcode` to "98074"
+ - Uses `service.Create()` to create the record in Dataverse
+
+2. **Retrieve**: Retrieves the account using `service.Retrieve()`:
+ - Uses `ColumnSet` to specify which attributes to retrieve (`name` and `ownerid`)
+ - Demonstrates accessing retrieved attribute values with `GetAttributeValue()`
+
+3. **Update**: Updates the account using proper update pattern:
+ - Creates a new `Entity` instance (does not reuse retrieved entity)
+ - Sets the primary key (`accountid`) to match the entity to update
+ - Updates `address1_postalcode` to "98052"
+ - Clears `address2_postalcode` by setting it to null
+ - Demonstrates using `Money` type for revenue
+ - Demonstrates using boolean for `creditonhold`
+
+4. **Delete**: Removes the account in the Cleanup method
+
+### Key Concepts
+
+**Late-bound vs Early-bound Entities:**
+- **Late-bound** (this sample): Uses the generic `Entity` class with string-based entity names and attributes
+ ```csharp
+ Entity account = new Entity("account");
+ account["name"] = "Fourth Coffee";
+ ```
+- **Early-bound**: Uses generated strongly-typed classes
+ ```csharp
+ Account account = new Account();
+ account.Name = "Fourth Coffee";
+ ```
+
+**Important Update Pattern:**
+> Do not update an entity using a retrieved entity instance. Always instantiate a new Entity and set the primary key value to match the entity you want to update. Only set the attribute values you are changing.
+
+This prevents accidentally updating attributes that shouldn't be changed and improves performance.
+
+### Clean up
+
+The Cleanup method deletes the account entity that was created during the demonstration.
+
+## Run this sample
+
+1. Clone or download the [PowerApps-Samples](https://github.com/microsoft/PowerApps-Samples) repository.
+
+2. Navigate to the sample directory:
+ ```
+ cd dataverse/orgsvc/CSharp-NETCore/CRUD/CRUD-Dynamic-Entity
+ ```
+
+3. Edit the `appsettings.json` file in the parent CRUD directory (`dataverse/orgsvc/CSharp-NETCore/CRUD/appsettings.json`) with your Dataverse environment connection information:
+ ```json
+ {
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+ }
+ ```
+
+4. Build and run the sample:
+ ```
+ dotnet run
+ ```
+
+## What this sample does
+
+When you run the sample, you will see output similar to:
+
+```
+Connected to Dataverse.
+
+Setup complete - no pre-existing entities required.
+
+Creating account entity named 'Fourth Coffee'...
+Created account entity named Fourth Coffee.
+
+Retrieving account...
+Retrieved entity:
+ Name: Fourth Coffee
+ Owner ID:
+
+Updating account...
+Updated entity:
+ Address 1 Postal Code: 98052
+ Address 2 Postal Code: null (cleared)
+ Revenue: $5,000,000
+ Credit On Hold: false
+
+Sample completed successfully.
+
+Press any key to undo environment data changes.
+
+Cleaning up entities...
+Deleted account entity.
+Cleanup complete.
+```
+
+## Demonstrates
+
+- Using late-bound `Entity` class for dynamic entity operations
+- Creating entities with `IOrganizationService.Create()`
+- Retrieving entities with `IOrganizationService.Retrieve()` and `ColumnSet`
+- Updating entities with proper update pattern
+- Deleting entities with `IOrganizationService.Delete()`
+- Working with `Money` type for currency fields
+- Clearing attribute values by setting to null
+- Tracking created entities for cleanup
+
+## See Also
+
+[Create entities using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-create)
+[Retrieve an entity using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-retrieve)
+[Update and delete entities using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-update-delete)
+[Late-bound and early-bound programming using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/early-bound-programming)
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/CreateUpdateRecordsWithRelatedRecords/CreateUpdateRecordsWithRelatedRecords.csproj b/dataverse/orgsvc/CSharp-NETCore/CRUD/CreateUpdateRecordsWithRelatedRecords/CreateUpdateRecordsWithRelatedRecords.csproj
new file mode 100644
index 00000000..f9980349
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/CreateUpdateRecordsWithRelatedRecords/CreateUpdateRecordsWithRelatedRecords.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/CreateUpdateRecordsWithRelatedRecords/Program.cs b/dataverse/orgsvc/CSharp-NETCore/CRUD/CreateUpdateRecordsWithRelatedRecords/Program.cs
new file mode 100644
index 00000000..45bf318d
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/CreateUpdateRecordsWithRelatedRecords/Program.cs
@@ -0,0 +1,245 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+
+namespace PowerApps.Samples
+{
+ ///
+ /// Demonstrates creating and updating records with related records in a single operation.
+ ///
+ internal class Program
+ {
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ private static IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application settings from a JSON configuration file.
+ ///
+ static Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ // Entity name and reference collection.
+ Dictionary entityStore;
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine(serviceClient.LastError);
+ return;
+ }
+
+ // Pre-create any table rows that the Run() method requires.
+ Setup(serviceClient, out entityStore);
+
+ // Execute the main logic of this program.
+ Run(serviceClient, entityStore);
+
+ // Pause program execution before resource cleanup.
+ Console.WriteLine("\nPress any key to undo environment data changes.");
+ Console.ReadKey();
+
+ // In Dataverse, delete any created table rows and then dispose the service connection.
+ Cleanup(serviceClient, entityStore);
+ serviceClient.Dispose();
+
+ Console.WriteLine("Sample completed. Press any key to exit.");
+ Console.ReadKey();
+ }
+
+ ///
+ /// Executes the code being demonstrated by this program.
+ ///
+ /// Authenticated web service connection.
+ /// Entity name and reference collection.
+ static public void Run(IOrganizationService service,
+ Dictionary entityStore)
+ {
+ Console.WriteLine("\n=== Creating Account with Related Letters ===");
+
+ // Define the account for which we will add letters
+ var accountToCreate = new Entity("account")
+ {
+ ["name"] = "Example Account"
+ };
+
+ // Define the IDs of the related letters we will create
+ Guid[] letterIds = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
+
+ // This acts as a container for each letter we create. Note that we haven't
+ // defined the relationship between the letter and account yet.
+ var relatedLettersToCreate = new EntityCollection
+ {
+ EntityName = "letter",
+ Entities =
+ {
+ new Entity("letter")
+ {
+ ["subject"] = "Letter 1",
+ ["activityid"] = letterIds[0]
+ },
+ new Entity("letter")
+ {
+ ["subject"] = "Letter 2",
+ ["activityid"] = letterIds[1]
+ },
+ new Entity("letter")
+ {
+ ["subject"] = "Letter 3",
+ ["activityid"] = letterIds[2]
+ }
+ }
+ };
+
+ // Creates the reference between which relationship between Letter and
+ // Account we would like to use.
+ var letterRelationship = new Relationship("Account_Letters");
+
+ // Adds the letters to the account under the specified relationship
+ accountToCreate.RelatedEntities.Add(letterRelationship, relatedLettersToCreate);
+
+ // Passes the Account (which contains the letters)
+ Guid accountId = service.Create(accountToCreate);
+
+ Console.WriteLine($"Created account with ID: {accountId}");
+ Console.WriteLine($"Created {letterIds.Length} related letter activities.");
+
+ // Store the created entities
+ entityStore.Add("account", new EntityReference("account", accountId));
+ for (int i = 0; i < letterIds.Length; i++)
+ {
+ entityStore.Add($"letter{i + 1}", new EntityReference("letter", letterIds[i]));
+ }
+
+ Console.WriteLine("\n=== Updating Account with Related Letters ===");
+
+ // Now we run through many of the same steps as the above "Create" example
+ var accountToUpdate = new Entity("account")
+ {
+ ["name"] = "Example Account - Updated",
+ Id = accountId
+ };
+
+ var relatedLettersToUpdate = new EntityCollection
+ {
+ EntityName = "letter",
+ Entities =
+ {
+ new Entity("letter")
+ {
+ ["subject"] = "Letter 1 - Updated",
+ Id = letterIds[0]
+ },
+ new Entity("letter")
+ {
+ ["subject"] = "Letter 2 - Updated",
+ Id = letterIds[1]
+ },
+ new Entity("letter")
+ {
+ ["subject"] = "Letter 3 - Updated",
+ Id = letterIds[2]
+ }
+ }
+ };
+
+ accountToUpdate.RelatedEntities.Add(letterRelationship, relatedLettersToUpdate);
+
+ // This will update the account as well as all of the related letters
+ service.Update(accountToUpdate);
+
+ Console.WriteLine("Updated account and all related letter activities.");
+ }
+
+ ///
+ /// Creates any pre-existing entity records required by the Run() method.
+ ///
+ /// Authenticated web service connection.
+ /// Entity name and reference collection.
+ static public void Setup(IOrganizationService service,
+ out Dictionary entityStore)
+ {
+ // Used to track any entities created by this program.
+ entityStore = new Dictionary();
+
+ Console.WriteLine("=== Setup ===");
+ Console.WriteLine("No setup required for this sample.");
+ }
+
+ ///
+ /// Delete any entity records (table rows) created by this program.
+ ///
+ /// Authenticated web service connection.
+ /// Entity name and reference collection.
+ static public void Cleanup(ServiceClient service,
+ Dictionary entityStore)
+ {
+ Console.WriteLine("\n=== Cleanup ===");
+
+ // Do some checking of the passed parameter values.
+ if (service == null || service.IsReady == false)
+ {
+ Console.WriteLine("Web service connection not available, cleanup aborted.");
+ return;
+ }
+
+ if (entityStore == null)
+ {
+ Console.WriteLine("entityStore is null, cleanup aborted.");
+ return;
+ }
+
+ // Collect the keys of entities to be deleted.
+ var keysToDelete = new List(entityStore.Keys);
+ keysToDelete.Reverse();
+
+ // Delete in Dataverse each entity in the entity store.
+ foreach (var key in keysToDelete)
+ {
+ var entref = entityStore[key];
+ try
+ {
+ service.Delete(entref.LogicalName, entref.Id);
+ Console.WriteLine($"Deleted {entref.LogicalName} with ID: {entref.Id}");
+ entityStore.Remove(key);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Exception deleting {key}: {ex.Message}");
+ continue;
+ }
+ }
+
+ // Output a list of entities that could not be deleted.
+ if (entityStore.Count > 0)
+ {
+ Console.WriteLine("The following entities could not be deleted:");
+ foreach (var item in entityStore)
+ {
+ Console.WriteLine($" Name={item.Key}, LogicalName={item.Value.LogicalName}, ID={item.Value.Id}");
+ }
+ }
+ else
+ {
+ Console.WriteLine("All created records have been deleted.");
+ }
+ }
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/CreateUpdateRecordsWithRelatedRecords/README.md b/dataverse/orgsvc/CSharp-NETCore/CRUD/CreateUpdateRecordsWithRelatedRecords/README.md
new file mode 100644
index 00000000..be9a4b8f
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/CreateUpdateRecordsWithRelatedRecords/README.md
@@ -0,0 +1,76 @@
+# Create and update records with related records
+
+This sample demonstrates how to create and update a record and related records in one call using the following methods:
+
+- [IOrganizationService.Create](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.create)
+- [IOrganizationService.Update](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.update)
+
+## Key Concepts
+
+This sample shows how to use the `RelatedEntities` property to include related records in create and update operations. This allows you to:
+
+1. Create a parent record along with multiple related child records in a single `Create` call
+2. Update a parent record along with multiple related child records in a single `Update` call
+
+## How to run this sample
+
+1. Clone or download the [PowerApps-Samples](https://github.com/microsoft/PowerApps-Samples) repository
+2. Navigate to the `dataverse/orgsvc/CSharp-NETCore/CRUD` directory
+3. Update `appsettings.json` with your Dataverse environment connection string
+4. Run the sample:
+ ```bash
+ cd CreateUpdateRecordsWithRelatedRecords
+ dotnet run
+ ```
+
+## What this sample does
+
+The sample demonstrates the following operations:
+
+### Create Operation
+1. Creates an account record named "Example Account"
+2. Creates 3 letter activity records with subjects "Letter 1", "Letter 2", and "Letter 3"
+3. Establishes the relationship between the account and letters
+4. Creates all records in a single `Create` operation using the `RelatedEntities` collection
+
+### Update Operation
+1. Updates the account name to "Example Account - Updated"
+2. Updates all 3 letter subjects to include "- Updated" suffix
+3. Updates all records in a single `Update` operation using the `RelatedEntities` collection
+
+## How this sample works
+
+### Setup
+No setup is required for this sample. All necessary records are created during the Run phase.
+
+### Demonstrate
+
+1. **Create account with related letters**:
+ - Creates an `Entity` object for an account
+ - Creates an `EntityCollection` containing 3 letter entities
+ - Defines a `Relationship` object for "Account_Letters"
+ - Adds the letters to the account's `RelatedEntities` using the relationship
+ - Calls `service.Create()` to create all records in one operation
+
+2. **Update account with related letters**:
+ - Creates an `Entity` object for the account with updated name
+ - Creates an `EntityCollection` containing the 3 existing letters with updated subjects
+ - Adds the letters to the account's `RelatedEntities` using the same relationship
+ - Calls `service.Update()` to update all records in one operation
+
+### Cleanup
+Displays an option to delete the records created. The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
+
+## Key Features
+
+- **Late-bound Entity syntax**: Uses the `Entity` class with string-based attribute access
+- **RelatedEntities collection**: Demonstrates using `Entity.RelatedEntities` to work with related records
+- **Relationship objects**: Shows how to define relationships between entities
+- **Single operation efficiency**: Creates/updates multiple related records in one call, reducing round trips
+
+## Related Resources
+
+- [Create related table rows in one operation](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-create#create-related-table-rows-in-one-operation)
+- [Update related table rows in one operation](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-update#update-related-table-rows-in-one-operation)
+- [Entity.RelatedEntities Property](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.entity.relatedentities)
+- [Relationship Class](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.relationship)
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/EarlyBoundEntityOperations/EarlyBoundEntityOperations.csproj b/dataverse/orgsvc/CSharp-NETCore/CRUD/EarlyBoundEntityOperations/EarlyBoundEntityOperations.csproj
new file mode 100644
index 00000000..55875542
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/EarlyBoundEntityOperations/EarlyBoundEntityOperations.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ PowerPlatform_Dataverse_CodeSamples
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/EarlyBoundEntityOperations/Program.cs b/dataverse/orgsvc/CSharp-NETCore/CRUD/EarlyBoundEntityOperations/Program.cs
new file mode 100644
index 00000000..78810472
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/EarlyBoundEntityOperations/Program.cs
@@ -0,0 +1,295 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform_Dataverse_CodeSamples
+{
+ ///
+ /// Demonstrates Create, Retrieve, Update, and Delete operations on account entities
+ /// using various attribute types including OptionSet, Money, Boolean, EntityReference, and Memo.
+ ///
+ ///
+ /// This sample was migrated from a legacy early-bound sample to use late-bound Entity class
+ /// for better consistency with modern .NET patterns. It demonstrates the same operations
+ /// that were originally done with strongly-typed early-bound classes.
+ ///
+ /// This sample demonstrates:
+ /// - Creating an account entity with basic attributes
+ /// - Retrieving entity with specific columns using ColumnSet
+ /// - Working with version numbers (BigInt attribute)
+ /// - Updating various attribute types:
+ /// * String attributes (postal codes)
+ /// * OptionSet attributes (address type, shipping method, industry code)
+ /// * Money attributes (revenue)
+ /// * Boolean attributes (credit on hold)
+ /// * EntityReference attributes (parent account)
+ /// * Memo attributes (description)
+ /// - Setting attributes to null
+ /// - Deleting entities
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ ///
+ ///
+ ///
+ ///
+ class Program
+ {
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// List of entity references to delete during cleanup
+ ///
+ private readonly List entityStore = new();
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ try
+ {
+ // Run the sample operations
+ app.Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ if (ex.InnerException != null)
+ {
+ Console.WriteLine("Inner exception: {0}", ex.InnerException.Message);
+ }
+ }
+ finally
+ {
+ // Cleanup
+ Console.WriteLine();
+ Console.WriteLine("Press any key to undo environment data changes.");
+ Console.ReadKey();
+
+ app.Cleanup(serviceClient);
+ serviceClient.Dispose();
+
+ Console.WriteLine("Program complete. Press any key to exit.");
+ Console.ReadKey();
+ }
+ }
+
+ ///
+ /// Main sample execution method
+ ///
+ private void Run(ServiceClient serviceClient)
+ {
+ Console.WriteLine("=== Early-bound Entity Operations Sample (Converted to Late-bound) ===");
+ Console.WriteLine();
+
+ // Setup: Create prerequisite records
+ Guid parentAccountId = Setup(serviceClient);
+
+ // Demonstrate: Perform CRUD operations with various attribute types
+ Demonstrate(serviceClient, parentAccountId);
+ }
+
+ ///
+ /// Creates prerequisite records for the sample
+ ///
+ private Guid Setup(ServiceClient serviceClient)
+ {
+ Console.WriteLine("--- Setup ---");
+
+ // Create a parent account for demonstrating EntityReference
+ var parentAccount = new Entity("account");
+ parentAccount["name"] = "Sample Parent Account";
+
+ Guid parentAccountId = serviceClient.Create(parentAccount);
+ Console.WriteLine("Created parent account with ID: {0}", parentAccountId);
+
+ // Store for cleanup
+ entityStore.Add(new EntityReference("account", parentAccountId));
+
+ Console.WriteLine();
+ return parentAccountId;
+ }
+
+ ///
+ /// Demonstrates Create, Retrieve, Update operations with various attribute types
+ ///
+ private void Demonstrate(ServiceClient serviceClient, Guid parentAccountId)
+ {
+ Console.WriteLine("--- Demonstrate ---");
+
+ // CREATE: Instantiate an account object.
+ // See the Entity Metadata documentation to determine
+ // which attributes must be set for each entity.
+ var account = new Entity("account");
+ account["name"] = "Fourth Coffee";
+
+ // Create an account record named Fourth Coffee.
+ Guid accountId = serviceClient.Create(account);
+ Console.WriteLine("Created account 'Fourth Coffee' with ID: {0}", accountId);
+ Console.WriteLine();
+
+ // Store for cleanup
+ entityStore.Add(new EntityReference("account", accountId));
+
+ // RETRIEVE: Retrieve the account containing several of its attributes.
+ var cols = new ColumnSet(
+ new string[] { "name", "address1_postalcode", "lastusedincampaign", "versionnumber" });
+
+ var retrievedAccount = serviceClient.Retrieve("account", accountId, cols);
+ Console.WriteLine("Retrieved account:");
+ Console.WriteLine(" Name: {0}", retrievedAccount.GetAttributeValue("name"));
+
+ // Retrieve version number of the account. Shows BigInt attribute usage.
+ long? versionNumber = retrievedAccount.GetAttributeValue("versionnumber");
+ if (versionNumber != null)
+ {
+ Console.WriteLine(" Version #: {0}", versionNumber);
+ }
+ Console.WriteLine();
+
+ // UPDATE: Update the account with various attribute types
+ Console.WriteLine("Updating account with various attribute types...");
+
+ // Update the postal code attribute (string type)
+ retrievedAccount["address1_postalcode"] = "98052";
+ Console.WriteLine(" Set address1_postalcode to '98052'");
+
+ // The address 2 postal code was set accidentally, so set it to null.
+ retrievedAccount["address2_postalcode"] = null;
+ Console.WriteLine(" Set address2_postalcode to null");
+
+ // Shows usage of option set (picklist) enumerations.
+ // In early-bound code, these used generated enum values.
+ // In late-bound code, we use OptionSetValue with integer values.
+
+ // address1_addresstypecode: 1 = Primary (originally AccountAddress1_AddressTypeCode.Primary)
+ retrievedAccount["address1_addresstypecode"] = new OptionSetValue(1);
+ Console.WriteLine(" Set address1_addresstypecode to 1 (Primary)");
+
+ // address1_shippingmethodcode: 5 = DHL (originally AccountAddress1_ShippingMethodCode.DHL)
+ retrievedAccount["address1_shippingmethodcode"] = new OptionSetValue(5);
+ Console.WriteLine(" Set address1_shippingmethodcode to 5 (DHL)");
+
+ // industrycode: 1 = Agriculture and Non-petrol Natural Resource Extraction
+ // (originally AccountIndustryCode.AgricultureandNonpetrolNaturalResourceExtraction)
+ retrievedAccount["industrycode"] = new OptionSetValue(1);
+ Console.WriteLine(" Set industrycode to 1 (Agriculture and Non-petrol Natural Resource Extraction)");
+
+ // Shows use of a Money value.
+ retrievedAccount["revenue"] = new Money(5000000);
+ Console.WriteLine(" Set revenue to $5,000,000");
+
+ // Shows use of a Boolean value.
+ retrievedAccount["creditonhold"] = false;
+ Console.WriteLine(" Set creditonhold to false");
+
+ // Shows use of EntityReference.
+ retrievedAccount["parentaccountid"] = new EntityReference("account", parentAccountId);
+ Console.WriteLine(" Set parentaccountid to reference parent account");
+
+ // Shows use of Memo attribute (multi-line text).
+ retrievedAccount["description"] = "Account for Fourth Coffee.";
+ Console.WriteLine(" Set description memo field");
+
+ // Update the account record with all changes.
+ serviceClient.Update(retrievedAccount);
+ Console.WriteLine();
+ Console.WriteLine("Account updated successfully.");
+ Console.WriteLine();
+
+ // Verify the updates
+ Console.WriteLine("Verifying updates...");
+ var verifyColumns = new ColumnSet(
+ "name",
+ "address1_postalcode",
+ "address2_postalcode",
+ "address1_addresstypecode",
+ "address1_shippingmethodcode",
+ "industrycode",
+ "revenue",
+ "creditonhold",
+ "parentaccountid",
+ "description"
+ );
+
+ var updatedAccount = serviceClient.Retrieve("account", accountId, verifyColumns);
+
+ Console.WriteLine("Updated account values:");
+ Console.WriteLine(" Name: {0}", updatedAccount.GetAttributeValue("name"));
+ Console.WriteLine(" Postal Code 1: {0}", updatedAccount.GetAttributeValue("address1_postalcode"));
+ Console.WriteLine(" Postal Code 2: {0}",
+ updatedAccount.Contains("address2_postalcode")
+ ? updatedAccount["address2_postalcode"]
+ : "(null)");
+ Console.WriteLine(" Address Type Code: {0}",
+ updatedAccount.GetAttributeValue("address1_addresstypecode")?.Value ?? 0);
+ Console.WriteLine(" Shipping Method Code: {0}",
+ updatedAccount.GetAttributeValue("address1_shippingmethodcode")?.Value ?? 0);
+ Console.WriteLine(" Industry Code: {0}",
+ updatedAccount.GetAttributeValue("industrycode")?.Value ?? 0);
+ Console.WriteLine(" Revenue: ${0:N0}", updatedAccount.GetAttributeValue("revenue")?.Value ?? 0);
+ Console.WriteLine(" Credit On Hold: {0}", updatedAccount.GetAttributeValue("creditonhold"));
+ Console.WriteLine(" Parent Account ID: {0}",
+ updatedAccount.GetAttributeValue("parentaccountid")?.Id ?? Guid.Empty);
+ Console.WriteLine(" Description: {0}", updatedAccount.GetAttributeValue("description"));
+ Console.WriteLine();
+ }
+
+ ///
+ /// Deletes all records created by this sample
+ ///
+ private void Cleanup(ServiceClient serviceClient)
+ {
+ Console.WriteLine();
+ Console.WriteLine("--- Cleanup ---");
+
+ // Delete in reverse order to handle parent-child relationships
+ // (delete child account before parent account)
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ var entityRef = entityStore[i];
+ serviceClient.Delete(entityRef.LogicalName, entityRef.Id);
+ Console.WriteLine("Deleted {0} with ID: {1}",
+ entityRef.LogicalName, entityRef.Id);
+ }
+
+ Console.WriteLine("Cleanup completed.");
+ }
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/EarlyBoundEntityOperations/README.md b/dataverse/orgsvc/CSharp-NETCore/CRUD/EarlyBoundEntityOperations/README.md
new file mode 100644
index 00000000..16985475
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/EarlyBoundEntityOperations/README.md
@@ -0,0 +1,107 @@
+# Early-bound Entity Operations (Converted to Late-bound)
+
+This sample demonstrates Create, Retrieve, Update, and Delete (CRUD) operations on account entities using various attribute types. Originally an early-bound sample using strongly-typed entity classes, this has been migrated to use late-bound Entity syntax for consistency with modern .NET patterns.
+
+## What this sample does
+
+This sample demonstrates working with multiple Dataverse attribute types including:
+
+- **String attributes** - Basic text fields like name and postal codes
+- **BigInt attributes** - Version numbers for concurrency tracking
+- **OptionSet attributes** - Picklist values (address type, shipping method, industry code)
+- **Money attributes** - Currency values with precision
+- **Boolean attributes** - True/false flags
+- **EntityReference attributes** - Lookups to related records
+- **Memo attributes** - Multi-line text fields
+
+The sample uses these common IOrganizationService methods:
+
+- [IOrganizationService.Create](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.create)
+- [IOrganizationService.Retrieve](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.retrieve)
+- [IOrganizationService.Update](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.update)
+- [IOrganizationService.Delete](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.delete)
+
+## How to run this sample
+
+1. Navigate to the CRUD folder and ensure `appsettings.json` is configured with your environment connection string
+2. Navigate to this sample's folder:
+ ```bash
+ cd dataverse/orgsvc/CSharp-NETCore/CRUD/EarlyBoundEntityOperations
+ ```
+3. Build and run the sample:
+ ```bash
+ dotnet run
+ ```
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for more information.
+
+## How this sample works
+
+### Setup
+
+1. Creates a parent account record that will be referenced by the main account
+
+### Demonstrate
+
+1. **Create**: Instantiates and creates an account entity named "Fourth Coffee"
+2. **Retrieve**: Retrieves the account with specific columns including the version number (BigInt)
+3. **Update**: Updates multiple attributes demonstrating different data types:
+ - Sets postal code (string)
+ - Sets another postal code to null
+ - Sets address type code to 1 (Primary) using OptionSetValue
+ - Sets shipping method code to 5 (DHL) using OptionSetValue
+ - Sets industry code to 1 (Agriculture) using OptionSetValue
+ - Sets revenue to $5,000,000 using Money
+ - Sets credit on hold to false (Boolean)
+ - Sets parent account reference using EntityReference
+ - Sets description using memo field
+4. **Verify**: Retrieves the account again to confirm all updates were applied
+
+### Cleanup
+
+Deletes all created records in reverse order to handle parent-child relationships properly.
+
+## Key concepts
+
+### Early-bound vs Late-bound
+
+This sample was originally written using **early-bound** entity classes generated by the Code Generation Tool (CrmSvcUtil). Early-bound classes provide:
+- Strong typing at compile time
+- IntelliSense support for attributes
+- Type safety
+
+The migrated version uses **late-bound** Entity class with string-based attribute access:
+- No code generation required
+- More flexible for dynamic scenarios
+- Consistent with modern sample patterns
+
+### OptionSet values
+
+In early-bound code, OptionSet enumerations are generated with descriptive names:
+```csharp
+// Early-bound (original)
+account.Address1_ShippingMethodCode = new OptionSetValue((int)AccountAddress1_ShippingMethodCode.DHL);
+```
+
+In late-bound code, integer values are used directly:
+```csharp
+// Late-bound (migrated)
+account["address1_shippingmethodcode"] = new OptionSetValue(5); // 5 = DHL
+```
+
+### Attribute types
+
+The sample demonstrates proper usage of Dataverse special attribute types:
+- **Money**: `new Money(5000000)` - Includes currency precision
+- **OptionSetValue**: `new OptionSetValue(1)` - Wraps integer picklist values
+- **EntityReference**: `new EntityReference("account", parentAccountId)` - References related records
+- **Null values**: Setting attributes to `null` removes their value in Dataverse
+
+## See also
+
+- [Entity operations using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations)
+- [Create entities using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-create)
+- [Retrieve an entity using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-retrieve)
+- [Update and delete entities using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-update-delete)
+- [Use the early bound entity classes](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/early-bound-programming)
+- [Late-bound and early-bound programming](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/early-bound-programming#late-bound-and-early-bound-programming-using-the-sdk-for-net)
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/InitializeRecordFromExisting/InitializeRecordFromExisting.csproj b/dataverse/orgsvc/CSharp-NETCore/CRUD/InitializeRecordFromExisting/InitializeRecordFromExisting.csproj
new file mode 100644
index 00000000..55875542
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/InitializeRecordFromExisting/InitializeRecordFromExisting.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ PowerPlatform_Dataverse_CodeSamples
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/InitializeRecordFromExisting/Program.cs b/dataverse/orgsvc/CSharp-NETCore/CRUD/InitializeRecordFromExisting/Program.cs
new file mode 100644
index 00000000..c073a444
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/InitializeRecordFromExisting/Program.cs
@@ -0,0 +1,242 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+
+namespace PowerPlatform_Dataverse_CodeSamples
+{
+ ///
+ /// Demonstrates using InitializeFromRequest to create new records
+ /// initialized from existing records.
+ ///
+ ///
+ /// This sample shows how to:
+ /// 1. Create an Account and initialize a new Account from it
+ /// 2. Create a Lead and initialize an Opportunity from it
+ ///
+ class Program
+ {
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ // Entity name and reference collection to track created entities.
+ Dictionary entityStore;
+
+ // Pre-create any table rows that the Run() method requires.
+ Setup(serviceClient, out entityStore);
+
+ // Execute the main logic of this program.
+ Run(serviceClient, entityStore);
+
+ // Pause program execution before resource cleanup.
+ Console.WriteLine("\nPress any key to undo environment data changes.");
+ Console.ReadKey();
+
+ // In Dataverse, delete any created table rows and then dispose the service connection.
+ Cleanup(serviceClient, entityStore);
+ serviceClient.Dispose();
+ }
+
+ ///
+ /// Executes the code being demonstrated by this program.
+ ///
+ /// Authenticated web service connection.
+ /// Entity name and reference collection.
+ static void Run(IOrganizationService service,
+ Dictionary entityStore)
+ {
+ Console.WriteLine("\nInitializing new Account from the initial Account...");
+
+ // Create the request object for initializing an Account from an existing Account
+ var initializeAccount = new InitializeFromRequest
+ {
+ // Set the target entity type
+ TargetEntityName = "account",
+
+ // Create the EntityMoniker from the existing account
+ EntityMoniker = entityStore["initialAccount"]
+ };
+
+ // Execute the request
+ InitializeFromResponse initializedAccount =
+ (InitializeFromResponse)service.Execute(initializeAccount);
+
+ if (initializedAccount.Entity != null)
+ {
+ Console.WriteLine(" New Account initialized successfully");
+ Console.WriteLine($" Initialized account name: {initializedAccount.Entity["name"]}");
+ }
+
+ Console.WriteLine("\nInitializing an Opportunity from the initial Lead...");
+
+ // Create the request object for initializing an Opportunity from a Lead
+ var initializeLead = new InitializeFromRequest
+ {
+ // Set the target entity type
+ TargetEntityName = "opportunity",
+
+ // Create the EntityMoniker from the existing lead
+ EntityMoniker = entityStore["initialLead"]
+ };
+
+ // Execute the request
+ InitializeFromResponse initializedOpportunity =
+ (InitializeFromResponse)service.Execute(initializeLead);
+
+ if (initializedOpportunity.Entity != null &&
+ initializedOpportunity.Entity.LogicalName == "opportunity")
+ {
+ Console.WriteLine(" New Opportunity initialized successfully");
+ Console.WriteLine($" Opportunity name: {initializedOpportunity.Entity["name"]}");
+ }
+ }
+
+ ///
+ /// Creates any pre-existing entity records required by the Run() method.
+ ///
+ /// Authenticated web service connection.
+ /// Entity name and reference collection.
+ static void Setup(IOrganizationService service,
+ out Dictionary entityStore)
+ {
+ // Used to track any entities created by this program.
+ entityStore = new Dictionary();
+
+ Console.WriteLine("Creating sample data...");
+
+ // Create an initial Account
+ Entity initialAccount = new("account")
+ {
+ ["name"] = "Contoso, Ltd"
+ };
+
+ try
+ {
+ initialAccount.Id = service.Create(initialAccount);
+ entityStore.Add("initialAccount",
+ new EntityReference("account", initialAccount.Id));
+ Console.WriteLine($" Created initial Account (Name={initialAccount["name"]})");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Setup(): error creating initial account.\n\t{ex.Message}");
+ throw;
+ }
+
+ // Create an initial Lead
+ Entity initialLead = new("lead")
+ {
+ ["subject"] = "A Sample Lead",
+ ["lastname"] = "Wilcox",
+ ["firstname"] = "Colin"
+ };
+
+ try
+ {
+ initialLead.Id = service.Create(initialLead);
+ entityStore.Add("initialLead",
+ new EntityReference("lead", initialLead.Id));
+ Console.WriteLine($" Created initial Lead (Subject={initialLead["subject"]}, " +
+ $"Name={initialLead["firstname"]} {initialLead["lastname"]})");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Setup(): error creating initial lead.\n\t{ex.Message}");
+ throw;
+ }
+
+ Console.WriteLine("Sample data created successfully.");
+ }
+
+ ///
+ /// Delete any entity records (table rows) created by this program.
+ ///
+ /// Authenticated web service connection.
+ /// Entity name and reference collection.
+ static void Cleanup(ServiceClient service,
+ Dictionary entityStore)
+ {
+ // Do some checking of the passed parameter values.
+ if (service == null || service.IsReady == false)
+ {
+ Console.WriteLine(
+ "Cleanup(): web service connection not available, cleanup aborted.");
+ return;
+ }
+
+ if (entityStore == null)
+ {
+ Console.WriteLine("Cleanup(): entityStore is null, cleanup aborted.");
+ Console.WriteLine("Cleanup(): run Setup() prior to Cleanup().");
+ return;
+ }
+
+ // Collect the keys of entities to be deleted.
+ var keysToDelete = new List(entityStore.Keys);
+ keysToDelete.Reverse();
+
+ Console.WriteLine("\nDeleting sample data...");
+
+ // Delete in Dataverse each entity in the entity store.
+ foreach (var key in keysToDelete)
+ {
+ var entref = entityStore[key];
+ try
+ {
+ service.Delete(entref.LogicalName, entref.Id);
+ entityStore.Remove(key);
+ Console.WriteLine($" Deleted {entref.LogicalName} with ID {entref.Id}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(
+ $"Cleanup(): exception deleting {key}\n\t{ex.Message}");
+ continue;
+ }
+ }
+
+ // Output a list of entities that could not be deleted.
+ if (entityStore.Count > 0)
+ {
+ Console.WriteLine(
+ "Cleanup(): the following entities could not be deleted:");
+
+ foreach (var item in entityStore)
+ {
+ Console.WriteLine($" name={item.Key}, " +
+ $"logical name={item.Value.LogicalName}, ID={item.Value.Id}");
+ }
+ }
+ else
+ {
+ Console.WriteLine("Sample data deleted successfully.");
+ }
+ }
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/InitializeRecordFromExisting/README.md b/dataverse/orgsvc/CSharp-NETCore/CRUD/InitializeRecordFromExisting/README.md
new file mode 100644
index 00000000..dec67bd2
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/InitializeRecordFromExisting/README.md
@@ -0,0 +1,70 @@
+# Initialize a record from existing record
+
+This sample shows how to use the [InitializeFromRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.initializefromrequest) message to create new records initialized from existing records.
+
+## How to run this sample
+
+1. Clone or download the [PowerApps-Samples](https://github.com/microsoft/PowerApps-Samples) repository.
+2. Navigate to the `dataverse/orgsvc/CSharp-NETCore/CRUD/` folder.
+3. Edit the `appsettings.json` file to set your Dataverse connection string.
+4. Build and run the sample:
+
+```bash
+cd InitializeRecordFromExisting
+dotnet build
+dotnet run
+```
+
+## What this sample does
+
+The `InitializeFromRequest` message is used in scenarios where you need to create a new record initialized with values from an existing record. This is commonly used when:
+
+- Creating a new account based on an existing account template
+- Converting a lead to an opportunity while preserving relevant data
+- Duplicating records with appropriate field mappings
+
+This sample demonstrates two use cases:
+
+1. **Account to Account**: Initializing a new account from an existing account
+2. **Lead to Opportunity**: Initializing an opportunity from a lead (common sales scenario)
+
+## How this sample works
+
+The sample follows the Setup/Run/Cleanup pattern common to modern .NET 6+ samples:
+
+### Setup
+
+1. Creates an initial Account record named "Contoso, Ltd"
+2. Creates an initial Lead record with subject "A Sample Lead" for contact "Colin Wilcox"
+3. Stores references to these entities in the entityStore dictionary for later cleanup
+
+### Demonstrate (Run)
+
+1. **Initialize Account from Account**:
+ - Creates an `InitializeFromRequest` with `TargetEntityName = "account"`
+ - Sets the `EntityMoniker` to reference the existing account
+ - Executes the request to get an initialized account entity
+ - The initialized account inherits mapped fields from the source account
+
+2. **Initialize Opportunity from Lead**:
+ - Creates an `InitializeFromRequest` with `TargetEntityName = "opportunity"`
+ - Sets the `EntityMoniker` to reference the existing lead
+ - Executes the request to get an initialized opportunity entity
+ - The initialized opportunity inherits relevant fields from the lead (like contact information)
+
+### Cleanup
+
+Prompts the user before deleting all created records (account and lead) from Dataverse.
+
+## Key concepts
+
+- **InitializeFromRequest**: Message that initializes a new entity from an existing entity based on entity mapping configuration
+- **EntityMoniker**: Reference to the source entity from which to initialize
+- **TargetEntityName**: The logical name of the entity type to create
+- **Entity Mappings**: Dataverse uses predefined entity mappings to determine which fields to copy from source to target
+
+## See also
+
+- [InitializeFromRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.initializefromrequest)
+- [Entity mapping and attribute mapping](https://learn.microsoft.com/power-apps/developer/data-platform/customize-entity-attribute-mappings)
+- [Lead to opportunity conversion](https://learn.microsoft.com/dynamics365/sales/qualify-lead-convert-opportunity-sales)
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/InsertRecordUsingUpsert.csproj b/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/InsertRecordUsingUpsert.csproj
new file mode 100644
index 00000000..c985a222
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/InsertRecordUsingUpsert.csproj
@@ -0,0 +1,27 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/Program.cs b/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/Program.cs
new file mode 100644
index 00000000..2f33aaff
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/Program.cs
@@ -0,0 +1,362 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Metadata.Query;
+using Microsoft.Xrm.Sdk.Query;
+using System.Xml;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates using UpsertRequest to insert or update records based on alternate keys
+ ///
+ ///
+ /// This sample shows how to use UpsertRequest for insert-or-update operations
+ /// with alternate keys. The sample processes XML files containing product data
+ /// and uses the product code as an alternate key to either create new records
+ /// or update existing ones.
+ ///
+ /// IMPORTANT: This sample requires the UpsertSample managed solution to be installed.
+ /// The solution creates a sample_product table with a sample_productcode alternate key.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid? asyncJobId = null;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("=== Setup ===");
+ Console.WriteLine();
+
+ // Note: This sample requires the UpsertSample managed solution to be installed.
+ // The solution creates:
+ // - sample_product table
+ // - sample_productcode alternate key on the Code field
+ //
+ // The solution file should be available as UpsertSample_1_0_0_0_managed.zip
+ //
+ // This sample assumes the solution is already installed.
+ // If not installed, you will see errors when trying to upsert records.
+
+ Console.WriteLine("Verifying alternate key is active...");
+
+ if (!VerifyProductCodeKeyIsActive(service))
+ {
+ throw new Exception(
+ "The sample_productcode alternate key is not active. " +
+ "Please ensure the UpsertSample managed solution is installed " +
+ "and the alternate key indexes are fully built.");
+ }
+
+ Console.WriteLine("Alternate key is active and ready.");
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("=== Demonstrate UpsertRequest ===");
+ Console.WriteLine();
+
+ // First pass: Process newsampleproduct.xml to create 13 new product records
+ Console.WriteLine("Processing newsampleproduct.xml...");
+ ProcessUpsert(service, "newsampleproduct.xml");
+ Console.WriteLine();
+
+ // Second pass: Process updatedsampleproduct.xml to update 6 existing products
+ Console.WriteLine("Processing updatedsampleproduct.xml...");
+ ProcessUpsert(service, "updatedsampleproduct.xml");
+ Console.WriteLine();
+
+ Console.WriteLine("UpsertRequest demonstration complete.");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("=== Cleanup ===");
+ Console.WriteLine();
+
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+
+ // Delete in reverse order
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ try
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error deleting record {entityStore[i].Id}: {ex.Message}");
+ }
+ }
+
+ Console.WriteLine("Records deleted.");
+ }
+ else if (!deleteCreatedRecords)
+ {
+ Console.WriteLine("Skipping record deletion.");
+ }
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ ///
+ /// Verifies that the alternate key index for sample_productcode is active
+ ///
+ private static bool VerifyProductCodeKeyIsActive(ServiceClient service, int iteration = 0)
+ {
+ const int maxIterations = 5;
+
+ if (iteration >= maxIterations)
+ {
+ Console.WriteLine("Maximum verification attempts reached.");
+ return false;
+ }
+
+ try
+ {
+ // Query metadata for the sample_product entity to check key status
+ var entityQuery = new EntityQueryExpression
+ {
+ Criteria = new MetadataFilterExpression(LogicalOperator.And)
+ {
+ Conditions =
+ {
+ new MetadataConditionExpression("LogicalName", MetadataConditionOperator.Equals, "sample_product")
+ }
+ },
+ Properties = new MetadataPropertiesExpression("Keys")
+ };
+
+ var metadataRequest = new RetrieveMetadataChangesRequest { Query = entityQuery };
+ var metadataResponse = (RetrieveMetadataChangesResponse)service.Execute(metadataRequest);
+
+ if (metadataResponse.EntityMetadata.Count == 0)
+ {
+ Console.WriteLine("sample_product entity not found. Ensure UpsertSample solution is installed.");
+ return false;
+ }
+
+ var productEntity = metadataResponse.EntityMetadata[0];
+
+ if (productEntity.Keys == null || productEntity.Keys.Length == 0)
+ {
+ Console.WriteLine("No alternate keys found on sample_product entity.");
+ return false;
+ }
+
+ var productCodeKey = productEntity.Keys.FirstOrDefault(k => k.LogicalName == "sample_productcode");
+
+ if (productCodeKey == null)
+ {
+ Console.WriteLine("sample_productcode alternate key not found.");
+ return false;
+ }
+
+ if (productCodeKey.EntityKeyIndexStatus == EntityKeyIndexStatus.Active)
+ {
+ return true;
+ }
+ else
+ {
+ Console.WriteLine($"Alternate key status: {productCodeKey.EntityKeyIndexStatus}");
+
+ // If there's an async job building the index, wait and retry
+ if (productCodeKey.AsyncJob != null)
+ {
+ asyncJobId = productCodeKey.AsyncJob.Id;
+ Console.WriteLine($"Waiting 30 seconds for index creation (attempt {iteration + 1}/{maxIterations})...");
+ Thread.Sleep(TimeSpan.FromSeconds(30));
+ return VerifyProductCodeKeyIsActive(service, iteration + 1);
+ }
+
+ return false;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error verifying alternate key: {ex.Message}");
+ return false;
+ }
+ }
+
+ ///
+ /// Processes an XML file containing product data and performs upsert operations
+ ///
+ private static void ProcessUpsert(ServiceClient service, string filename)
+ {
+ if (!File.Exists(filename))
+ {
+ Console.WriteLine($"Error: File '{filename}' not found.");
+ return;
+ }
+
+ try
+ {
+ var xmlDoc = new XmlDocument();
+ xmlDoc.Load(filename);
+
+ XmlNodeList? productNodes = xmlDoc.DocumentElement?.SelectNodes("/products/product");
+
+ if (productNodes == null || productNodes.Count == 0)
+ {
+ Console.WriteLine("No product nodes found in XML file.");
+ return;
+ }
+
+ int createCount = 0;
+ int updateCount = 0;
+
+ foreach (XmlNode productNode in productNodes)
+ {
+ string? productCode = productNode.SelectSingleNode("Code")?.InnerText;
+ string? productName = productNode.SelectSingleNode("Name")?.InnerText;
+ string? productCategory = productNode.SelectSingleNode("Category")?.InnerText;
+ string? productMake = productNode.SelectSingleNode("Make")?.InnerText;
+
+ if (string.IsNullOrEmpty(productCode))
+ {
+ Console.WriteLine("Skipping product with missing code.");
+ continue;
+ }
+
+ // Create entity using alternate key constructor
+ // This specifies the alternate key name and value to use for upsert
+ var product = new Entity("sample_product", "sample_productcode", productCode)
+ {
+ ["sample_name"] = productName,
+ ["sample_category"] = productCategory,
+ ["sample_make"] = productMake
+ };
+
+ // Create and execute UpsertRequest
+ var upsertRequest = new UpsertRequest
+ {
+ Target = product
+ };
+
+ try
+ {
+ var upsertResponse = (UpsertResponse)service.Execute(upsertRequest);
+
+ if (upsertResponse.RecordCreated)
+ {
+ createCount++;
+ Console.WriteLine($" Created: {productName} (Code: {productCode})");
+
+ // Track created records for cleanup
+ // Use the Target entity which now contains the Id after upsert
+ if (product.Id != Guid.Empty)
+ {
+ entityStore.Add(new EntityReference("sample_product", product.Id));
+ }
+ }
+ else
+ {
+ updateCount++;
+ Console.WriteLine($" Updated: {productName} (Code: {productCode})");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($" Error upserting {productName}: {ex.Message}");
+ }
+ }
+
+ Console.WriteLine();
+ Console.WriteLine($"Summary: {createCount} created, {updateCount} updated");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error processing XML file: {ex.Message}");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine();
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+
+ if (ex.InnerException != null)
+ {
+ Console.WriteLine("Inner exception:");
+ Console.WriteLine(ex.InnerException.Message);
+ }
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/README.md b/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/README.md
new file mode 100644
index 00000000..6c9ad158
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/README.md
@@ -0,0 +1,139 @@
+# Insert or update a record using Upsert (.NET 6.0)
+
+This sample demonstrates how to insert or update records using the [UpsertRequest](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.messages.upsertrequest) message with alternate keys.
+
+## Key Concepts
+
+- **UpsertRequest**: Performs an insert-or-update operation in a single request
+- **Alternate Keys**: Uses alternate keys (instead of primary GUID) to identify records
+- **Late-bound Entity**: Creates entities without requiring generated early-bound classes
+- **XML Processing**: Reads and processes product data from XML files
+
+## How to run this sample
+
+1. Download or clone this repository
+2. Open the `InsertRecordUsingUpsert.csproj` file in Visual Studio 2022 or later
+3. Update the connection string in `../appsettings.json` with your Dataverse environment URL and credentials
+4. Press F5 to run the sample
+
+Alternatively, use the .NET CLI:
+
+```bash
+cd dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert
+dotnet build
+dotnet run
+```
+
+## Prerequisites
+
+This sample requires the **UpsertSample** managed solution to be installed in your environment. The solution creates:
+
+- `sample_product` table
+- `sample_productcode` alternate key on the Code field
+
+The solution file (`UpsertSample_1_0_0_0_managed.zip`) can be found in the legacy sample directory or imported from the original Power Apps documentation.
+
+## What this sample does
+
+The `UpsertRequest` message performs an update if a record with the specified alternate key exists, or creates a new record if it doesn't exist. This eliminates the need to check for record existence before deciding whether to create or update.
+
+## How this sample works
+
+### Setup
+
+1. Verifies the alternate key indexes are active and ready for use
+2. The alternate key creation is asynchronous, so the sample waits if the index is still being built
+
+### Demonstrate
+
+1. **First upsert operation** (`newsampleproduct.xml`):
+ - Processes 13 product records
+ - Since these products don't exist yet, all 13 are created as new records
+ - `UpsertResponse.RecordCreated` returns `true` for each
+
+2. **Second upsert operation** (`updatedsampleproduct.xml`):
+ - Processes 6 product records with the same product codes as before
+ - Since these products already exist (matched by alternate key), they are updated
+ - `UpsertResponse.RecordCreated` returns `false` for each
+ - Notice the "Updated" suffix in the product names
+
+### Key Code Pattern
+
+```csharp
+// Create entity using alternate key constructor
+var product = new Entity("sample_product", "sample_productcode", productCode)
+{
+ ["sample_name"] = productName,
+ ["sample_category"] = productCategory,
+ ["sample_make"] = productMake
+};
+
+// Execute UpsertRequest
+var upsertRequest = new UpsertRequest { Target = product };
+var upsertResponse = (UpsertResponse)service.Execute(upsertRequest);
+
+// Check if record was created or updated
+if (upsertResponse.RecordCreated)
+ Console.WriteLine("New record created!");
+else
+ Console.WriteLine("Existing record updated!");
+```
+
+### Cleanup
+
+Deletes all created product records (if user confirms).
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+=== Setup ===
+
+Verifying alternate key is active...
+Alternate key is active and ready.
+Setup complete.
+
+=== Demonstrate UpsertRequest ===
+
+Processing newsampleproduct.xml...
+ Created: Nam01 (Code: Cam01)
+ Created: Nam02 (Code: Ody01)
+ Created: Nam03 (Code: Civ01)
+ ...
+
+Summary: 13 created, 0 updated
+
+Processing updatedsampleproduct.xml...
+ Updated: Nam01Updated (Code: Cam01)
+ Updated: Nam02Updated (Code: Ody01)
+ Updated: Nam03Updated (Code: Civ01)
+ ...
+
+Summary: 0 created, 6 updated
+
+UpsertRequest demonstration complete.
+
+=== Cleanup ===
+
+Deleting 13 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## Key Features
+
+- **Modern .NET 6.0** implementation using `Microsoft.PowerPlatform.Dataverse.Client`
+- **Setup/Run/Cleanup** pattern for clear code organization
+- **Entity tracking** for proper cleanup
+- **Error handling** with detailed exception messages
+- **Alternate key validation** to ensure indexes are ready before operations
+- **XML processing** to demonstrate real-world data import scenarios
+
+## See Also
+
+- [UpsertRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.messages.upsertrequest)
+- [Use alternate keys](https://learn.microsoft.com/power-apps/developer/data-platform/use-alternate-key-create-record)
+- [Define alternate keys for a table](https://learn.microsoft.com/power-apps/developer/data-platform/define-alternate-keys-entity)
+- [Work with data using code](https://learn.microsoft.com/power-apps/developer/data-platform/work-with-data)
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/newsampleproduct.xml b/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/newsampleproduct.xml
new file mode 100644
index 00000000..3f2a276a
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/newsampleproduct.xml
@@ -0,0 +1,81 @@
+
+
+
+ Cam01
+ Nam01
+ Cat01
+ Widget1
+
+
+ Ody01
+ Nam02
+ Cat02
+ Widget2
+
+
+ Civ01
+ Nam03
+ Cat03
+ Widget2
+
+
+ Rav01
+ Nam04
+ Cat04
+ Widget1
+
+
+ Maz01
+ Nam05
+ Cat05
+ Widget3
+
+
+ Leaf06
+ Nam06
+ Cat06
+ Widget4
+
+
+ Out01
+ Nam07
+ Cat07
+ Widget5
+
+
+ Car01
+ Nam08
+ Cat08
+ Widget6
+
+
+ A301
+ Nam09
+ Cat09
+ Widget7
+
+
+ M401
+ Nam10
+ Cat10
+ Widget8
+
+
+ Max01
+ Nam11
+ Cat11
+ Widget4
+
+
+ Jet01
+ Nam12
+ Cat12
+ Widget9
+
+
+ Alt01
+ Nam13
+ Cat13
+ Widget4
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/updatedsampleproduct.xml b/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/updatedsampleproduct.xml
new file mode 100644
index 00000000..0d1c15ac
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/InsertRecordUsingUpsert/updatedsampleproduct.xml
@@ -0,0 +1,40 @@
+
+
+
+ Cam01
+ Nam01Updated
+ Cat01
+ Widget1
+
+
+ Ody01
+ Nam02Updated
+ Cat02
+ Widget2
+
+
+ Civ01
+ Nam03Updated
+ Cat03
+ Widget2
+
+
+ Rav01
+ Nam04Updated
+ Cat04
+ Widget1
+
+
+ Maz01
+ Nam05Updated
+ Cat05
+ Widget3
+
+
+ Leaf06
+ Nam06
+ Cat06
+ Widget4
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/LateBoundEntityOperations/LateBoundEntityOperations.csproj b/dataverse/orgsvc/CSharp-NETCore/CRUD/LateBoundEntityOperations/LateBoundEntityOperations.csproj
new file mode 100644
index 00000000..56ab4f18
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/LateBoundEntityOperations/LateBoundEntityOperations.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ PowerPlatform_Dataverse_CodeSamples
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/LateBoundEntityOperations/Program.cs b/dataverse/orgsvc/CSharp-NETCore/CRUD/LateBoundEntityOperations/Program.cs
new file mode 100644
index 00000000..a1490d7e
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/LateBoundEntityOperations/Program.cs
@@ -0,0 +1,233 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform_Dataverse_CodeSamples
+{
+ ///
+ /// Demonstrates Create, Retrieve, Update, and Delete operations using late-bound Entity class.
+ ///
+ ///
+ /// Late-bound entities use the generic Entity class with string-based attribute access.
+ /// This approach doesn't require generated early-bound classes and provides flexibility
+ /// when working with dynamic entity structures.
+ ///
+ /// This sample demonstrates:
+ /// - Creating an account using late-bound Entity with string indexer syntax
+ /// - Retrieving entity with specific columns using ColumnSet
+ /// - Updating attributes including Money and Boolean types
+ /// - Setting an attribute to null
+ /// - Deleting the entity
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ ///
+ ///
+ ///
+ ///
+ class Program
+ {
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// List of entity references to delete during cleanup
+ ///
+ private readonly List entityStore = new();
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ try
+ {
+ // Run the sample operations
+ app.Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ if (ex.InnerException != null)
+ {
+ Console.WriteLine("Inner exception: {0}", ex.InnerException.Message);
+ }
+ }
+ finally
+ {
+ // Cleanup
+ Console.WriteLine();
+ Console.WriteLine("Press any key to undo environment data changes.");
+ Console.ReadKey();
+
+ app.Cleanup(serviceClient);
+ serviceClient.Dispose();
+
+ Console.WriteLine("Program complete. Press any key to exit.");
+ Console.ReadKey();
+ }
+ }
+
+ ///
+ /// Main sample execution method demonstrating late-bound entity operations
+ ///
+ private void Run(ServiceClient serviceClient)
+ {
+ Console.WriteLine("=== Late-bound Entity Operations Sample ===");
+ Console.WriteLine();
+
+ // Setup: Create initial account
+ Guid accountId = Setup(serviceClient);
+
+ // Demonstrate: Perform CRUD operations
+ Demonstrate(serviceClient, accountId);
+ }
+
+ ///
+ /// Creates the initial account record for the sample
+ ///
+ private Guid Setup(ServiceClient serviceClient)
+ {
+ Console.WriteLine("--- Setup ---");
+
+ // Instantiate an account object using late-bound Entity class.
+ // The constructor takes the logical name of the entity.
+ var account = new Entity("account");
+
+ // Set the required attributes using string-based indexer.
+ // For account entity, only the name is required.
+ // See the Entity Metadata documentation to determine
+ // which attributes must be set for each entity.
+ account["name"] = "Fourth Coffee";
+
+ // Create an account record named Fourth Coffee.
+ Guid accountId = serviceClient.Create(account);
+
+ Console.WriteLine("Created account '{0}' with ID: {1}",
+ account["name"], accountId);
+
+ // Store the entity reference for cleanup
+ entityStore.Add(new EntityReference("account", accountId));
+
+ Console.WriteLine();
+ return accountId;
+ }
+
+ ///
+ /// Demonstrates Retrieve, Update operations on the account
+ ///
+ private void Demonstrate(ServiceClient serviceClient, Guid accountId)
+ {
+ Console.WriteLine("--- Demonstrate ---");
+
+ // Create a column set to define which attributes should be retrieved.
+ // This is more efficient than retrieving all columns.
+ var attributes = new ColumnSet(new string[] { "name", "ownerid" });
+
+ // Retrieve the account and its name and ownerid attributes.
+ var account = serviceClient.Retrieve("account", accountId, attributes);
+
+ Console.WriteLine("Retrieved account:");
+ Console.WriteLine(" Name: {0}", account["name"]);
+ Console.WriteLine(" Owner ID: {0}", account.GetAttributeValue("ownerid").Id);
+ Console.WriteLine();
+
+ // Update multiple attributes to demonstrate different data types
+ Console.WriteLine("Updating account attributes...");
+
+ // Update the postal code attribute (string type)
+ account["address1_postalcode"] = "98052";
+ Console.WriteLine(" Set address1_postalcode to '98052'");
+
+ // The address 2 postal code was set accidentally, so set it to null.
+ // Setting an attribute to null removes its value in Dataverse.
+ account["address2_postalcode"] = null;
+ Console.WriteLine(" Set address2_postalcode to null");
+
+ // Shows use of Money data type.
+ // Money is a special Dataverse type that includes currency precision.
+ account["revenue"] = new Money(5000000);
+ Console.WriteLine(" Set revenue to $5,000,000");
+
+ // Shows use of boolean data type.
+ account["creditonhold"] = false;
+ Console.WriteLine(" Set creditonhold to false");
+
+ // Update the account with all the changes.
+ serviceClient.Update(account);
+ Console.WriteLine("Account updated successfully.");
+ Console.WriteLine();
+
+ // Retrieve again to verify the updates
+ Console.WriteLine("Verifying updates...");
+ var updatedAccount = serviceClient.Retrieve(
+ "account",
+ accountId,
+ new ColumnSet("name", "address1_postalcode", "address2_postalcode", "revenue", "creditonhold")
+ );
+
+ Console.WriteLine("Updated account values:");
+ Console.WriteLine(" Name: {0}", updatedAccount.GetAttributeValue("name"));
+ Console.WriteLine(" Postal Code 1: {0}", updatedAccount.GetAttributeValue("address1_postalcode"));
+ Console.WriteLine(" Postal Code 2: {0}",
+ updatedAccount.Contains("address2_postalcode")
+ ? updatedAccount["address2_postalcode"]
+ : "(null)");
+ Console.WriteLine(" Revenue: {0}", updatedAccount.GetAttributeValue("revenue")?.Value ?? 0);
+ Console.WriteLine(" Credit On Hold: {0}", updatedAccount.GetAttributeValue("creditonhold"));
+ Console.WriteLine();
+ }
+
+ ///
+ /// Deletes all records created by this sample
+ ///
+ private void Cleanup(ServiceClient serviceClient)
+ {
+ Console.WriteLine();
+ Console.WriteLine("--- Cleanup ---");
+
+ foreach (var entityRef in entityStore)
+ {
+ serviceClient.Delete(entityRef.LogicalName, entityRef.Id);
+ Console.WriteLine("Deleted {0} with ID: {1}",
+ entityRef.LogicalName, entityRef.Id);
+ }
+
+ Console.WriteLine("Cleanup completed.");
+ }
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/LateBoundEntityOperations/README.md b/dataverse/orgsvc/CSharp-NETCore/CRUD/LateBoundEntityOperations/README.md
new file mode 100644
index 00000000..7a0fd800
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/LateBoundEntityOperations/README.md
@@ -0,0 +1,143 @@
+# Late-bound Entity Operations
+
+This sample demonstrates Create, Retrieve, Update, and Delete (CRUD) operations using the late-bound `Entity` class in Microsoft Dataverse.
+
+## What is Late-bound Programming?
+
+Late-bound programming uses the generic `Entity` class with string-based attribute access instead of strongly-typed, generated entity classes (early-bound). This approach provides:
+
+- **Flexibility**: No need to regenerate entity classes when metadata changes
+- **Dynamic scenarios**: Works well with custom entities or dynamic attribute access
+- **Simplicity**: No code generation step required
+
+**Trade-offs**: Late-bound programming lacks compile-time type checking and IntelliSense support that early-bound provides.
+
+## What This Sample Does
+
+This sample demonstrates:
+
+1. **Creating an entity** - Instantiates a late-bound `Entity` object and sets attributes using string indexer syntax
+2. **Retrieving an entity** - Uses `ColumnSet` to specify which attributes to retrieve
+3. **Updating attributes** - Shows how to work with different data types:
+ - String values (`address1_postalcode`)
+ - Null values (`address2_postalcode`)
+ - Money type (`revenue`)
+ - Boolean type (`creditonhold`)
+4. **Deleting an entity** - Removes the created record during cleanup
+
+## How to Run This Sample
+
+1. Clone or download the [PowerApps-Samples](https://github.com/microsoft/PowerApps-Samples) repository
+2. Navigate to the sample directory:
+ ```
+ cd dataverse/orgsvc/CSharp-NETCore/CRUD/LateBoundEntityOperations
+ ```
+3. Update the connection string in `../../appsettings.json` with your Dataverse environment details:
+ ```json
+ {
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+ }
+ ```
+4. Build and run the sample:
+ ```bash
+ dotnet build
+ dotnet run
+ ```
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+=== Late-bound Entity Operations Sample ===
+
+--- Setup ---
+Created account 'Fourth Coffee' with ID:
+
+--- Demonstrate ---
+Retrieved account:
+ Name: Fourth Coffee
+ Owner ID:
+
+Updating account attributes...
+ Set address1_postalcode to '98052'
+ Set address2_postalcode to null
+ Set revenue to $5,000,000
+ Set creditonhold to false
+Account updated successfully.
+
+Verifying updates...
+Updated account values:
+ Name: Fourth Coffee
+ Postal Code 1: 98052
+ Postal Code 2: (null)
+ Revenue: 5000000
+ Credit On Hold: False
+
+Press any key to undo environment data changes.
+
+--- Cleanup ---
+Deleted account with ID:
+Cleanup completed.
+Program complete. Press any key to exit.
+```
+
+## Key Concepts
+
+### Late-bound Entity Creation
+
+```csharp
+// Instantiate using logical name
+var account = new Entity("account");
+
+// Set attributes using string indexer
+account["name"] = "Fourth Coffee";
+account["address1_postalcode"] = "98052";
+
+// Create in Dataverse
+Guid id = serviceClient.Create(account);
+```
+
+### Working with Different Data Types
+
+```csharp
+// String
+account["name"] = "Fourth Coffee";
+
+// Money (special Dataverse type)
+account["revenue"] = new Money(5000000);
+
+// Boolean
+account["creditonhold"] = false;
+
+// Null (removes attribute value)
+account["address2_postalcode"] = null;
+```
+
+### Retrieving with ColumnSet
+
+```csharp
+// Specify only needed columns for efficiency
+var columnSet = new ColumnSet("name", "ownerid", "revenue");
+var account = serviceClient.Retrieve("account", accountId, columnSet);
+
+// Access attributes
+string name = account.GetAttributeValue("name");
+Money revenue = account.GetAttributeValue("revenue");
+```
+
+## See Also
+
+- [Use the Entity class for create, update and delete](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-create)
+- [Retrieve a table row using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-retrieve)
+- [Update and delete table rows using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-update-delete)
+- [Late-bound and early-bound programming using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/early-bound-programming)
+- [Sample: Early-bound entity operations](../EarlyBoundEntityOperations/) - Compare with early-bound approach
+
+## Requirements
+
+- .NET 6.0 SDK or later
+- Access to a Microsoft Dataverse environment
+- Appropriate permissions to create and delete account records
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/OptimisticConcurrency/OptimisticConcurrency.csproj b/dataverse/orgsvc/CSharp-NETCore/CRUD/OptimisticConcurrency/OptimisticConcurrency.csproj
new file mode 100644
index 00000000..6e765802
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/OptimisticConcurrency/OptimisticConcurrency.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ PowerPlatform.Dataverse.CodeSamples
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/OptimisticConcurrency/Program.cs b/dataverse/orgsvc/CSharp-NETCore/CRUD/OptimisticConcurrency/Program.cs
new file mode 100644
index 00000000..504f282a
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/OptimisticConcurrency/Program.cs
@@ -0,0 +1,191 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates using optimistic concurrency with RowVersion for update and delete operations.
+ ///
+ ///
+ /// This sample shows how to use the ConcurrencyBehavior.IfRowVersionMatches option
+ /// to ensure that updates and deletes only succeed when the row version matches.
+ /// Set the appropriate Url and Username values for your test environment
+ /// in the appsettings.json file before running this program.
+ /// You will be prompted in the default browser to enter a password.
+ ///
+ ///
+ class Program
+ {
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Stores entities created by Setup for use in Run and Cleanup.
+ ///
+ List entityStore = new();
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ path ??= "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main()
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ throw new Exception("Failed to connect to Dataverse");
+ }
+
+ // Run the sample
+ app.Setup(serviceClient);
+ app.Run(serviceClient);
+ app.Cleanup(serviceClient);
+
+ serviceClient.Dispose();
+ }
+
+ ///
+ /// Creates sample data required for the demonstration.
+ ///
+ /// The authenticated IOrganizationService instance.
+ void Setup(IOrganizationService service)
+ {
+ Console.WriteLine("\n--Setup--");
+
+ // Create an account record
+ Entity account = new("account")
+ {
+ ["name"] = "Fourth Coffee",
+ ["creditlimit"] = new Money(50000)
+ };
+
+ Guid accountId = service.Create(account);
+ entityStore.Add(new EntityReference("account", accountId));
+
+ Console.WriteLine($"Created account '{account["name"]}' with credit limit of {((Money)account["creditlimit"]).Value}.");
+ }
+
+ ///
+ /// Demonstrates optimistic concurrency using RowVersion.
+ ///
+ /// The authenticated IOrganizationService instance.
+ void Run(IOrganizationService service)
+ {
+ Console.WriteLine("\n--Run--");
+
+ Guid accountId = entityStore[0].Id;
+
+ // Retrieve the account with the current row version
+ Entity account = service.Retrieve(
+ entityName: "account",
+ id: accountId,
+ columnSet: new ColumnSet("name", "creditlimit")
+ );
+
+ Console.WriteLine($"Retrieved account. Row version: {account.RowVersion}");
+
+ // Create an in-memory account object from the retrieved account
+ // Include the RowVersion to enable optimistic concurrency checking
+ Entity updatedAccount = new()
+ {
+ LogicalName = account.LogicalName,
+ Id = account.Id,
+ RowVersion = account.RowVersion
+ };
+
+ // Update just the credit limit
+ updatedAccount["creditlimit"] = new Money(1000000);
+
+ // Create update request with concurrency behavior
+ UpdateRequest updateRequest = new()
+ {
+ Target = updatedAccount,
+ ConcurrencyBehavior = ConcurrencyBehavior.IfRowVersionMatches
+ };
+
+ // Execute the update
+ UpdateResponse updateResponse = (UpdateResponse)service.Execute(updateRequest);
+ Console.WriteLine($"Updated account '{account["name"]}' credit limit to {((Money)updatedAccount["creditlimit"]).Value}.");
+
+ // Retrieve the account again to get the new row version
+ account = service.Retrieve(
+ entityName: "account",
+ id: accountId,
+ columnSet: new ColumnSet()
+ );
+
+ Console.WriteLine($"New row version after update: {account.RowVersion}");
+
+ // Store the row version for cleanup
+ entityStore[0].RowVersion = account.RowVersion;
+ }
+
+ ///
+ /// Deletes sample data created by Setup, demonstrating optimistic concurrency on delete.
+ ///
+ /// The authenticated IOrganizationService instance.
+ void Cleanup(IOrganizationService service)
+ {
+ Console.WriteLine("\n--Cleanup--");
+ Console.WriteLine("Do you want to delete the created records? (y/n) [y]: ");
+ string? answer = Console.ReadLine();
+
+ bool deleteRecords = string.IsNullOrEmpty(answer) ||
+ answer.Equals("y", StringComparison.OrdinalIgnoreCase);
+
+ if (deleteRecords)
+ {
+ // Delete the account record using optimistic concurrency
+ // The delete will only succeed if the row version matches
+ EntityReference accountToDelete = entityStore[0];
+
+ DeleteRequest deleteRequest = new()
+ {
+ Target = accountToDelete,
+ ConcurrencyBehavior = ConcurrencyBehavior.IfRowVersionMatches
+ };
+
+ try
+ {
+ service.Execute(deleteRequest);
+ Console.WriteLine("Account record deleted successfully.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Delete failed: {ex.Message}");
+
+ // If delete with concurrency check failed, try regular delete
+ Console.WriteLine("Attempting regular delete without concurrency check...");
+ service.Delete(accountToDelete.LogicalName, accountToDelete.Id);
+ Console.WriteLine("Account record deleted.");
+ }
+ }
+ else
+ {
+ Console.WriteLine("Cleanup skipped.");
+ }
+ }
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/OptimisticConcurrency/README.md b/dataverse/orgsvc/CSharp-NETCore/CRUD/OptimisticConcurrency/README.md
new file mode 100644
index 00000000..09b57b51
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/OptimisticConcurrency/README.md
@@ -0,0 +1,77 @@
+# Optimistic Concurrency Sample
+
+This sample demonstrates how to use optimistic concurrency with update and delete operations in Dataverse using the RowVersion property.
+
+## What this sample does
+
+This sample shows how to:
+
+1. Create an account record with an initial credit limit
+2. Retrieve the account and capture its RowVersion
+3. Update the account using `ConcurrencyBehavior.IfRowVersionMatches` to ensure the update only succeeds if the RowVersion hasn't changed
+4. Delete the account using optimistic concurrency to ensure the delete only succeeds if the RowVersion matches
+
+## How this sample works
+
+### Setup
+
+1. Creates an account record named "Fourth Coffee" with a credit limit of $50,000
+2. Stores the account reference in the entityStore for use in Run and Cleanup
+
+### Demonstrate
+
+1. Retrieves the account record and displays its current RowVersion
+2. Creates an in-memory Entity object with the same LogicalName, Id, and RowVersion
+3. Updates the credit limit to $1,000,000 using an UpdateRequest with `ConcurrencyBehavior.IfRowVersionMatches`
+4. Retrieves the account again to show the new RowVersion after the update
+5. Stores the updated RowVersion for use in Cleanup
+
+### Cleanup
+
+1. Prompts the user to confirm deletion
+2. Attempts to delete the account using a DeleteRequest with `ConcurrencyBehavior.IfRowVersionMatches`
+3. If the delete fails (e.g., due to concurrent modification), falls back to a regular delete
+
+## Key Concepts
+
+### Optimistic Concurrency
+
+Optimistic concurrency is a technique that allows multiple users to access the same data without locking it. Instead, the system checks whether the data has changed since it was retrieved before allowing an update or delete operation to proceed.
+
+### RowVersion
+
+The `RowVersion` property is a unique identifier that changes every time a record is updated. By including the RowVersion when performing update or delete operations, you can ensure that your operation only succeeds if no one else has modified the record since you retrieved it.
+
+### ConcurrencyBehavior
+
+The `ConcurrencyBehavior` enumeration has three values:
+
+- **Default** (0): Normal behavior - operation proceeds regardless of RowVersion
+- **IfRowVersionMatches** (1): Operation only succeeds if the RowVersion matches
+- **AlwaysOverwrite** (2): Operation always succeeds, overwriting any changes
+
+## How to run this sample
+
+1. Update the connection string in `appsettings.json` with your Dataverse environment details:
+ - Replace `yourorg.crm.dynamics.com` with your organization URL
+ - Replace `youruser@yourdomain.com` with your username
+
+2. Build and run the sample:
+ ```bash
+ cd CSharp-NETCore/CRUD/OptimisticConcurrency
+ dotnet build
+ dotnet run
+ ```
+
+3. You will be prompted to sign in via your default browser
+
+4. The sample will:
+ - Create a test account
+ - Demonstrate optimistic concurrency with updates
+ - Ask if you want to delete the test data
+
+## See also
+
+- [Optimistic concurrency](https://learn.microsoft.com/power-apps/developer/data-platform/optimistic-concurrency)
+- [Use UpdateRequest and DeleteRequest](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-update-delete#check-for-duplicate-records)
+- [Microsoft.Xrm.Sdk.Messages Namespace](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.messages)
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/README.md b/dataverse/orgsvc/CSharp-NETCore/CRUD/README.md
new file mode 100644
index 00000000..9ff8d6ec
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/README.md
@@ -0,0 +1,51 @@
+# CRUD
+
+Samples demonstrating Create, Read, Update, and Delete operations in Dataverse.
+
+These samples show fundamental entity operations including working with late-bound and early-bound entities, handling related records, using upsert operations, optimistic concurrency, and record initialization patterns.
+
+More information: [Entity operations using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations)
+
+## Samples
+
+|Sample folder|Description|Build target|
+|---|---|---|
+|CRUD-Dynamic-Entity|Basic CRUD operations using late-bound entities|.NET 6|
+|CreateUpdateRecordsWithRelatedRecords|Create and update records with related entities|.NET 6|
+|EarlyBoundEntityOperations|CRUD operations using early-bound entity classes|.NET 6|
+|LateBoundEntityOperations|CRUD operations using late-bound Entity class|.NET 6|
+|InitializeRecordFromExisting|Initialize new records from existing records|.NET 6|
+|InsertRecordUsingUpsert|Insert or update records using Upsert|.NET 6|
+|OptimisticConcurrency|Handle concurrent updates with RowVersion|.NET 6|
+
+## Prerequisites
+
+- Visual Studio 2022 or later
+- .NET 6.0 SDK or later
+- Access to a Dataverse environment
+
+## How to run samples
+
+1. Clone the PowerApps-Samples repository
+2. Navigate to `dataverse/orgsvc/CSharp-NETCore/CRUD/`
+3. Open the desired sample folder
+4. Edit the `appsettings.json` file (located in the CRUD folder) with your environment connection details:
+ ```json
+ {
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+ }
+ ```
+5. Build and run the sample:
+ ```bash
+ cd SampleFolder
+ dotnet run
+ ```
+
+## See also
+
+[Entity operations using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations)
+[Create entities using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-create)
+[Retrieve an entity using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-retrieve)
+[Update and delete entities using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-update-delete)
diff --git a/dataverse/orgsvc/CSharp-NETCore/CRUD/appsettings.json b/dataverse/orgsvc/CSharp-NETCore/CRUD/appsettings.json
new file mode 100644
index 00000000..037aca85
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/CRUD/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DetectMultipleDuplicateRecords/DetectMultipleDuplicateRecords.csproj b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DetectMultipleDuplicateRecords/DetectMultipleDuplicateRecords.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DetectMultipleDuplicateRecords/DetectMultipleDuplicateRecords.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DetectMultipleDuplicateRecords/Program.cs b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DetectMultipleDuplicateRecords/Program.cs
new file mode 100644
index 00000000..0df45450
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DetectMultipleDuplicateRecords/Program.cs
@@ -0,0 +1,324 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to detect multiple duplicate records using BulkDetectDuplicatesRequest
+ ///
+ ///
+ /// This sample shows how to:
+ /// - Create duplicate account records
+ /// - Create a duplicate detection rule programmatically
+ /// - Add conditions to the duplicate rule
+ /// - Publish the duplicate rule
+ /// - Use BulkDetectDuplicatesRequest to detect duplicates
+ /// - Wait for the async job to complete
+ /// - Query duplicate records to verify detection
+ ///
+ /// Prerequisites:
+ /// - System Administrator or System Customizer role
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid bulkDetectJobId;
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data including duplicate accounts and a duplicate detection rule
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating duplicate account records...");
+
+ string accountName = "Contoso, Ltd";
+ string websiteUrl = "http://www.contoso.com/";
+
+ // Create duplicate accounts
+ for (int i = 0; i < 2; i++)
+ {
+ var account = new Entity("account")
+ {
+ ["name"] = accountName,
+ ["websiteurl"] = websiteUrl
+ };
+ Guid accountId = service.Create(account);
+ entityStore.Add(new EntityReference("account", accountId));
+ }
+ Console.WriteLine($" Created 2 duplicate accounts (Name={accountName}, Website={websiteUrl})");
+
+ // Create a non-duplicate account
+ string nonDuplicateName = "Contoso Pharmaceuticals";
+ var distinctAccount = new Entity("account")
+ {
+ ["name"] = nonDuplicateName,
+ ["websiteurl"] = websiteUrl
+ };
+ Guid distinctAccountId = service.Create(distinctAccount);
+ entityStore.Add(new EntityReference("account", distinctAccountId));
+ Console.WriteLine($" Created non-duplicate account (Name={nonDuplicateName}, Website={websiteUrl})");
+ Console.WriteLine();
+
+ Console.WriteLine("Creating duplicate detection rule...");
+ // Create a duplicate detection rule
+ var rule = new Entity("duplicaterule")
+ {
+ ["name"] = "Accounts with the same Account name and website url",
+ ["baseentityname"] = "account",
+ ["matchingentityname"] = "account"
+ };
+ Guid ruleId = service.Create(rule);
+ entityStore.Add(new EntityReference("duplicaterule", ruleId));
+ Console.WriteLine($" Rule created: {ruleId}");
+
+ // Create rule conditions
+ var nameCondition = new Entity("duplicaterulecondition")
+ {
+ ["baseattributename"] = "name",
+ ["matchingattributename"] = "name",
+ ["operatorcode"] = new OptionSetValue(0), // Exact match
+ ["regardingobjectid"] = new EntityReference("duplicaterule", ruleId)
+ };
+ Guid nameConditionId = service.Create(nameCondition);
+ entityStore.Add(new EntityReference("duplicaterulecondition", nameConditionId));
+
+ var websiteCondition = new Entity("duplicaterulecondition")
+ {
+ ["baseattributename"] = "websiteurl",
+ ["matchingattributename"] = "websiteurl",
+ ["operatorcode"] = new OptionSetValue(0), // Exact match
+ ["regardingobjectid"] = new EntityReference("duplicaterule", ruleId)
+ };
+ Guid websiteConditionId = service.Create(websiteCondition);
+ entityStore.Add(new EntityReference("duplicaterulecondition", websiteConditionId));
+ Console.WriteLine(" Rule conditions created");
+
+ Console.WriteLine("Publishing duplicate detection rule...");
+ var publishRequest = new PublishDuplicateRuleRequest
+ {
+ DuplicateRuleId = ruleId
+ };
+ var publishResponse = (PublishDuplicateRuleResponse)service.Execute(publishRequest);
+
+ // Wait for the rule to publish
+ Console.WriteLine(" Waiting for rule to publish...");
+ WaitForAsyncJobToFinish(service, publishResponse.JobId, 120);
+ Console.WriteLine(" Rule published successfully");
+ Console.WriteLine();
+ }
+
+ ///
+ /// Demonstrates bulk duplicate detection
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Creating BulkDetectDuplicatesRequest...");
+ var request = new BulkDetectDuplicatesRequest
+ {
+ JobName = "Detect Duplicate Accounts",
+ Query = new QueryExpression("account")
+ {
+ ColumnSet = new ColumnSet(true)
+ },
+ RecurrencePattern = string.Empty,
+ RecurrenceStartTime = DateTime.Now,
+ ToRecipients = Array.Empty(),
+ CCRecipients = Array.Empty()
+ };
+
+ Console.WriteLine("Executing BulkDetectDuplicatesRequest...");
+ var response = (BulkDetectDuplicatesResponse)service.Execute(request);
+ bulkDetectJobId = response.JobId;
+
+ // Track the job for cleanup
+ entityStore.Add(new EntityReference("asyncoperation", bulkDetectJobId));
+
+ Console.WriteLine($" Job ID: {bulkDetectJobId}");
+ Console.WriteLine(" Waiting for duplicate detection job to complete...");
+
+ WaitForAsyncJobToFinish(service, bulkDetectJobId, 240);
+
+ // Query for duplicate records
+ Console.WriteLine("Querying for detected duplicates...");
+ var duplicateQuery = new QueryByAttribute("duplicaterecord")
+ {
+ ColumnSet = new ColumnSet(true)
+ };
+ duplicateQuery.Attributes.Add("asyncoperationid");
+ duplicateQuery.Values.Add(bulkDetectJobId);
+
+ var duplicateResults = service.RetrieveMultiple(duplicateQuery);
+
+ if (duplicateResults.Entities.Count > 0)
+ {
+ Console.WriteLine($" Found {duplicateResults.Entities.Count} duplicate record(s):");
+
+ var duplicateIds = new HashSet();
+ foreach (var duplicate in duplicateResults.Entities)
+ {
+ var baseRecordId = duplicate.GetAttributeValue("baserecordid");
+ if (baseRecordId != null)
+ {
+ duplicateIds.Add(baseRecordId.Id);
+ Console.WriteLine($" Base Record ID: {baseRecordId.Id}");
+ }
+ }
+
+ // Verify that expected duplicates were found
+ var expectedDuplicates = entityStore
+ .Where(e => e.LogicalName == "account")
+ .Take(2) // First 2 accounts are duplicates
+ .Select(e => e.Id)
+ .ToList();
+
+ bool allFound = expectedDuplicates.All(id => duplicateIds.Contains(id));
+ if (allFound)
+ {
+ Console.WriteLine(" All expected duplicate accounts were detected successfully!");
+ }
+ else
+ {
+ Console.WriteLine(" Warning: Not all expected duplicates were detected.");
+ }
+ }
+ else
+ {
+ Console.WriteLine(" No duplicates found.");
+ Console.WriteLine(" Note: Duplicate detection may take time to process.");
+ }
+
+ Console.WriteLine();
+ Console.WriteLine("Bulk duplicate detection complete.");
+ }
+
+ ///
+ /// Cleans up sample data
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+
+ // Delete in reverse order to handle dependencies
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ var entityRef = entityStore[i];
+ try
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($" Warning: Could not delete {entityRef.LogicalName} {entityRef.Id}: {ex.Message}");
+ }
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ ///
+ /// Waits for an async job to complete
+ ///
+ private static void WaitForAsyncJobToFinish(ServiceClient service, Guid jobId, int maxTimeSeconds)
+ {
+ for (int i = 0; i < maxTimeSeconds; i++)
+ {
+ var asyncJob = service.Retrieve("asyncoperation", jobId, new ColumnSet("statecode"));
+
+ var stateCode = asyncJob.GetAttributeValue("statecode");
+
+ // StateCode 3 = Completed
+ if (stateCode != null && stateCode.Value == 3)
+ {
+ return;
+ }
+
+ Thread.Sleep(1000);
+ }
+
+ throw new Exception($"Exceeded maximum time of {maxTimeSeconds} seconds waiting for asynchronous job to complete");
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DetectMultipleDuplicateRecords/README.md b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DetectMultipleDuplicateRecords/README.md
new file mode 100644
index 00000000..b1181a3d
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DetectMultipleDuplicateRecords/README.md
@@ -0,0 +1,112 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates how to detect multiple duplicate records using BulkDetectDuplicatesRequest"
+---
+
+# DetectMultipleDuplicateRecords
+
+Demonstrates how to detect multiple duplicate records using BulkDetectDuplicatesRequest
+
+## What this sample does
+
+This sample shows how to:
+- Create duplicate account records programmatically
+- Create a duplicate detection rule with multiple conditions
+- Publish the duplicate detection rule
+- Use BulkDetectDuplicatesRequest to detect duplicates across multiple records
+- Wait for the asynchronous bulk detection job to complete
+- Query and retrieve the detected duplicate records
+
+The BulkDetectDuplicatesRequest is used to detect duplicates for all records of a specified entity type, running as an asynchronous background job.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates 2 duplicate account records with the same name ("Contoso, Ltd") and website ("http://www.contoso.com/")
+2. Creates 1 non-duplicate account with a different name ("Contoso Pharmaceuticals") but the same website
+3. Creates a duplicate detection rule programmatically that checks for duplicates based on both account name and website URL
+4. Adds two rule conditions:
+ - Exact match on "name" attribute
+ - Exact match on "websiteurl" attribute
+5. Publishes the duplicate detection rule using PublishDuplicateRuleRequest
+6. Waits for the publish operation to complete (async job tracking)
+
+### Run
+
+The main demonstration:
+1. Creates a BulkDetectDuplicatesRequest with:
+ - JobName for identifying the operation
+ - QueryExpression to specify which records to check (all accounts)
+ - Empty recurrence pattern (one-time execution)
+2. Executes the request, which returns a JobId for the async operation
+3. Waits for the bulk detection job to complete (polls asyncoperation table)
+4. Queries the duplicaterecord table to retrieve all detected duplicates
+5. Verifies that the expected duplicate accounts were detected
+6. Displays results including duplicate record IDs
+
+### Cleanup
+
+The cleanup process:
+1. Deletes all created records in reverse order to handle dependencies:
+ - Async operation record
+ - Duplicate rule conditions
+ - Duplicate detection rule
+ - Account records
+2. Handles errors gracefully if deletion fails
+
+## Demonstrates
+
+This sample demonstrates:
+- **BulkDetectDuplicatesRequest/Response**: Initiating bulk duplicate detection
+- **PublishDuplicateRuleRequest/Response**: Publishing duplicate detection rules programmatically
+- **Programmatic rule creation**: Creating duplicate rules and conditions via SDK
+- **Async job tracking**: Polling asyncoperation table for job completion
+- **QueryByAttribute**: Querying duplicaterecord table by asyncoperationid
+- **Multiple rule conditions**: Creating rules with AND logic across multiple attributes
+- **EntityReference tracking**: Managing created entities for cleanup
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating duplicate account records...
+ Created 2 duplicate accounts (Name=Contoso, Ltd, Website=http://www.contoso.com/)
+ Created non-duplicate account (Name=Contoso Pharmaceuticals, Website=http://www.contoso.com/)
+
+Creating duplicate detection rule...
+ Rule created: a1234567-89ab-cdef-0123-456789abcdef
+ Rule conditions created
+Publishing duplicate detection rule...
+ Waiting for rule to publish...
+ Rule published successfully
+
+Creating BulkDetectDuplicatesRequest...
+Executing BulkDetectDuplicatesRequest...
+ Job ID: b2345678-90cd-ef01-2345-6789abcdef01
+ Waiting for duplicate detection job to complete...
+Querying for detected duplicates...
+ Found 2 duplicate record(s):
+ Base Record ID: c3456789-01de-f012-3456-789abcdef012
+ Base Record ID: d4567890-12ef-0123-4567-89abcdef0123
+ All expected duplicate accounts were detected successfully!
+
+Bulk duplicate detection complete.
+Cleaning up...
+Deleting 6 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Detect duplicate data](https://learn.microsoft.com/power-apps/developer/data-platform/detect-duplicate-data)
+[Run duplicate detection](https://learn.microsoft.com/power-apps/developer/data-platform/run-duplicate-detection)
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DuplicateDetection.slnx b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DuplicateDetection.slnx
new file mode 100644
index 00000000..86b63517
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/DuplicateDetection.slnx
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/EnableDuplicateDetection/EnableDuplicateDetection.csproj b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/EnableDuplicateDetection/EnableDuplicateDetection.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/EnableDuplicateDetection/EnableDuplicateDetection.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/EnableDuplicateDetection/Program.cs b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/EnableDuplicateDetection/Program.cs
new file mode 100644
index 00000000..70d48918
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/EnableDuplicateDetection/Program.cs
@@ -0,0 +1,395 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Metadata;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to enable duplicate detection for the organization and entities
+ ///
+ ///
+ /// This sample shows how to:
+ /// - Enable duplicate detection at the organization level
+ /// - Enable duplicate detection for a specific entity (account)
+ /// - Publish duplicate detection rules
+ /// - Create duplicate records
+ /// - Retrieve duplicate records using RetrieveDuplicatesRequest
+ ///
+ /// Prerequisites:
+ /// - System Administrator or System Customizer role
+ /// - At least one duplicate detection rule must exist for the account entity
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up duplicate detection by enabling it and publishing rules
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Enabling duplicate detection...");
+
+ // Enable duplicate detection for the organization
+ EnableDuplicateDetectionForOrg(service);
+
+ // Enable duplicate detection for the account entity
+ EnableDuplicateDetectionForEntity(service, "account");
+
+ // Publish all duplicate rules for the account entity
+ PublishRulesForEntity(service, "account");
+
+ Console.WriteLine("Duplicate detection enabled and rules published.");
+ Console.WriteLine();
+ }
+
+ ///
+ /// Demonstrates creating duplicate records and retrieving them
+ ///
+ private static void Run(ServiceClient service)
+ {
+ // Create duplicate account records
+ Console.WriteLine("Creating duplicate account records...");
+ var account1 = new Entity("account")
+ {
+ ["name"] = "Microsoft"
+ };
+ Guid accountId1 = service.Create(account1);
+ entityStore.Add(new EntityReference("account", accountId1));
+
+ var account2 = new Entity("account")
+ {
+ ["name"] = "Microsoft"
+ };
+ Guid accountId2 = service.Create(account2);
+ entityStore.Add(new EntityReference("account", accountId2));
+
+ Console.WriteLine($"Created duplicate records:");
+ Console.WriteLine($" Account 1: {accountId1}");
+ Console.WriteLine($" Account 2: {accountId2}");
+ Console.WriteLine();
+
+ // Retrieve duplicates
+ Console.WriteLine("Retrieving duplicate records...");
+ var request = new RetrieveDuplicatesRequest
+ {
+ BusinessEntity = new Entity("account") { ["name"] = "Microsoft" },
+ MatchingEntityName = "account",
+ PagingInfo = new PagingInfo { PageNumber = 1, Count = 50 }
+ };
+
+ var response = (RetrieveDuplicatesResponse)service.Execute(request);
+
+ if (response.DuplicateCollection.Entities.Count > 0)
+ {
+ Console.WriteLine($"Found {response.DuplicateCollection.Entities.Count} duplicate(s):");
+ foreach (var duplicate in response.DuplicateCollection.Entities)
+ {
+ string name = duplicate.GetAttributeValue("name");
+ Guid id = duplicate.Id;
+ Console.WriteLine($" {name} (ID: {id})");
+ }
+ }
+ else
+ {
+ Console.WriteLine("No duplicates found.");
+ Console.WriteLine("Note: Duplicate detection may take a moment to process after enabling.");
+ }
+
+ Console.WriteLine();
+ Console.WriteLine("Duplicate detection operations complete.");
+ }
+
+ ///
+ /// Cleans up sample data
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ ///
+ /// Enables duplicate detection for the organization
+ ///
+ private static void EnableDuplicateDetectionForOrg(ServiceClient service)
+ {
+ // Retrieve the organization ID
+ Guid? orgId = RetrieveOrganizationId(service);
+ if (!orgId.HasValue)
+ {
+ Console.WriteLine("Could not retrieve organization ID.");
+ return;
+ }
+
+ Console.WriteLine($"Enabling duplicate detection for organization: {orgId.Value}");
+
+ // Enable duplicate detection for each type
+ var organization = new Entity("organization")
+ {
+ Id = orgId.Value,
+ ["isduplicatedetectionenabled"] = true,
+ ["isduplicatedetectionenabledforimport"] = true,
+ ["isduplicatedetectionenabledforofflinesync"] = true,
+ ["isduplicatedetectionenabledforonlinecreateupdate"] = true
+ };
+
+ service.Update(organization);
+ Console.WriteLine("Organization duplicate detection enabled.");
+ }
+
+ ///
+ /// Enables duplicate detection for a specific entity
+ ///
+ private static void EnableDuplicateDetectionForEntity(ServiceClient service, string entityName)
+ {
+ Console.WriteLine($"Retrieving entity metadata for {entityName}...");
+
+ // Retrieve the entity metadata
+ var retrieveEntityRequest = new RetrieveEntityRequest
+ {
+ RetrieveAsIfPublished = true,
+ LogicalName = entityName
+ };
+
+ var retrieveEntityResponse = (RetrieveEntityResponse)service.Execute(retrieveEntityRequest);
+ var entityMetadata = retrieveEntityResponse.EntityMetadata;
+
+ Console.WriteLine($"Enabling duplicate detection for {entityName}...");
+
+ // Update the duplicate detection flag
+ entityMetadata.IsDuplicateDetectionEnabled = new BooleanManagedProperty(true);
+
+ // Update the entity metadata
+ service.Execute(new UpdateEntityRequest { Entity = entityMetadata });
+
+ Console.WriteLine($"Publishing {entityName} entity...");
+
+ // Publish the entity
+ var publishRequest = new PublishXmlRequest
+ {
+ ParameterXml = $"{entityName}"
+ };
+
+ service.Execute(publishRequest);
+ Console.WriteLine($"Entity {entityName} published.");
+ }
+
+ ///
+ /// Publishes all duplicate rules for an entity and waits for completion
+ ///
+ private static void PublishRulesForEntity(ServiceClient service, string entityName)
+ {
+ Console.WriteLine($"Retrieving duplicate rules for {entityName}...");
+
+ // Retrieve all rules for the entity
+ var query = new QueryByAttribute("duplicaterule")
+ {
+ ColumnSet = new ColumnSet("duplicateruleid"),
+ Attributes = { "matchingentityname" },
+ Values = { entityName }
+ };
+
+ var rules = service.RetrieveMultiple(query);
+
+ if (rules.Entities.Count == 0)
+ {
+ Console.WriteLine($"No duplicate rules found for {entityName}.");
+ return;
+ }
+
+ Console.WriteLine($"Found {rules.Entities.Count} duplicate rule(s). Publishing...");
+
+ var asyncJobIds = new List();
+ foreach (var rule in rules.Entities)
+ {
+ Console.WriteLine($" Publishing duplicate rule: {rule.Id}");
+
+ // Publish each rule and get the job ID since it is async
+ var publishRequest = new PublishDuplicateRuleRequest
+ {
+ DuplicateRuleId = rule.Id
+ };
+
+ var publishResponse = (PublishDuplicateRuleResponse)service.Execute(publishRequest);
+ asyncJobIds.Add(publishResponse.JobId);
+ }
+
+ // Wait until all rules are published
+ WaitForAsyncJobCompletion(service, asyncJobIds);
+ Console.WriteLine("All duplicate rules published successfully.");
+ }
+
+ ///
+ /// Retrieves the organization ID
+ ///
+ private static Guid? RetrieveOrganizationId(ServiceClient service)
+ {
+ var query = new QueryExpression("organization")
+ {
+ ColumnSet = new ColumnSet("organizationid"),
+ PageInfo = new PagingInfo { PageNumber = 1, Count = 1 }
+ };
+
+ var entities = service.RetrieveMultiple(query);
+
+ if (entities != null && entities.Entities.Count > 0)
+ {
+ return entities.Entities[0].Id;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Waits for async jobs to complete
+ ///
+ private static void WaitForAsyncJobCompletion(ServiceClient service, IEnumerable asyncJobIds)
+ {
+ var asyncJobList = new List(asyncJobIds);
+ var columnSet = new ColumnSet("statecode", "asyncoperationid");
+ int retryCount = 100;
+
+ Console.WriteLine("Waiting for async operations to complete...");
+
+ while (asyncJobList.Count > 0 && retryCount > 0)
+ {
+ // Retrieve the async operations based on the IDs
+ var query = new QueryExpression("asyncoperation")
+ {
+ ColumnSet = columnSet,
+ Criteria = new FilterExpression
+ {
+ Conditions =
+ {
+ new ConditionExpression("asyncoperationid",
+ ConditionOperator.In, asyncJobList.ToArray())
+ }
+ }
+ };
+
+ var asyncJobs = service.RetrieveMultiple(query);
+
+ // Check if operations are completed and remove them from the list
+ foreach (var job in asyncJobs.Entities)
+ {
+ var stateCode = job.GetAttributeValue("statecode");
+ var asyncOpId = job.GetAttributeValue("asyncoperationid");
+
+ // StateCode 3 = Completed
+ if (stateCode != null && stateCode.Value == 3)
+ {
+ asyncJobList.Remove(asyncOpId);
+ Console.WriteLine($" Async operation completed: {asyncOpId}");
+ }
+ }
+
+ // If there are still jobs remaining, wait before checking again
+ if (asyncJobList.Count > 0)
+ {
+ Thread.Sleep(2000);
+ }
+
+ retryCount--;
+ }
+
+ if (retryCount == 0 && asyncJobList.Count > 0)
+ {
+ Console.WriteLine("Warning: Some async operations did not complete:");
+ foreach (var jobId in asyncJobList)
+ {
+ Console.WriteLine($" - {jobId}");
+ }
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/EnableDuplicateDetection/README.md b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/EnableDuplicateDetection/README.md
new file mode 100644
index 00000000..8a2b45a6
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/EnableDuplicateDetection/README.md
@@ -0,0 +1,126 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates how to enable duplicate detection for the organization and entities"
+---
+
+# EnableDuplicateDetection
+
+Demonstrates how to enable duplicate detection for the organization and entities
+
+## What this sample does
+
+This sample shows how to:
+- Enable duplicate detection at the organization level
+- Enable duplicate detection for a specific entity (account)
+- Publish all duplicate detection rules for an entity
+- Create duplicate records
+- Retrieve duplicate records using RetrieveDuplicatesRequest
+
+Duplicate detection must be enabled at both the organization level and the entity level before it can detect duplicates. This sample demonstrates the complete setup process.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. **Enables organization-level duplicate detection**:
+ - Retrieves the organization record
+ - Sets four duplicate detection flags:
+ - isduplicatedetectionenabled (general duplicate detection)
+ - isduplicatedetectionenabledforimport (during import)
+ - isduplicatedetectionenabledforofflinesync (during offline sync)
+ - isduplicatedetectionenabledforonlinecreateupdate (during online CRUD)
+
+2. **Enables entity-level duplicate detection**:
+ - Retrieves entity metadata for the account entity using RetrieveEntityRequest
+ - Sets IsDuplicateDetectionEnabled managed property to true
+ - Updates the entity metadata using UpdateEntityRequest
+ - Publishes the entity changes using PublishXmlRequest
+
+3. **Publishes existing duplicate rules**:
+ - Queries for all duplicate rules for the account entity
+ - Publishes each rule using PublishDuplicateRuleRequest
+ - Waits for all async publishing jobs to complete
+ - Polls asyncoperation table to track job status
+
+### Run
+
+The main demonstration:
+1. Creates two duplicate account records with the same name ("Microsoft")
+2. Uses RetrieveDuplicatesRequest to find duplicates:
+ - Creates a BusinessEntity with the name to check
+ - Specifies the matching entity name
+ - Provides paging information
+3. Displays all found duplicate records with their names and IDs
+
+**Note**: Duplicate detection may take a moment to process after enabling, so immediate results may not appear.
+
+### Cleanup
+
+The cleanup process:
+1. Restores original organization auditing settings if they were changed
+2. Deletes all created account records
+
+## Demonstrates
+
+This sample demonstrates:
+- **Organization entity updates**: Modifying organization-level settings
+- **RetrieveEntityRequest/Response**: Getting entity metadata
+- **UpdateEntityRequest**: Modifying entity metadata properties
+- **BooleanManagedProperty**: Setting managed metadata properties
+- **PublishXmlRequest**: Publishing entity metadata changes
+- **PublishDuplicateRuleRequest/Response**: Publishing duplicate detection rules
+- **Async job tracking**: Waiting for multiple async operations to complete
+- **RetrieveDuplicatesRequest/Response**: Retrieving duplicate records
+- **QueryByAttribute**: Finding duplicate rules by entity name
+- **PagingInfo**: Controlling result set size
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Enabling duplicate detection...
+Enabling duplicate detection for organization: org-id-here
+Organization duplicate detection enabled.
+Retrieving entity metadata for account...
+Enabling duplicate detection for account...
+Publishing account entity...
+Entity account published.
+Retrieving duplicate rules for account...
+Found 2 duplicate rule(s). Publishing...
+ Publishing duplicate rule: rule-id-1
+ Publishing duplicate rule: rule-id-2
+Waiting for async operations to complete...
+ Async operation completed: job-id-1
+ Async operation completed: job-id-2
+All duplicate rules published successfully.
+Duplicate detection enabled and rules published.
+
+Creating duplicate account records...
+Created duplicate records:
+ Account 1: account-id-1
+ Account 2: account-id-2
+
+Retrieving duplicate records...
+Found 2 duplicate(s):
+ Microsoft (ID: account-id-1)
+ Microsoft (ID: account-id-2)
+
+Duplicate detection operations complete.
+Cleaning up...
+Deleting 2 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Detect duplicate data](https://learn.microsoft.com/power-apps/developer/data-platform/detect-duplicate-data)
+[Enable and disable duplicate detection](https://learn.microsoft.com/power-apps/developer/data-platform/enable-disable-duplicate-detection)
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/README.md b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/README.md
new file mode 100644
index 00000000..efc6ca83
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/README.md
@@ -0,0 +1,57 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates duplicate detection capabilities including enabling duplicate detection rules and detecting duplicate records"
+---
+
+# DuplicateDetection
+Demonstrates duplicate detection capabilities including enabling duplicate detection rules and detecting duplicate records
+
+More information: [DuplicateDetection](https://learn.microsoft.com/power-apps/developer/data-platform/detect-duplicate-data)
+
+## Samples
+
+This folder contains the following samples:
+
+|Sample folder|Description|Build target|
+|---|---|---|
+|[EnableDuplicateDetection](EnableDuplicateDetection)|Demonstrates how to enable duplicate detection at the organization and entity levels, publish rules, and retrieve duplicate records|.NET 6|
+|[DetectMultipleDuplicateRecords](DetectMultipleDuplicateRecords)|Demonstrates how to use BulkDetectDuplicatesRequest to detect duplicates across multiple records asynchronously|.NET 6|
+|[UseDuplicatedetectionforCRUD](UseDuplicatedetectionforCRUD)|Demonstrates using the SuppressDuplicateDetection parameter to control duplicate detection during Create and Update operations|.NET 6|
+
+## Prerequisites
+
+- Microsoft Visual Studio 2022
+- Access to Dataverse with appropriate privileges for the operations demonstrated
+
+## How to run samples
+
+1. Clone or download the PowerApps-Samples repository
+2. Navigate to `/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/`
+3. Open `DuplicateDetection.sln` in Visual Studio 2022
+4. Edit the `appsettings.json` file in the category folder root with your Dataverse environment details:
+ - Set `Url` to your Dataverse environment URL
+ - Set `Username` to your user account
+5. Build and run the desired sample project
+
+## appsettings.json
+
+Each sample in this category references the shared `appsettings.json` file in the category root folder. The connection string format is:
+
+```json
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
+```
+
+You can also set the `DATAVERSE_APPSETTINGS` environment variable to point to a custom appsettings.json file location if you prefer to keep your connection string outside the repository.
+
+## See also
+
+[SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/overview)
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/UseDuplicatedetectionforCRUD/Program.cs b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/UseDuplicatedetectionforCRUD/Program.cs
new file mode 100644
index 00000000..f6583fd1
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/UseDuplicatedetectionforCRUD/Program.cs
@@ -0,0 +1,283 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates using duplicate detection with Create and Update operations
+ ///
+ ///
+ /// This sample shows how to:
+ /// - Create and publish a duplicate detection rule
+ /// - Use SuppressDuplicateDetection parameter with CreateRequest
+ /// - Use SuppressDuplicateDetection parameter with UpdateRequest
+ /// - Control whether duplicate detection fires during CRUD operations
+ ///
+ /// Prerequisites:
+ /// - System Administrator or System Customizer role
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid ruleId;
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data including an account and duplicate detection rule
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating initial account record...");
+
+ // Create an account record named Fourth Coffee
+ var account = new Entity("account")
+ {
+ ["name"] = "Fourth Coffee",
+ ["accountnumber"] = "ACC005"
+ };
+ Guid accountId = service.Create(account);
+ entityStore.Add(new EntityReference("account", accountId));
+ Console.WriteLine($" Created account: {account["name"]} ({account["accountnumber"]})");
+ Console.WriteLine();
+
+ Console.WriteLine("Creating duplicate detection rule...");
+
+ // Create a duplicate detection rule for accounts with same account number
+ var rule = new Entity("duplicaterule")
+ {
+ ["name"] = "DuplicateRule: Accounts with the same Account Number",
+ ["baseentityname"] = "account",
+ ["matchingentityname"] = "account"
+ };
+ ruleId = service.Create(rule);
+ entityStore.Add(new EntityReference("duplicaterule", ruleId));
+ Console.WriteLine($" Rule created: {ruleId}");
+
+ // Create a duplicate detection rule condition
+ var condition = new Entity("duplicaterulecondition")
+ {
+ ["baseattributename"] = "accountnumber",
+ ["matchingattributename"] = "accountnumber",
+ ["operatorcode"] = new OptionSetValue(0), // Exact match
+ ["regardingobjectid"] = new EntityReference("duplicaterule", ruleId)
+ };
+ Guid conditionId = service.Create(condition);
+ entityStore.Add(new EntityReference("duplicaterulecondition", conditionId));
+ Console.WriteLine(" Rule condition created");
+
+ Console.WriteLine("Publishing duplicate detection rule...");
+
+ // Publish the duplicate detection rule
+ var publishRequest = new PublishDuplicateRuleRequest
+ {
+ DuplicateRuleId = ruleId
+ };
+ service.Execute(publishRequest);
+
+ // Wait for the rule to be published (poll statuscode until it's Published = 2)
+ Console.WriteLine(" Waiting for rule to publish...");
+ int attempts = 0;
+ while (attempts < 20)
+ {
+ var retrievedRule = service.Retrieve("duplicaterule", ruleId, new ColumnSet("statuscode"));
+ var statusCode = retrievedRule.GetAttributeValue("statuscode");
+
+ // StatusCode 2 = Published
+ if (statusCode != null && statusCode.Value == 2)
+ {
+ Console.WriteLine(" Rule published successfully");
+ break;
+ }
+
+ attempts++;
+ Thread.Sleep(1000);
+ }
+
+ if (attempts >= 20)
+ {
+ Console.WriteLine(" Warning: Rule may still be publishing");
+ }
+
+ Console.WriteLine();
+ }
+
+ ///
+ /// Demonstrates using SuppressDuplicateDetection parameter with Create and Update
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Demonstrating duplicate detection control with CRUD operations...");
+ Console.WriteLine();
+
+ // Create an account with a duplicate account number
+ // Using SuppressDuplicateDetection = true to bypass duplicate detection
+ Console.WriteLine("Creating duplicate account with SuppressDuplicateDetection = true...");
+
+ var duplicateAccount = new Entity("account")
+ {
+ ["name"] = "Proseware, Inc.",
+ ["accountnumber"] = "ACC005" // Same as Fourth Coffee
+ };
+
+ var createRequest = new CreateRequest
+ {
+ Target = duplicateAccount
+ };
+ createRequest.Parameters.Add("SuppressDuplicateDetection", true);
+
+ var createResponse = (CreateResponse)service.Execute(createRequest);
+ Guid dupAccountId = createResponse.id;
+ entityStore.Add(new EntityReference("account", dupAccountId));
+
+ Console.WriteLine($" Created: {duplicateAccount["name"]} ({duplicateAccount["accountnumber"]})");
+ Console.WriteLine(" Duplicate detection was suppressed, so the duplicate was created");
+ Console.WriteLine();
+
+ // Retrieve the account
+ Console.WriteLine("Retrieving the account...");
+ var retrievedAccount = service.Retrieve("account", dupAccountId,
+ new ColumnSet("name", "accountnumber"));
+ Console.WriteLine($" Retrieved: {retrievedAccount["name"]}");
+ Console.WriteLine();
+
+ // Update the account with a new account number
+ // Using SuppressDuplicateDetection = false to activate duplicate detection
+ Console.WriteLine("Updating account with SuppressDuplicateDetection = false...");
+
+ retrievedAccount["accountnumber"] = "ACC006";
+
+ var updateRequest = new UpdateRequest
+ {
+ Target = retrievedAccount
+ };
+ updateRequest["SuppressDuplicateDetection"] = false;
+
+ service.Execute(updateRequest);
+
+ Console.WriteLine($" Updated account number to: {retrievedAccount["accountnumber"]}");
+ Console.WriteLine(" Duplicate detection was active, update succeeded (no duplicates found)");
+ Console.WriteLine();
+
+ Console.WriteLine("Duplicate detection CRUD operations complete.");
+ }
+
+ ///
+ /// Cleans up sample data including unpublishing the rule
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ // Unpublish the duplicate detection rule before deleting
+ try
+ {
+ Console.WriteLine("Unpublishing duplicate detection rule...");
+ var unpublishRequest = new UnpublishDuplicateRuleRequest
+ {
+ DuplicateRuleId = ruleId
+ };
+ service.Execute(unpublishRequest);
+ Console.WriteLine(" Rule unpublished");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($" Warning: Could not unpublish rule: {ex.Message}");
+ }
+
+ // Delete records in reverse order to handle dependencies
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ var entityRef = entityStore[i];
+ try
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($" Warning: Could not delete {entityRef.LogicalName} {entityRef.Id}: {ex.Message}");
+ }
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/UseDuplicatedetectionforCRUD/README.md b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/UseDuplicatedetectionforCRUD/README.md
new file mode 100644
index 00000000..5016c6bb
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/UseDuplicatedetectionforCRUD/README.md
@@ -0,0 +1,111 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates using duplicate detection with Create and Update operations"
+---
+
+# UseDuplicatedetectionforCRUD
+
+Demonstrates using duplicate detection with Create and Update operations
+
+## What this sample does
+
+This sample shows how to:
+- Create and publish a duplicate detection rule programmatically
+- Control duplicate detection behavior during Create operations using the SuppressDuplicateDetection parameter
+- Control duplicate detection behavior during Update operations using the SuppressDuplicateDetection parameter
+- Bypass duplicate detection when needed
+- Allow duplicate detection to run when needed
+
+The SuppressDuplicateDetection parameter provides fine-grained control over when duplicate detection fires during CRUD operations, allowing developers to choose when to enforce or bypass duplicate detection rules.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates an initial account record named "Fourth Coffee" with account number "ACC005"
+2. Creates a duplicate detection rule for accounts with matching account numbers
+3. Adds a rule condition for exact match on the "accountnumber" attribute
+4. Publishes the duplicate detection rule using PublishDuplicateRuleRequest
+5. Polls the rule's statuscode until it reaches "Published" (statuscode = 2)
+
+### Run
+
+The main demonstration:
+1. **Create with SuppressDuplicateDetection = true**:
+ - Creates a duplicate account "Proseware, Inc." with the same account number "ACC005"
+ - Uses CreateRequest with SuppressDuplicateDetection parameter set to true
+ - The duplicate is created successfully because detection was suppressed
+
+2. **Retrieve the duplicate account**:
+ - Uses standard Retrieve to get the account record
+ - Retrieves name and accountnumber attributes
+
+3. **Update with SuppressDuplicateDetection = false**:
+ - Updates the account with a new account number "ACC006"
+ - Uses UpdateRequest with SuppressDuplicateDetection parameter set to false
+ - Duplicate detection is active, but no duplicates exist with "ACC006", so update succeeds
+
+### Cleanup
+
+The cleanup process:
+1. Unpublishes the duplicate detection rule using UnpublishDuplicateRuleRequest
+2. Deletes all created records in reverse order:
+ - Duplicate rule conditions
+ - Duplicate detection rule
+ - Account records
+3. Handles errors gracefully if unpublish or delete operations fail
+
+## Demonstrates
+
+This sample demonstrates:
+- **CreateRequest with SuppressDuplicateDetection**: Bypassing duplicate detection during create
+- **UpdateRequest with SuppressDuplicateDetection**: Controlling duplicate detection during update
+- **PublishDuplicateRuleRequest/Response**: Publishing duplicate detection rules
+- **UnpublishDuplicateRuleRequest**: Unpublishing rules before deletion
+- **Status code polling**: Waiting for rule publishing to complete
+- **Programmatic rule creation**: Creating duplicate rules and conditions via SDK
+- **Request.Parameters collection**: Adding optional parameters to SDK requests
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating initial account record...
+ Created account: Fourth Coffee (ACC005)
+
+Creating duplicate detection rule...
+ Rule created: a1234567-89ab-cdef-0123-456789abcdef
+ Rule condition created
+Publishing duplicate detection rule...
+ Waiting for rule to publish...
+ Rule published successfully
+
+Demonstrating duplicate detection control with CRUD operations...
+
+Creating duplicate account with SuppressDuplicateDetection = true...
+ Created: Proseware, Inc. (ACC005)
+ Duplicate detection was suppressed, so the duplicate was created
+
+Retrieving the account...
+ Retrieved: Proseware, Inc.
+
+Updating account with SuppressDuplicateDetection = false...
+ Updated account number to: ACC006
+ Duplicate detection was active, update succeeded (no duplicates found)
+
+Duplicate detection CRUD operations complete.
+Cleaning up...
+Unpublishing duplicate detection rule...
+ Rule unpublished
+Deleting 4 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/UseDuplicatedetectionforCRUD/UseDuplicatedetectionforCRUD.csproj b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/UseDuplicatedetectionforCRUD/UseDuplicatedetectionforCRUD.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/UseDuplicatedetectionforCRUD/UseDuplicatedetectionforCRUD.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/appsettings.json b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/appsettings.json
new file mode 100644
index 00000000..037aca85
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/DuplicateDetection/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/Convertqueriesfetchqueryexpressions/Convertqueriesfetchqueryexpressions.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/Convertqueriesfetchqueryexpressions/Convertqueriesfetchqueryexpressions.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/Convertqueriesfetchqueryexpressions/Convertqueriesfetchqueryexpressions.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/Convertqueriesfetchqueryexpressions/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/Convertqueriesfetchqueryexpressions/Program.cs
new file mode 100644
index 00000000..4724d4d9
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/Convertqueriesfetchqueryexpressions/Program.cs
@@ -0,0 +1,359 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates converting between FetchXML and QueryExpression
+ ///
+ ///
+ /// This sample shows how to:
+ /// 1. Convert QueryExpression to FetchXML using QueryExpressionToFetchXmlRequest
+ /// 2. Convert FetchXML to QueryExpression using FetchXmlToQueryExpressionRequest
+ /// 3. Execute queries using both formats and compare results
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating sample data...");
+
+ // Create an account
+ var account = new Entity("account")
+ {
+ ["name"] = "Litware, Inc.",
+ ["address1_stateorprovince"] = "Colorado"
+ };
+ Guid accountId = service.Create(account);
+ entityStore.Add(new EntityReference("account", accountId));
+
+ // Create the first contact
+ var contact1 = new Entity("contact")
+ {
+ ["firstname"] = "Ben",
+ ["lastname"] = "Andrews",
+ ["emailaddress1"] = "sample@example.com",
+ ["address1_city"] = "Redmond",
+ ["address1_stateorprovince"] = "WA",
+ ["address1_telephone1"] = "(206)555-5555",
+ ["parentcustomerid"] = new EntityReference("account", accountId)
+ };
+ Guid contactId1 = service.Create(contact1);
+ entityStore.Add(new EntityReference("contact", contactId1));
+
+ // Create the second contact
+ var contact2 = new Entity("contact")
+ {
+ ["firstname"] = "Colin",
+ ["lastname"] = "Wilcox",
+ ["emailaddress1"] = "sample@example.com",
+ ["address1_city"] = "Bellevue",
+ ["address1_stateorprovince"] = "WA",
+ ["address1_telephone1"] = "(425)555-5555",
+ ["parentcustomerid"] = new EntityReference("account", accountId)
+ };
+ Guid contactId2 = service.Create(contact2);
+ entityStore.Add(new EntityReference("contact", contactId2));
+
+ // Create the first opportunity
+ var opportunity1 = new Entity("opportunity")
+ {
+ ["name"] = "Litware, Inc. Opportunity 1",
+ ["estimatedclosedate"] = DateTime.Now.AddMonths(6),
+ ["customerid"] = new EntityReference("account", accountId)
+ };
+ Guid opportunityId1 = service.Create(opportunity1);
+ entityStore.Add(new EntityReference("opportunity", opportunityId1));
+
+ // Create the second opportunity
+ var opportunity2 = new Entity("opportunity")
+ {
+ ["name"] = "Litware, Inc. Opportunity 2",
+ ["estimatedclosedate"] = DateTime.Now.AddYears(4),
+ ["customerid"] = new EntityReference("account", accountId)
+ };
+ Guid opportunityId2 = service.Create(opportunity2);
+ entityStore.Add(new EntityReference("opportunity", opportunityId2));
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ // Demonstrate QueryExpression to FetchXML conversion
+ DoQueryExpressionToFetchXmlConversion(service);
+
+ // Demonstrate FetchXML to QueryExpression conversion
+ DoFetchXmlToQueryExpressionConversion(service);
+ }
+
+ private static void DoQueryExpressionToFetchXmlConversion(ServiceClient service)
+ {
+ Console.WriteLine("=== QueryExpression to FetchXML Conversion ===");
+ Console.WriteLine();
+
+ // Build a query expression that we will turn into FetchXML
+ var queryExpression = new QueryExpression()
+ {
+ Distinct = false,
+ EntityName = "contact",
+ ColumnSet = new ColumnSet("fullname", "address1_telephone1"),
+ LinkEntities =
+ {
+ new LinkEntity
+ {
+ JoinOperator = JoinOperator.LeftOuter,
+ LinkFromAttributeName = "parentcustomerid",
+ LinkFromEntityName = "contact",
+ LinkToAttributeName = "accountid",
+ LinkToEntityName = "account",
+ LinkCriteria =
+ {
+ Conditions =
+ {
+ new ConditionExpression("name", ConditionOperator.Equal, "Litware, Inc.")
+ }
+ }
+ }
+ },
+ Criteria =
+ {
+ Filters =
+ {
+ new FilterExpression
+ {
+ FilterOperator = LogicalOperator.And,
+ Conditions =
+ {
+ new ConditionExpression("address1_stateorprovince", ConditionOperator.Equal, "WA"),
+ new ConditionExpression("address1_city", ConditionOperator.In, new String[] {"Redmond", "Bellevue" , "Kirkland", "Seattle"}),
+ new ConditionExpression("createdon", ConditionOperator.LastXDays, 30),
+ new ConditionExpression("emailaddress1", ConditionOperator.NotNull)
+ },
+ },
+ new FilterExpression
+ {
+ FilterOperator = LogicalOperator.Or,
+ Conditions =
+ {
+ new ConditionExpression("address1_telephone1", ConditionOperator.Like, "(206)%"),
+ new ConditionExpression("address1_telephone1", ConditionOperator.Like, "(425)%")
+ }
+ }
+ }
+ }
+ };
+
+ // Run the query as a query expression
+ EntityCollection queryExpressionResult = service.RetrieveMultiple(queryExpression);
+ Console.WriteLine("Output for query as QueryExpression:");
+ DisplayContactQueryResults(queryExpressionResult);
+
+ // Convert the query expression to FetchXML
+ var conversionRequest = new QueryExpressionToFetchXmlRequest
+ {
+ Query = queryExpression
+ };
+ var conversionResponse = (QueryExpressionToFetchXmlResponse)service.Execute(conversionRequest);
+
+ // Use the converted query to make a retrieve multiple request
+ string fetchXml = conversionResponse.FetchXml;
+ Console.WriteLine("Converted FetchXML:");
+ Console.WriteLine(fetchXml);
+ Console.WriteLine();
+
+ var fetchQuery = new FetchExpression(fetchXml);
+ EntityCollection fetchResult = service.RetrieveMultiple(fetchQuery);
+
+ // Display the results
+ Console.WriteLine("Output for query after conversion to FetchXML:");
+ DisplayContactQueryResults(fetchResult);
+ Console.WriteLine();
+ }
+
+ private static void DisplayContactQueryResults(EntityCollection result)
+ {
+ Console.WriteLine("List all contacts matching specified parameters");
+ Console.WriteLine("===============================================");
+ foreach (Entity entity in result.Entities)
+ {
+ Console.WriteLine("Contact ID: {0}", entity.Id);
+ Console.WriteLine("Contact Name: {0}", entity.GetAttributeValue("fullname"));
+ Console.WriteLine("Contact Phone: {0}", entity.GetAttributeValue("address1_telephone1"));
+ Console.WriteLine();
+ }
+ Console.WriteLine("");
+ Console.WriteLine();
+ }
+
+ private static void DoFetchXmlToQueryExpressionConversion(ServiceClient service)
+ {
+ Console.WriteLine("=== FetchXML to QueryExpression Conversion ===");
+ Console.WriteLine();
+
+ // Create a Fetch query that we will convert into a query expression
+ var fetchXml =
+ @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ";
+
+ Console.WriteLine("Original FetchXML:");
+ Console.WriteLine(fetchXml);
+ Console.WriteLine();
+
+ // Run the query with the FetchXML
+ var fetchExpression = new FetchExpression(fetchXml);
+ EntityCollection fetchResult = service.RetrieveMultiple(fetchExpression);
+ Console.WriteLine("Output for query as FetchXML:");
+ DisplayOpportunityQueryResults(fetchResult);
+
+ // Convert the FetchXML into a query expression
+ var conversionRequest = new FetchXmlToQueryExpressionRequest
+ {
+ FetchXml = fetchXml
+ };
+
+ var conversionResponse = (FetchXmlToQueryExpressionResponse)service.Execute(conversionRequest);
+
+ // Use the newly converted query expression to make a retrieve multiple request
+ QueryExpression queryExpression = conversionResponse.Query;
+
+ EntityCollection queryResult = service.RetrieveMultiple(queryExpression);
+
+ // Display the results
+ Console.WriteLine("Output for query after conversion to QueryExpression:");
+ DisplayOpportunityQueryResults(queryResult);
+ Console.WriteLine();
+ }
+
+ private static void DisplayOpportunityQueryResults(EntityCollection result)
+ {
+ Console.WriteLine("List all opportunities matching specified parameters.");
+ Console.WriteLine("===========================================================================");
+ foreach (Entity entity in result.Entities)
+ {
+ Console.WriteLine("Opportunity ID: {0}", entity.Id);
+ Console.WriteLine("Opportunity: {0}", entity.GetAttributeValue("name"));
+
+ // Get the aliased contact name from the linked entity
+ if (entity.Contains("contact2.fullname"))
+ {
+ var aliased = (AliasedValue)entity["contact2.fullname"];
+ var contactName = (string)aliased.Value;
+ Console.WriteLine("Associated contact: {0}", contactName);
+ }
+ Console.WriteLine();
+ }
+ Console.WriteLine("");
+ Console.WriteLine();
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+
+ // Delete in reverse order to handle dependencies
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/Convertqueriesfetchqueryexpressions/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/Convertqueriesfetchqueryexpressions/README.md
new file mode 100644
index 00000000..8f754ade
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/Convertqueriesfetchqueryexpressions/README.md
@@ -0,0 +1,151 @@
+# Convert queries between FetchXML and QueryExpression
+
+This sample demonstrates how to convert queries between FetchXML and QueryExpression formats using the Dataverse SDK.
+
+## Demonstrates
+
+This sample shows how to:
+
+1. Build a complex `QueryExpression` with link entities and multiple filters
+2. Convert a `QueryExpression` to FetchXML using `QueryExpressionToFetchXmlRequest`
+3. Execute queries in both QueryExpression and FetchXML formats
+4. Convert FetchXML to `QueryExpression` using `FetchXmlToQueryExpressionRequest`
+5. Work with aliased values from linked entities in query results
+
+## Key Concepts
+
+### QueryExpression to FetchXML Conversion
+
+The sample creates a `QueryExpression` that queries contacts with:
+- Link to parent account entity
+- Multiple filter conditions (state, city, creation date, email)
+- OR conditions for phone numbers
+- Retrieves full name and phone number
+
+It then converts this to FetchXML using `QueryExpressionToFetchXmlRequest` and executes both versions to show they produce identical results.
+
+### FetchXML to QueryExpression Conversion
+
+The sample uses FetchXML that queries opportunities with:
+- Filter on estimated close date (next 3 fiscal years)
+- Nested link entities (opportunity -> account -> contact)
+- Conditions on the linked contact entity
+
+It converts this to a `QueryExpression` using `FetchXmlToQueryExpressionRequest` and demonstrates that both formats retrieve the same data.
+
+### Working with Aliased Values
+
+When querying linked entities, the results contain `AliasedValue` objects. The sample shows how to:
+- Access aliased values using the entity alias prefix (e.g., "contact2.fullname")
+- Extract the actual value from the `AliasedValue` object
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating sample data...
+Setup complete.
+
+=== QueryExpression to FetchXML Conversion ===
+
+Output for query as QueryExpression:
+List all contacts matching specified parameters
+===============================================
+Contact ID: {guid}
+Contact Name: Ben Andrews
+Contact Phone: (206)555-5555
+
+Contact ID: {guid}
+Contact Name: Colin Wilcox
+Contact Phone: (425)555-5555
+
+
+
+Converted FetchXML:
+
+
+
+
+ ...
+
+
+
+Output for query after conversion to FetchXML:
+List all contacts matching specified parameters
+===============================================
+Contact ID: {guid}
+Contact Name: Ben Andrews
+Contact Phone: (206)555-5555
+
+Contact ID: {guid}
+Contact Name: Colin Wilcox
+Contact Phone: (425)555-5555
+
+
+
+=== FetchXML to QueryExpression Conversion ===
+
+Original FetchXML:
+
+
+
+ ...
+
+
+
+Output for query as FetchXML:
+List all opportunities matching specified parameters.
+===========================================================================
+Opportunity ID: {guid}
+Opportunity: Litware, Inc. Opportunity 2
+Associated contact: Colin Wilcox
+
+
+
+Output for query after conversion to QueryExpression:
+List all opportunities matching specified parameters.
+===========================================================================
+Opportunity ID: {guid}
+Opportunity: Litware, Inc. Opportunity 2
+Associated contact: Colin Wilcox
+
+
+
+Cleaning up...
+Deleting 5 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## How to run the sample
+
+1. Clone or download the [PowerApps-Samples](https://github.com/microsoft/PowerApps-Samples) repository.
+2. Open the `Convertqueriesfetchqueryexpressions.csproj` file in Visual Studio 2022 or later.
+3. Edit the `appsettings.json` file in the parent `Query` folder. Set the connection string values appropriate for your test environment.
+4. Build and run the project.
+
+The sample will:
+- Create test data (1 account, 2 contacts, 2 opportunities)
+- Demonstrate QueryExpression to FetchXML conversion
+- Demonstrate FetchXML to QueryExpression conversion
+- Display query results from both formats
+- Clean up the test data
+
+## Clean up
+
+By default, this sample deletes all the data it creates. If you want to view the created data, change the `deleteCreatedRecords` variable to `false` in the `Main` method before the `finally` block.
+
+## What this sample does
+
+The sample uses the following message requests:
+
+- [QueryExpressionToFetchXmlRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.queryexpressiontofetchxmlrequest): Converts a QueryExpression to FetchXML format
+- [FetchXmlToQueryExpressionRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.fetchxmltoqueryrequestexpression): Converts FetchXML to QueryExpression format
+
+## Supporting information
+
+- [Query data using FetchXML](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/overview)
+- [Build queries with QueryExpression](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/build-queries-with-queryexpression)
+- [Use FetchXML to construct a query](https://learn.microsoft.com/power-apps/developer/data-platform/use-fetchxml-construct-query)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/ExportDataUsingFetchXmlToAnnotation/ExportDataUsingFetchXmlToAnnotation.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/ExportDataUsingFetchXmlToAnnotation/ExportDataUsingFetchXmlToAnnotation.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/ExportDataUsingFetchXmlToAnnotation/ExportDataUsingFetchXmlToAnnotation.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/ExportDataUsingFetchXmlToAnnotation/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/ExportDataUsingFetchXmlToAnnotation/Program.cs
new file mode 100644
index 00000000..bc143d2c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/ExportDataUsingFetchXmlToAnnotation/Program.cs
@@ -0,0 +1,326 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+using System.Data;
+using System.Text;
+using System.Xml;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid annotationId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setup: Creating sample account records for export...");
+
+ // Create 5 sample accounts to demonstrate data export
+ for (int i = 1; i <= 5; i++)
+ {
+ var account = new Entity("account")
+ {
+ ["name"] = $"Sample Export Account {i}",
+ ["emailaddress1"] = $"export{i}@contoso.com",
+ ["telephone1"] = $"555-010{i}",
+ ["address1_city"] = $"City {i}"
+ };
+ Guid accountId = service.Create(account);
+ entityStore.Add(new EntityReference("account", accountId));
+ }
+
+ Console.WriteLine($"Created {entityStore.Count} sample accounts.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Demonstrating export of query results to annotation (note)...");
+ Console.WriteLine();
+
+ // Create a FetchXML query to retrieve accounts
+ // This query will select name, email, phone, and city from accounts
+ string fetchXml = @"
+
+
+
+
+
+
+
+
+
+
+ ";
+
+ Console.WriteLine("Step 1: Executing FetchXML query to retrieve account records...");
+ var fetchedRecords = FetchAllDataFromFetchXml(fetchXml, service);
+ Console.WriteLine($"Retrieved {fetchedRecords.Count} records.");
+ Console.WriteLine();
+
+ Console.WriteLine("Step 2: Converting entity data to CSV format...");
+ var csvString = ConvertEntitiesToCsv(fetchedRecords);
+ Console.WriteLine("CSV conversion complete.");
+ Console.WriteLine();
+ Console.WriteLine("CSV Preview (first 500 characters):");
+ Console.WriteLine(csvString.Length > 500 ? csvString.Substring(0, 500) + "..." : csvString);
+ Console.WriteLine();
+
+ Console.WriteLine("Step 3: Creating annotation (note) record with CSV data...");
+ annotationId = CreateAnnotationWithCsvData(service, csvString);
+ entityStore.Add(new EntityReference("annotation", annotationId));
+ Console.WriteLine($"Created annotation with ID: {annotationId}");
+ Console.WriteLine();
+
+ Console.WriteLine("Export complete! The CSV data has been saved as an annotation.");
+ Console.WriteLine("You can view this annotation in Dataverse under the Notes section.");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("\nCleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ ///
+ /// Retrieves all records defined by a FetchXML query, handling paging automatically.
+ ///
+ /// The FetchXML query definition.
+ /// The ServiceClient instance.
+ /// A list of all entities retrieved by the query.
+ private static List FetchAllDataFromFetchXml(string fetchXml, ServiceClient service)
+ {
+ List allRecords = new List();
+ int pageNumber = 1;
+ string? pagingCookie = null;
+
+ // Retrieve first page
+ EntityCollection result = service.RetrieveMultiple(new FetchExpression(fetchXml));
+ allRecords.AddRange(result.Entities);
+
+ // Continue retrieving pages while more records exist
+ while (result.MoreRecords)
+ {
+ pageNumber++;
+ pagingCookie = result.PagingCookie;
+
+ // Modify FetchXML to include paging information
+ var xmlDoc = new XmlDocument();
+ xmlDoc.LoadXml(fetchXml);
+ var attributes = xmlDoc.DocumentElement!.Attributes!;
+
+ if (!string.IsNullOrEmpty(pagingCookie))
+ {
+ var cookieAttribute = xmlDoc.CreateAttribute("paging-cookie");
+ cookieAttribute.Value = pagingCookie;
+ attributes.SetNamedItem(cookieAttribute);
+ }
+
+ var pageAttribute = attributes.GetNamedItem("page");
+ if (pageAttribute != null)
+ {
+ pageAttribute.Value = pageNumber.ToString();
+ }
+ else
+ {
+ pageAttribute = xmlDoc.CreateAttribute("page");
+ pageAttribute.Value = pageNumber.ToString();
+ attributes.SetNamedItem(pageAttribute);
+ }
+
+ // Retrieve next page
+ using (var stringWriter = new StringWriter())
+ using (var xmlTextWriter = XmlWriter.Create(stringWriter))
+ {
+ xmlDoc.WriteTo(xmlTextWriter);
+ xmlTextWriter.Flush();
+ result = service.RetrieveMultiple(new FetchExpression(stringWriter.GetStringBuilder().ToString()));
+ allRecords.AddRange(result.Entities);
+ }
+ }
+
+ return allRecords;
+ }
+
+ ///
+ /// Converts a list of entities to CSV format.
+ ///
+ /// The entities to convert.
+ /// A CSV string representation of the entity data.
+ private static string ConvertEntitiesToCsv(List entities)
+ {
+ if (entities.Count == 0)
+ {
+ return string.Empty;
+ }
+
+ // Build a DataTable to organize the entity data
+ DataTable dataTable = new DataTable();
+
+ // Populate the DataTable with entity attributes
+ foreach (var entity in entities)
+ {
+ var dataRow = dataTable.NewRow();
+ foreach (var attribute in entity.Attributes)
+ {
+ // Add column if it doesn't exist
+ if (!dataTable.Columns.Contains(attribute.Key))
+ {
+ dataTable.Columns.Add(attribute.Key);
+ }
+
+ // Serialize the attribute value appropriately
+ dataRow[attribute.Key] = SerializeAttributeValue(attribute.Value);
+ }
+ dataTable.Rows.Add(dataRow);
+ }
+
+ // Convert DataTable to CSV
+ return ConvertDataTableToCsv(dataTable);
+ }
+
+ ///
+ /// Serializes Dataverse attribute values to string format suitable for CSV.
+ ///
+ /// The attribute value to serialize.
+ /// A string representation of the value.
+ private static string SerializeAttributeValue(object value)
+ {
+ return value switch
+ {
+ Money money => money.Value.ToString(),
+ OptionSetValue optionSetValue => optionSetValue.Value.ToString(),
+ EntityReference entityReference => entityReference.Id.ToString(),
+ OptionSetValueCollection optionSetValueCollection =>
+ string.Join(",", optionSetValueCollection.Select(option => option.Value)),
+ DateTime datetime => datetime.ToString("yyyy-MM-ddTHH:mm:ssZ"),
+ AliasedValue aliasedValue => SerializeAttributeValue(aliasedValue.Value),
+ _ => value?.ToString() ?? string.Empty
+ };
+ }
+
+ ///
+ /// Converts a DataTable to CSV format.
+ ///
+ /// The DataTable to convert.
+ /// A CSV string.
+ private static string ConvertDataTableToCsv(DataTable dataTable)
+ {
+ var csv = new StringBuilder();
+
+ // Add header row with column names
+ var columnNames = dataTable.Columns.Cast().Select(column => column.ColumnName);
+ csv.AppendLine(string.Join(",", columnNames));
+
+ // Add data rows
+ foreach (DataRow row in dataTable.Rows)
+ {
+ var fields = row.ItemArray.Select(field =>
+ {
+ // Escape quotes and wrap in quotes for CSV format
+ string fieldValue = field?.ToString() ?? string.Empty;
+ return $"\"{fieldValue.Replace("\"", "\"\"")}\"";
+ });
+ csv.AppendLine(string.Join(",", fields));
+ }
+
+ return csv.ToString();
+ }
+
+ ///
+ /// Creates an annotation (note) record with CSV data as an attachment.
+ ///
+ /// The ServiceClient instance.
+ /// The CSV data to store in the annotation.
+ /// The ID of the created annotation record.
+ private static Guid CreateAnnotationWithCsvData(ServiceClient service, string csvString)
+ {
+ Entity annotation = new Entity("annotation")
+ {
+ ["subject"] = "Export Data Using FetchXml To Csv",
+ ["documentbody"] = Convert.ToBase64String(Encoding.UTF8.GetBytes(csvString)),
+ ["filename"] = "exportdatausingfetchxml.csv",
+ ["mimetype"] = "text/csv"
+ };
+
+ return service.Create(annotation);
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ IConfiguration Configuration { get; }
+
+ Program()
+ {
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ Console.WriteLine();
+ Console.WriteLine("Stack Trace:");
+ Console.WriteLine(ex.StackTrace);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/ExportDataUsingFetchXmlToAnnotation/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/ExportDataUsingFetchXmlToAnnotation/README.md
new file mode 100644
index 00000000..97ef66bf
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/ExportDataUsingFetchXmlToAnnotation/README.md
@@ -0,0 +1,182 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates exporting FetchXML query results to CSV format and storing them as annotation (note) attachments"
+---
+
+# ExportDataUsingFetchXmlToAnnotation
+
+Demonstrates exporting FetchXML query results to CSV format and storing them as annotation (note) attachments in Dataverse
+
+## What this sample does
+
+This sample shows how to:
+- Execute FetchXML queries to retrieve entity records
+- Handle FetchXML paging automatically to retrieve all records
+- Convert entity data to CSV format
+- Handle various Dataverse data types (Money, EntityReference, OptionSetValue, DateTime, etc.)
+- Create annotation (note) records with CSV file attachments
+- Store base64-encoded file data in the documentbody attribute
+
+This pattern is useful for:
+- Creating data exports that can be viewed or downloaded from Dataverse
+- Generating reports as CSV files attached to records
+- Creating audit trails or data snapshots
+- Building custom export functionality
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates 5 sample account records with name, email, phone, and city data
+2. Stores entity references in entityStore for cleanup
+
+### Run
+
+The main demonstration:
+1. **Execute FetchXML Query**: Creates a FetchXML query to retrieve accounts matching "Sample Export Account"
+2. **Fetch All Data with Paging**: Calls `FetchAllDataFromFetchXml()` which:
+ - Executes the initial FetchXML query
+ - Checks for MoreRecords flag
+ - Automatically handles paging by modifying FetchXML with paging-cookie and page attributes
+ - Continues retrieving pages until all records are fetched
+3. **Convert to CSV**: Calls `ConvertEntitiesToCsv()` which:
+ - Builds a DataTable from entity attributes
+ - Dynamically adds columns for all attributes found
+ - Serializes Dataverse-specific types (Money, EntityReference, OptionSetValue, DateTime, AliasedValue)
+ - Converts DataTable to CSV format with proper quoting and escaping
+4. **Create Annotation**: Calls `CreateAnnotationWithCsvData()` which:
+ - Creates an annotation entity
+ - Sets subject and filename attributes
+ - Encodes CSV string to base64 and stores in documentbody
+ - Sets mimetype to "text/csv"
+ - Returns the annotation ID
+
+### Cleanup
+
+The cleanup process deletes all created records (accounts and annotation) in reverse order.
+
+## Demonstrates
+
+This sample demonstrates:
+- **FetchXML**: Building and executing XML-based queries
+- **Automatic Paging**: Handling large result sets with paging-cookie
+- **Data Type Serialization**: Converting Dataverse types to string format
+- **CSV Generation**: Creating properly formatted CSV with headers and quoted fields
+- **Annotations**: Creating note records with file attachments
+- **Base64 Encoding**: Encoding file content for storage in documentbody
+- **Dynamic Schema**: Handling entities with varying attributes
+- **Late-Bound Entities**: Using Entity class without early-bound types
+- **EntityStore Pattern**: Tracking created entities for cleanup
+
+## Key Methods
+
+### FetchAllDataFromFetchXml
+Retrieves all records from a FetchXML query by automatically handling paging:
+- Uses RetrieveMultiple with FetchExpression
+- Checks MoreRecords flag
+- Modifies FetchXML XML to add paging-cookie and page attributes
+- Loops until all records are retrieved
+
+### ConvertEntitiesToCsv
+Converts entity collection to CSV format:
+- Builds DataTable dynamically based on entity attributes
+- Handles all entities in collection, even with different attribute sets
+- Calls SerializeAttributeValue for each attribute
+- Converts DataTable to CSV with proper formatting
+
+### SerializeAttributeValue
+Converts Dataverse attribute values to string format:
+- **Money**: Extracts decimal value
+- **OptionSetValue**: Extracts integer value
+- **EntityReference**: Extracts GUID
+- **OptionSetValueCollection**: Joins multiple values with comma
+- **DateTime**: Formats as ISO 8601 (yyyy-MM-ddTHH:mm:ssZ)
+- **AliasedValue**: Recursively serializes inner value
+- **Other types**: Uses ToString()
+
+### ConvertDataTableToCsv
+Converts DataTable to CSV string:
+- Creates header row with column names
+- Wraps all fields in quotes for CSV compliance
+- Escapes internal quotes by doubling them (" → "")
+- Joins fields with commas
+
+### CreateAnnotationWithCsvData
+Creates annotation record with CSV attachment:
+- Sets subject for identification
+- Sets filename with .csv extension
+- Converts CSV string to UTF8 bytes
+- Encodes bytes to base64 string
+- Stores in documentbody attribute
+- Sets mimetype to text/csv
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Setup: Creating sample account records for export...
+Created 5 sample accounts.
+
+Demonstrating export of query results to annotation (note)...
+
+Step 1: Executing FetchXML query to retrieve account records...
+Retrieved 5 records.
+
+Step 2: Converting entity data to CSV format...
+CSV conversion complete.
+
+CSV Preview (first 500 characters):
+name,emailaddress1,telephone1,address1_city
+"Sample Export Account 1","export1@contoso.com","555-0101","City 1"
+"Sample Export Account 2","export2@contoso.com","555-0102","City 2"
+"Sample Export Account 3","export3@contoso.com","555-0103","City 3"
+"Sample Export Account 4","export4@contoso.com","555-0104","City 4"
+"Sample Export Account 5","export5@contoso.com","555-0105","City 5"
+
+Step 3: Creating annotation (note) record with CSV data...
+Created annotation with ID: 12345678-abcd-1234-abcd-123456789012
+
+Export complete! The CSV data has been saved as an annotation.
+You can view this annotation in Dataverse under the Notes section.
+
+Cleaning up...
+Deleting 6 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## Real-World Use Cases
+
+This pattern can be applied to:
+1. **Custom Export Reports**: Create scheduled exports of business data
+2. **Audit Trails**: Generate snapshots of data at specific points in time
+3. **Integration**: Prepare data for external systems that consume CSV
+4. **User Downloads**: Allow users to export filtered data from custom pages
+5. **Backup Annotations**: Store data backups as attachments to configuration records
+
+## Extension Ideas
+
+To extend this sample:
+- Add filtering parameters to customize the FetchXML query
+- Support exporting to other formats (Excel, JSON, XML)
+- Associate annotations with specific records (set objectid and objecttypecode)
+- Compress large CSV files before storing
+- Add metadata headers to CSV (export date, user, filter criteria)
+- Implement incremental exports using modification dates
+- Add support for linked entities in FetchXML
+- Handle binary attributes (images, files)
+
+## See also
+
+[Use FetchXML to construct a query](https://learn.microsoft.com/power-apps/developer/data-platform/use-fetchxml-construct-query)
+[Page large result sets with FetchXML](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/page-large-result-sets-with-fetchxml)
+[Work with annotations (notes)](https://learn.microsoft.com/power-apps/developer/data-platform/annotation-note-entity)
+[Query data using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-query-data)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueriesUsingLINQ/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/QueriesUsingLINQ/Program.cs
new file mode 100644
index 00000000..f439b982
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueriesUsingLINQ/Program.cs
@@ -0,0 +1,628 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Client;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates querying data using Language-Integrated Query (LINQ)
+ ///
+ ///
+ /// This sample shows various LINQ query patterns including:
+ /// - Simple where clauses and filtering
+ /// - Joins (inner, left, self)
+ /// - Operators (equals, not equals, greater than, contains, StartsWith, EndsWith)
+ /// - Ordering and paging
+ /// - String and math operations
+ /// - Late-bound entity queries
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating sample data...");
+
+ // Create contacts for LINQ samples
+ var contact1 = new Entity("contact")
+ {
+ ["firstname"] = "Colin",
+ ["lastname"] = "Wilcox",
+ ["address1_city"] = "Redmond",
+ ["address1_stateorprovince"] = "WA",
+ ["address1_postalcode"] = "98052",
+ ["anniversary"] = new DateTime(2010, 3, 5),
+ ["creditlimit"] = new Money(300),
+ ["description"] = "Alpine Ski House",
+ ["numberofchildren"] = 1,
+ ["address1_latitude"] = 47.6741667,
+ ["address1_longitude"] = -122.1202778
+ };
+ Guid contactId1 = service.Create(contact1);
+ entityStore.Add(new EntityReference("contact", contactId1));
+
+ var contact2 = new Entity("contact")
+ {
+ ["firstname"] = "Brian",
+ ["lastname"] = "Smith",
+ ["address1_city"] = "Bellevue",
+ ["address1_stateorprovince"] = "WA",
+ ["address1_postalcode"] = "98008",
+ ["anniversary"] = new DateTime(2010, 4, 5),
+ ["creditlimit"] = new Money(30000),
+ ["description"] = "Coho Winery",
+ ["numberofchildren"] = 2,
+ ["address1_latitude"] = 47.6105556,
+ ["address1_longitude"] = -122.1994444
+ };
+ Guid contactId2 = service.Create(contact2);
+ entityStore.Add(new EntityReference("contact", contactId2));
+
+ var contact3 = new Entity("contact")
+ {
+ ["firstname"] = "Darren",
+ ["lastname"] = "Parker",
+ ["address1_city"] = "Kirkland",
+ ["address1_stateorprovince"] = "WA",
+ ["address1_postalcode"] = "98033",
+ ["anniversary"] = new DateTime(2010, 10, 5),
+ ["creditlimit"] = new Money(10000),
+ ["description"] = "Coho Winery",
+ ["numberofchildren"] = 2
+ };
+ Guid contactId3 = service.Create(contact3);
+ entityStore.Add(new EntityReference("contact", contactId3));
+
+ var contact4 = new Entity("contact")
+ {
+ ["firstname"] = "Ben",
+ ["lastname"] = "Smith",
+ ["address1_city"] = "Kirkland",
+ ["address1_stateorprovince"] = "WA",
+ ["anniversary"] = new DateTime(2010, 7, 5),
+ ["creditlimit"] = new Money(12000),
+ ["description"] = "Coho Winery",
+ ["numberofchildren"] = 2,
+ ["creditonhold"] = true
+ };
+ Guid contactId4 = service.Create(contact4);
+ entityStore.Add(new EntityReference("contact", contactId4));
+
+ // Create accounts
+ var account1 = new Entity("account")
+ {
+ ["name"] = "Coho Winery",
+ ["address1_name"] = "Coho Vineyard & Winery",
+ ["address1_city"] = "Redmond"
+ };
+ Guid accountId1 = service.Create(account1);
+ entityStore.Add(new EntityReference("account", accountId1));
+
+ var lead = new Entity("lead")
+ {
+ ["firstname"] = "Diogo",
+ ["lastname"] = "Andrade"
+ };
+ Guid leadId = service.Create(lead);
+ entityStore.Add(new EntityReference("lead", leadId));
+
+ var account2 = new Entity("account")
+ {
+ ["name"] = "Contoso Ltd",
+ ["parentaccountid"] = new EntityReference("account", accountId1),
+ ["address1_name"] = "Contoso Pharmaceuticals",
+ ["address1_city"] = "Redmond",
+ ["originatingleadid"] = new EntityReference("lead", leadId),
+ ["primarycontactid"] = new EntityReference("contact", contactId2)
+ };
+ Guid accountId2 = service.Create(account2);
+ entityStore.Add(new EntityReference("account", accountId2));
+
+ // Create additional accounts for simple queries
+ var account3 = new Entity("account")
+ {
+ ["name"] = "Fourth Coffee",
+ ["address1_stateorprovince"] = "Colorado"
+ };
+ entityStore.Add(new EntityReference("account", service.Create(account3)));
+
+ var account4 = new Entity("account")
+ {
+ ["name"] = "School of Fine Art",
+ ["address1_stateorprovince"] = "Illinois",
+ ["address1_county"] = "Lake County"
+ };
+ entityStore.Add(new EntityReference("account", service.Create(account4)));
+
+ var account5 = new Entity("account")
+ {
+ ["name"] = "Tailspin Toys",
+ ["address1_stateorprovince"] = "Washington",
+ ["address1_county"] = "King County"
+ };
+ entityStore.Add(new EntityReference("account", service.Create(account5)));
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("=".PadRight(70, '='));
+ Console.WriteLine("LINQ Query Examples");
+ Console.WriteLine("=".PadRight(70, '='));
+ Console.WriteLine();
+
+ using (OrganizationServiceContext orgContext = new OrganizationServiceContext(service))
+ {
+ // Example 1: Simple where clause with Contains
+ Console.WriteLine("1. Simple where clause - Accounts containing 'Contoso'");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query1 = from a in orgContext.CreateQuery("account")
+ where ((string)a["name"]).Contains("Contoso")
+ select new
+ {
+ Name = a["name"],
+ City = a.GetAttributeValue("address1_city")
+ };
+ foreach (var a in query1)
+ {
+ Console.WriteLine($" {a.Name} - {a.City}");
+ }
+ Console.WriteLine();
+
+ // Example 2: Multiple where clauses
+ Console.WriteLine("2. Multiple where clauses - Contoso in Redmond");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query2 = from a in orgContext.CreateQuery("account")
+ where ((string)a["name"]).Contains("Contoso")
+ where a.GetAttributeValue("address1_city") == "Redmond"
+ select new
+ {
+ Name = a["name"],
+ City = a["address1_city"]
+ };
+ foreach (var a in query2)
+ {
+ Console.WriteLine($" {a.Name} - {a.City}");
+ }
+ Console.WriteLine();
+
+ // Example 3: Inner join
+ Console.WriteLine("3. Inner join - Contacts with their accounts");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query3 = from c in orgContext.CreateQuery("contact")
+ join a in orgContext.CreateQuery("account")
+ on c["contactid"] equals a["primarycontactid"]
+ select new
+ {
+ ContactName = c["fullname"],
+ AccountName = a["name"]
+ };
+ foreach (var item in query3)
+ {
+ Console.WriteLine($" Contact: {item.ContactName}, Account: {item.AccountName}");
+ }
+ Console.WriteLine();
+
+ // Example 4: Left join
+ Console.WriteLine("4. Left join - Accounts with and without primary contacts");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query4 = from a in orgContext.CreateQuery("account")
+ join c in orgContext.CreateQuery("contact")
+ on a["primarycontactid"] equals c["contactid"] into gr
+ from c_joined in gr.DefaultIfEmpty()
+ select new
+ {
+ AccountName = a["name"],
+ ContactName = c_joined != null ? c_joined.GetAttributeValue("fullname") : "(none)"
+ };
+ foreach (var item in query4)
+ {
+ Console.WriteLine($" Account: {item.AccountName}, Contact: {item.ContactName}");
+ }
+ Console.WriteLine();
+
+ // Example 5: Using Distinct
+ Console.WriteLine("5. Distinct - Unique contact last names");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query5 = (from c in orgContext.CreateQuery("contact")
+ select c.GetAttributeValue("lastname")).Distinct();
+ foreach (var lastName in query5)
+ {
+ Console.WriteLine($" {lastName}");
+ }
+ Console.WriteLine();
+
+ // Example 6: Equals operator
+ Console.WriteLine("6. Equals operator - Contacts named Colin");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query6 = from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("firstname") == "Colin"
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"],
+ City = c.GetAttributeValue("address1_city")
+ };
+ foreach (var c in query6)
+ {
+ Console.WriteLine($" {c.FirstName} {c.LastName} - {c.City}");
+ }
+ Console.WriteLine();
+
+ // Example 7: Not equals operator
+ Console.WriteLine("7. Not equals - Contacts not in Redmond");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query7 = from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("address1_city") != "Redmond"
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"],
+ City = c["address1_city"]
+ };
+ foreach (var c in query7)
+ {
+ Console.WriteLine($" {c.FirstName} {c.LastName} - {c.City}");
+ }
+ Console.WriteLine();
+
+ // Example 8: Greater than operator with Money
+ Console.WriteLine("8. Greater than - Contacts with credit limit > $20,000");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query8 = from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("creditlimit") != null
+ && c.GetAttributeValue("creditlimit").Value > 20000
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"],
+ CreditLimit = c.GetAttributeValue("creditlimit")
+ };
+ foreach (var c in query8)
+ {
+ Console.WriteLine($" {c.FirstName} {c.LastName} - ${c.CreditLimit?.Value}");
+ }
+ Console.WriteLine();
+
+ // Example 9: Greater than with DateTime
+ Console.WriteLine("9. Date comparison - Anniversaries after Feb 5, 2010");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query9 = from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("anniversary") > new DateTime(2010, 2, 5)
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"],
+ Anniversary = c.GetAttributeValue("anniversary")
+ };
+ foreach (var c in query9)
+ {
+ Console.WriteLine($" {c.FirstName} {c.LastName} - {c.Anniversary?.ToShortDateString()}");
+ }
+ Console.WriteLine();
+
+ // Example 10: Contains operator
+ Console.WriteLine("10. Contains operator - Description contains 'Alpine'");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query10 = from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("description") != null
+ && ((string)c["description"]).Contains("Alpine")
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"]
+ };
+ foreach (var c in query10)
+ {
+ Console.WriteLine($" {c.FirstName} {c.LastName}");
+ }
+ Console.WriteLine();
+
+ // Example 11: StartsWith operator
+ Console.WriteLine("11. StartsWith - First names starting with 'Bri'");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query11 = from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("firstname") != null
+ && ((string)c["firstname"]).StartsWith("Bri")
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"]
+ };
+ foreach (var c in query11)
+ {
+ Console.WriteLine($" {c.FirstName} {c.LastName}");
+ }
+ Console.WriteLine();
+
+ // Example 12: EndsWith operator
+ Console.WriteLine("12. EndsWith - Last names ending with 'cox'");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query12 = from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("lastname") != null
+ && ((string)c["lastname"]).EndsWith("cox")
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"]
+ };
+ foreach (var c in query12)
+ {
+ Console.WriteLine($" {c.FirstName} {c.LastName}");
+ }
+ Console.WriteLine();
+
+ // Example 13: AND/OR operators
+ Console.WriteLine("13. AND/OR operators - (Redmond OR Bellevue) AND CreditLimit >= $200");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query13 = from c in orgContext.CreateQuery("contact")
+ where ((c.GetAttributeValue("address1_city") == "Redmond" ||
+ c.GetAttributeValue("address1_city") == "Bellevue") &&
+ c.GetAttributeValue("creditlimit") != null &&
+ c.GetAttributeValue("creditlimit").Value >= 200)
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"],
+ City = c["address1_city"],
+ CreditLimit = c.GetAttributeValue("creditlimit")
+ };
+ foreach (var c in query13)
+ {
+ Console.WriteLine($" {c.FirstName} {c.LastName} - {c.City} - ${c.CreditLimit?.Value}");
+ }
+ Console.WriteLine();
+
+ // Example 14: OrderBy descending
+ Console.WriteLine("14. OrderBy descending - Contacts by credit limit");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query14 = from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("creditlimit") != null
+ orderby c.GetAttributeValue("creditlimit").Value descending
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"],
+ CreditLimit = c.GetAttributeValue("creditlimit")
+ };
+ foreach (var c in query14)
+ {
+ Console.WriteLine($" ${c.CreditLimit?.Value} - {c.FirstName} {c.LastName}");
+ }
+ Console.WriteLine();
+
+ // Example 15: Multiple OrderBy
+ Console.WriteLine("15. Multiple OrderBy - By last name desc, first name asc");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query15 = from c in orgContext.CreateQuery("contact")
+ orderby c.GetAttributeValue("lastname") descending,
+ c.GetAttributeValue("firstname") ascending
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"]
+ };
+ foreach (var c in query15)
+ {
+ Console.WriteLine($" {c.LastName}, {c.FirstName}");
+ }
+ Console.WriteLine();
+
+ // Example 16: Skip and Take (paging)
+ Console.WriteLine("16. Skip and Take - Skip 1, take 2 contacts");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query16 = (from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("lastname") != "Parker"
+ orderby c.GetAttributeValue("firstname")
+ select new
+ {
+ FirstName = c["firstname"],
+ LastName = c["lastname"]
+ }).Skip(1).Take(2);
+ foreach (var c in query16)
+ {
+ Console.WriteLine($" {c.FirstName} {c.LastName}");
+ }
+ Console.WriteLine();
+
+ // Example 17: First
+ Console.WriteLine("17. First - Get first contact");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var firstContact = orgContext.CreateQuery("contact").First();
+ Console.WriteLine($" {firstContact.GetAttributeValue("fullname")}");
+ Console.WriteLine();
+
+ // Example 18: Count with Distinct
+ Console.WriteLine("18. Count - Number of accounts with county specified");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var accountsWithCounty = (from a in orgContext.CreateQuery("account")
+ where a.GetAttributeValue("address1_county") != null
+ select a).ToArray().Count();
+ Console.WriteLine($" Accounts with county: {accountsWithCounty}");
+ Console.WriteLine();
+
+ // Example 19: Distinct count of states
+ Console.WriteLine("19. Distinct count - Unique states with accounts");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var statesWithAccounts = (from a in orgContext.CreateQuery("account")
+ where a.GetAttributeValue("address1_stateorprovince") != null
+ select a.GetAttributeValue("address1_stateorprovince"))
+ .Distinct().ToArray().Count();
+ Console.WriteLine($" Unique states: {statesWithAccounts}");
+ Console.WriteLine();
+
+ // Example 20: Self join
+ Console.WriteLine("20. Self join - Accounts with parent accounts");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query20 = from a in orgContext.CreateQuery("account")
+ join a2 in orgContext.CreateQuery("account")
+ on a["parentaccountid"] equals a2["accountid"]
+ select new
+ {
+ AccountName = a["name"],
+ ParentName = a2["name"]
+ };
+ foreach (var a in query20)
+ {
+ Console.WriteLine($" {a.AccountName} -> Parent: {a.ParentName}");
+ }
+ Console.WriteLine();
+
+ // Example 21: Multiple joins
+ Console.WriteLine("21. Multiple joins - Account, Contact, and Lead");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query21 = from a in orgContext.CreateQuery("account")
+ join c in orgContext.CreateQuery("contact")
+ on a["primarycontactid"] equals c["contactid"]
+ join l in orgContext.CreateQuery("lead")
+ on a["originatingleadid"] equals l["leadid"]
+ select new
+ {
+ ContactName = c["fullname"],
+ AccountName = a["name"],
+ LeadName = l["fullname"]
+ };
+ foreach (var item in query21)
+ {
+ Console.WriteLine($" Contact: {item.ContactName}, Account: {item.AccountName}, Lead: {item.LeadName}");
+ }
+ Console.WriteLine();
+
+ // Example 22: GetAttributeValue method
+ Console.WriteLine("22. GetAttributeValue - Strongly typed attribute access");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query22 = from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("numberofchildren") > 1
+ select new
+ {
+ FirstName = c.GetAttributeValue("firstname"),
+ LastName = c.GetAttributeValue("lastname"),
+ NumberOfChildren = c.GetAttributeValue("numberofchildren")
+ };
+ foreach (var c in query22)
+ {
+ Console.WriteLine($" {c.FirstName} {c.LastName} - Children: {c.NumberOfChildren}");
+ }
+ Console.WriteLine();
+
+ // Example 23: Math operations
+ Console.WriteLine("23. Math operations - Round, Floor, Ceiling on latitude");
+ Console.WriteLine("-".PadRight(70, '-'));
+ var query23 = from c in orgContext.CreateQuery("contact")
+ where c.GetAttributeValue("address1_latitude") != null
+ select new
+ {
+ Name = c.GetAttributeValue("fullname"),
+ Latitude = c.GetAttributeValue("address1_latitude"),
+ Round = Math.Round(c.GetAttributeValue("address1_latitude")),
+ Floor = Math.Floor(c.GetAttributeValue("address1_latitude")),
+ Ceiling = Math.Ceiling(c.GetAttributeValue("address1_latitude"))
+ };
+ foreach (var c in query23)
+ {
+ Console.WriteLine($" {c.Name}: {c.Latitude} -> Round:{c.Round}, Floor:{c.Floor}, Ceiling:{c.Ceiling}");
+ }
+ Console.WriteLine();
+ }
+
+ Console.WriteLine("=".PadRight(70, '='));
+ Console.WriteLine("All LINQ query examples completed successfully!");
+ Console.WriteLine("=".PadRight(70, '='));
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine();
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ // Delete in reverse order to respect dependencies
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ Console.WriteLine();
+ Console.WriteLine("Stack Trace:");
+ Console.WriteLine(ex.StackTrace);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueriesUsingLINQ/QueriesUsingLINQ.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/QueriesUsingLINQ/QueriesUsingLINQ.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueriesUsingLINQ/QueriesUsingLINQ.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueriesUsingLINQ/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/QueriesUsingLINQ/README.md
new file mode 100644
index 00000000..2157753b
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueriesUsingLINQ/README.md
@@ -0,0 +1,123 @@
+# Sample: Query data using LINQ
+
+This sample demonstrates how to query business data using Language-Integrated Query (LINQ) with the Dataverse SDK for .NET.
+
+## Summary
+
+This sample shows various LINQ query patterns for querying Dataverse data, including:
+
+- Simple where clauses and filtering
+- Joins (inner, left, self, multiple)
+- Comparison operators (equals, not equals, greater than, less than)
+- String operations (Contains, StartsWith, EndsWith)
+- Logical operators (AND, OR, NOT)
+- Ordering and sorting
+- Paging with Skip and Take
+- Aggregate operations (Count, Distinct)
+- Math operations on numeric fields
+- GetAttributeValue for strongly-typed attribute access
+- Late-bound entity queries using OrganizationServiceContext
+
+## How to run this sample
+
+1. Download or clone the sample so that you have a local copy.
+2. Open the sample solution in Visual Studio Code or Visual Studio.
+3. Update the connection string in the `appsettings.json` file in the Query folder with your Dataverse environment URL and credentials.
+4. Build and run the sample using `dotnet run` or F5 in Visual Studio.
+
+## What this sample does
+
+The sample demonstrates 23 different LINQ query patterns:
+
+1. **Simple where clause** - Filter accounts by name containing text
+2. **Multiple where clauses** - Combine multiple filter conditions
+3. **Inner join** - Join contacts with their associated accounts
+4. **Left join** - Retrieve accounts with or without primary contacts
+5. **Distinct** - Get unique values from a field
+6. **Equals operator** - Exact match filtering
+7. **Not equals** - Exclusion filtering
+8. **Greater than with Money** - Compare currency fields
+9. **Date comparison** - Filter by date ranges
+10. **Contains operator** - Partial text matching
+11. **StartsWith** - Prefix matching
+12. **EndsWith** - Suffix matching
+13. **AND/OR operators** - Complex logical conditions
+14. **OrderBy descending** - Sort results in descending order
+15. **Multiple OrderBy** - Multi-level sorting
+16. **Skip and Take** - Implement paging
+17. **First** - Get the first record
+18. **Count** - Count filtered results
+19. **Distinct count** - Count unique values
+20. **Self join** - Join a table to itself
+21. **Multiple joins** - Join three or more tables
+22. **GetAttributeValue** - Strongly-typed attribute access
+23. **Math operations** - Use mathematical functions in queries
+
+## How this sample works
+
+### Setup
+
+The sample creates test data including:
+- 4 contacts with various attributes (names, addresses, credit limits, etc.)
+- 5 accounts with different locations and properties
+- 1 lead record
+- Relationships between these entities
+
+### Demonstrate
+
+The sample executes 23 different LINQ queries, each demonstrating a specific pattern or technique. All queries use late-bound entities with `OrganizationServiceContext`, making them compatible with any Dataverse environment without requiring early-bound classes.
+
+Key differences from early-bound LINQ:
+- Uses `orgContext.CreateQuery("tablename")` instead of typed sets like `ContactSet`
+- Accesses attributes using indexers: `entity["attributename"]`
+- Uses `GetAttributeValue()` for strongly-typed access
+- Handles lookups as `EntityReference` objects
+- Requires explicit casting for certain operations
+
+### Cleanup
+
+The sample deletes all created records in reverse order to respect entity dependencies. The cleanup prompt can be disabled by setting `deleteCreatedRecords = false` in the code.
+
+## Key Concepts
+
+### OrganizationServiceContext
+
+The `OrganizationServiceContext` class provides a LINQ query provider for Dataverse. It translates LINQ queries into QueryExpression objects that are executed against the Dataverse API.
+
+### Late-Bound Entities
+
+This sample uses late-bound entities, which means:
+- No need for early-bound classes generated by CrmSvcUtil
+- Works with any Dataverse environment
+- Attributes accessed by string names
+- Less compile-time type safety but more flexibility
+
+### Query Translation
+
+LINQ queries are translated to QueryExpression at runtime. Not all LINQ features are supported - only those that can be translated to valid QueryExpression operations.
+
+Supported operations:
+- Where, Select, OrderBy, ThenBy
+- Skip, Take, First, FirstOrDefault, Single, SingleOrDefault
+- Count, Distinct
+- Join (inner and left)
+- String methods: Contains, StartsWith, EndsWith
+- Comparison operators: ==, !=, >, <, >=, <=
+- Math functions: Round, Floor, Ceiling, Abs
+
+Unsupported operations that will throw exceptions:
+- GroupBy
+- Aggregate functions other than Count
+- Complex expressions that can't be translated
+- Client-side evaluation (all operations must be server-side)
+
+## Related Documentation
+
+- [Query data using LINQ](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/linq-query-examples)
+- [Use LINQ to construct a query](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/use-linq-construct-query)
+- [Late-bound and early-bound programming](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/early-bound-programming)
+- [Build queries with LINQ (.NET language-integrated query)](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/build-queries-with-linq-net-language-integrated-query)
+
+## See Also
+
+[Query data using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-query-data)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryByReciprocalRole/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByReciprocalRole/Program.cs
new file mode 100644
index 00000000..ffdd6627
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByReciprocalRole/Program.cs
@@ -0,0 +1,235 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates querying connection roles by reciprocal role
+ ///
+ ///
+ /// This sample shows how to query for connection roles that have a specific
+ /// role listed as a reciprocal role, using QueryExpression with LinkEntity.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid primaryConnectionRoleId;
+ private static Guid reciprocalConnectionRoleId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating sample data...");
+
+ // Define connection role categories
+ int businessCategory = 1;
+
+ // Create the primary connection role
+ var primaryConnectionRole = new Entity("connectionrole")
+ {
+ ["name"] = "Example Primary Connection Role",
+ ["category"] = new OptionSetValue(businessCategory)
+ };
+
+ primaryConnectionRoleId = service.Create(primaryConnectionRole);
+ entityStore.Add(new EntityReference("connectionrole", primaryConnectionRoleId));
+ Console.WriteLine("Created primary connection role: {0}", primaryConnectionRole["name"]);
+
+ // Create a related Connection Role Object Type Code record for Account
+ // on the primary role
+ var accountPrimaryConnectionRoleTypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", primaryConnectionRoleId),
+ ["associatedobjecttypecode"] = "account"
+ };
+
+ Guid primaryTypeCodeId = service.Create(accountPrimaryConnectionRoleTypeCode);
+ entityStore.Add(new EntityReference("connectionroleobjecttypecode", primaryTypeCodeId));
+ Console.WriteLine("Created Connection Role Object Type Code for Account on primary role.");
+
+ // Create the reciprocal connection role
+ var reciprocalConnectionRole = new Entity("connectionrole")
+ {
+ ["name"] = "Example Reciprocal Connection Role",
+ ["category"] = new OptionSetValue(businessCategory)
+ };
+
+ reciprocalConnectionRoleId = service.Create(reciprocalConnectionRole);
+ entityStore.Add(new EntityReference("connectionrole", reciprocalConnectionRoleId));
+ Console.WriteLine("Created reciprocal connection role: {0}", reciprocalConnectionRole["name"]);
+
+ // Create a related Connection Role Object Type Code record for Account
+ // on the reciprocal role
+ var accountReciprocalConnectionRoleTypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", reciprocalConnectionRoleId),
+ ["associatedobjecttypecode"] = "account"
+ };
+
+ Guid reciprocalTypeCodeId = service.Create(accountReciprocalConnectionRoleTypeCode);
+ entityStore.Add(new EntityReference("connectionroleobjecttypecode", reciprocalTypeCodeId));
+ Console.WriteLine("Created Connection Role Object Type Code for Account on reciprocal role.");
+
+ // Associate the connection roles using the connectionroleassociation relationship
+ var associateRequest = new AssociateRequest
+ {
+ Target = new EntityReference("connectionrole", primaryConnectionRoleId),
+ RelatedEntities = new EntityReferenceCollection()
+ {
+ new EntityReference("connectionrole", reciprocalConnectionRoleId)
+ },
+ Relationship = new Relationship()
+ {
+ PrimaryEntityRole = EntityRole.Referencing,
+ SchemaName = "connectionroleassociation_association"
+ }
+ };
+
+ service.Execute(associateRequest);
+ Console.WriteLine("Associated primary and reciprocal connection roles.");
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Querying for connection roles by reciprocal role...");
+ Console.WriteLine();
+
+ // This query retrieves all connection roles that have the specified role
+ // listed as a reciprocal role.
+ var query = new QueryExpression
+ {
+ EntityName = "connectionrole",
+ ColumnSet = new ColumnSet("connectionroleid", "name"),
+ LinkEntities =
+ {
+ new LinkEntity
+ {
+ JoinOperator = JoinOperator.Inner,
+ LinkFromEntityName = "connectionrole",
+ LinkFromAttributeName = "connectionroleid",
+ LinkToEntityName = "connectionroleassociation",
+ LinkToAttributeName = "connectionroleid",
+ LinkCriteria = new FilterExpression
+ {
+ FilterOperator = LogicalOperator.And,
+ Conditions =
+ {
+ new ConditionExpression
+ {
+ AttributeName = "associatedconnectionroleid",
+ Operator = ConditionOperator.Equal,
+ Values = { reciprocalConnectionRoleId }
+ }
+ }
+ }
+ }
+ }
+ };
+
+ EntityCollection results = service.RetrieveMultiple(query);
+
+ Console.WriteLine("Retrieved {0} connection role(s) with reciprocal role association.", results.Entities.Count);
+ Console.WriteLine();
+
+ foreach (Entity role in results.Entities)
+ {
+ Console.WriteLine("Connection Role ID: {0}", role["connectionroleid"]);
+ Console.WriteLine("Connection Role Name: {0}", role["name"]);
+ Console.WriteLine();
+ }
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+
+ // Delete in reverse order to handle dependencies
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryByReciprocalRole/QueryByReciprocalRole.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByReciprocalRole/QueryByReciprocalRole.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByReciprocalRole/QueryByReciprocalRole.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryByReciprocalRole/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByReciprocalRole/README.md
new file mode 100644
index 00000000..1b5af3e0
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByReciprocalRole/README.md
@@ -0,0 +1,91 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates querying connection roles by reciprocal role"
+---
+
+# QueryByReciprocalRole
+
+Demonstrates querying connection roles by reciprocal role
+
+## What this sample does
+
+This sample shows how to:
+- Create connection roles with reciprocal associations
+- Query for connection roles using the connectionroleassociation relationship
+- Use QueryExpression with LinkEntity to filter by associated connection role
+- Work with connection role object type codes
+
+Connection roles define the types of relationships that can exist between records in Dataverse. Reciprocal roles define the inverse relationship (e.g., "Manager" and "Report").
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates a primary connection role ("Example Primary Connection Role")
+2. Creates a connection role object type code associating the primary role with the Account entity
+3. Creates a reciprocal connection role ("Example Reciprocal Connection Role")
+4. Creates a connection role object type code associating the reciprocal role with the Account entity
+5. Associates the primary and reciprocal roles using the connectionroleassociation relationship
+
+### Run
+
+The main demonstration:
+1. Creates a QueryExpression for the "connectionrole" entity
+2. Adds a LinkEntity to join with the "connectionroleassociation" entity
+3. Filters by the reciprocal connection role ID in the association
+4. Executes RetrieveMultiple to retrieve all connection roles that have the specified reciprocal role
+5. Displays the retrieved connection role IDs and names
+
+### Cleanup
+
+The cleanup process deletes all created connection roles and connection role object type codes in reverse order to handle dependencies.
+
+## Demonstrates
+
+This sample demonstrates:
+- **Connection Roles**: Creating and configuring connection roles
+- **Connection Role Associations**: Associating connection roles as reciprocals
+- **AssociateRequest**: Using the Associate message to create many-to-many relationships
+- **QueryExpression with LinkEntity**: Querying across related entities
+- **Connection Role Object Type Codes**: Defining which entity types a role applies to
+- **Entity-based syntax**: Using late-bound Entity objects with string-based attribute access
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating sample data...
+Created primary connection role: Example Primary Connection Role
+Created Connection Role Object Type Code for Account on primary role.
+Created reciprocal connection role: Example Reciprocal Connection Role
+Created Connection Role Object Type Code for Account on reciprocal role.
+Associated primary and reciprocal connection roles.
+Setup complete.
+
+Querying for connection roles by reciprocal role...
+
+Retrieved 1 connection role(s) with reciprocal role association.
+
+Connection Role ID:
+Connection Role Name: Example Primary Connection Role
+
+Cleaning up...
+Deleting 4 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Connection roles](https://learn.microsoft.com/power-apps/developer/data-platform/connection-entities)
+[Build queries with QueryExpression](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/build-queries-with-queryexpression)
+[Join tables using QueryExpression](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/join-tables-using-queryexpression)
+[Use the Associate message](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-associate-disassociate)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryByRecord/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByRecord/Program.cs
new file mode 100644
index 00000000..578ffa7f
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByRecord/Program.cs
@@ -0,0 +1,256 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates querying connections for a specific record
+ ///
+ ///
+ /// This sample shows how to query connection records to find all connections
+ /// that a specific entity record is part of using QueryExpression.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Define connection categories
+ var Categories = new
+ {
+ Business = 1,
+ Family = 2,
+ Social = 3,
+ Sales = 4,
+ Other = 5
+ };
+
+ // Create a Connection Role
+ var setupConnectionRole = new Entity("connectionrole")
+ {
+ ["name"] = "Example Connection Role",
+ ["description"] = "This is an example one sided connection role.",
+ ["category"] = new OptionSetValue(Categories.Business)
+ };
+
+ Guid connectionRoleId = service.Create(setupConnectionRole);
+ entityStore.Add(new EntityReference("connectionrole", connectionRoleId));
+ Console.WriteLine("Created connection role: {0}", setupConnectionRole["name"]);
+
+ // Create a related Connection Role Object Type Code record for Account
+ var newAccountConnectionRoleTypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", connectionRoleId),
+ ["associatedobjecttypecode"] = "account"
+ };
+
+ Guid accountTypeCodeId = service.Create(newAccountConnectionRoleTypeCode);
+ entityStore.Add(new EntityReference("connectionroleobjecttypecode", accountTypeCodeId));
+ Console.WriteLine("Created a related Connection Role Object Type Code record for Account.");
+
+ // Create a related Connection Role Object Type Code record for Contact
+ var newContactConnectionRoleTypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", connectionRoleId),
+ ["associatedobjecttypecode"] = "contact"
+ };
+
+ Guid contactTypeCodeId = service.Create(newContactConnectionRoleTypeCode);
+ entityStore.Add(new EntityReference("connectionroleobjecttypecode", contactTypeCodeId));
+ Console.WriteLine("Created a related Connection Role Object Type Code record for Contact.");
+
+ // Create a few account records for use in the connections
+ var setupAccount1 = new Entity("account")
+ {
+ ["name"] = "Example Account 1"
+ };
+ Guid account1Id = service.Create(setupAccount1);
+ entityStore.Add(new EntityReference("account", account1Id));
+ Console.WriteLine("Created {0}.", setupAccount1["name"]);
+
+ var setupAccount2 = new Entity("account")
+ {
+ ["name"] = "Example Account 2"
+ };
+ Guid account2Id = service.Create(setupAccount2);
+ entityStore.Add(new EntityReference("account", account2Id));
+ Console.WriteLine("Created {0}.", setupAccount2["name"]);
+
+ // Create a contact used in the connection
+ var setupContact = new Entity("contact")
+ {
+ ["lastname"] = "Example Contact"
+ };
+ Guid contactId = service.Create(setupContact);
+ entityStore.Add(new EntityReference("contact", contactId));
+ Console.WriteLine("Created contact: {0}.", setupContact["lastname"]);
+
+ // Create a new connection between Account 1 and the contact record
+ var newConnection1 = new Entity("connection")
+ {
+ ["record1id"] = new EntityReference("account", account1Id),
+ ["record1roleid"] = new EntityReference("connectionrole", connectionRoleId),
+ ["record2id"] = new EntityReference("contact", contactId)
+ };
+
+ Guid connection1Id = service.Create(newConnection1);
+ entityStore.Add(new EntityReference("connection", connection1Id));
+ Console.WriteLine("Created a connection between account 1 and the contact.");
+
+ // Create a new connection between the contact and Account 2 record
+ var newConnection2 = new Entity("connection")
+ {
+ ["record1id"] = new EntityReference("contact", contactId),
+ ["record1roleid"] = new EntityReference("connectionrole", connectionRoleId),
+ ["record2id"] = new EntityReference("account", account2Id)
+ };
+
+ Guid connection2Id = service.Create(newConnection2);
+ entityStore.Add(new EntityReference("connection", connection2Id));
+ Console.WriteLine("Created a connection between the contact and account 2.");
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Query Connections By Record");
+ Console.WriteLine("===============================");
+
+ // Get the contact ID from our entity store
+ // The contact is at index 5 (after connection role, 2 type codes, and 2 accounts)
+ Guid contactId = entityStore[5].Id;
+
+ // This query retrieves all connections this contact is part of
+ var query = new QueryExpression
+ {
+ EntityName = "connection",
+ ColumnSet = new ColumnSet("connectionid"),
+ Criteria = new FilterExpression
+ {
+ FilterOperator = LogicalOperator.And,
+ Conditions =
+ {
+ // You can safely query against only record1id or
+ // record2id - Dataverse will find all connections this
+ // entity is a part of either way.
+ new ConditionExpression
+ {
+ AttributeName = "record1id",
+ Operator = ConditionOperator.Equal,
+ Values = { contactId }
+ }
+ }
+ }
+ };
+
+ EntityCollection results = service.RetrieveMultiple(query);
+
+ // Here you could do a variety of tasks with the
+ // connections retrieved, such as listing the connected entities,
+ // finding reciprocal connections, etc.
+
+ Console.WriteLine("Retrieved {0} connection instances for the contact.", results.Entities.Count);
+
+ foreach (var connection in results.Entities)
+ {
+ Console.WriteLine(" Connection ID: {0}", connection.Id);
+ }
+
+ Console.WriteLine("===============================");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ // Delete in reverse order to handle dependencies
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryByRecord/QueryByRecord.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByRecord/QueryByRecord.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByRecord/QueryByRecord.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryByRecord/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByRecord/README.md
new file mode 100644
index 00000000..e7e6a8a3
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryByRecord/README.md
@@ -0,0 +1,102 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates querying connections for a specific record"
+---
+
+# QueryByRecord
+
+Demonstrates querying connections for a specific record
+
+## What this sample does
+
+This sample shows how to:
+- Query connection records using QueryExpression to find all connections a specific entity record is part of
+- Create connection roles and associated object type codes
+- Create connections between different entity records
+- Use QueryExpression with FilterExpression to filter by record ID
+
+Connection records link two records together in Dataverse. This sample demonstrates how to query connections to find all related records, regardless of whether the target record is in the record1id or record2id position.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates a connection role with "Business" category
+2. Creates connection role object type codes for both Account and Contact entities
+3. Creates two account records ("Example Account 1" and "Example Account 2")
+4. Creates a contact record ("Example Contact")
+5. Creates a connection between Account 1 and the Contact
+6. Creates a connection between the Contact and Account 2
+
+### Run
+
+The main demonstration:
+1. Retrieves the contact ID from the entity store
+2. Creates a QueryExpression to query the "connection" entity
+3. Adds a filter condition on "record1id" to find connections where the contact is record1
+4. Executes RetrieveMultiple to retrieve matching connection records
+5. Displays the count and IDs of all connections found
+
+**Important**: Dataverse automatically creates reciprocal connection records, so querying against just record1id will find all connections the entity is part of, whether it's in the record1 or record2 position.
+
+### Cleanup
+
+The cleanup process deletes all created records in reverse order to handle dependencies:
+- Connection records
+- Contact record
+- Account records
+- Connection role object type codes
+- Connection role
+
+## Demonstrates
+
+This sample demonstrates:
+- **QueryExpression**: Building queries with criteria and column sets
+- **FilterExpression**: Adding filter conditions to queries
+- **ConditionExpression**: Specifying attribute-based filter criteria
+- **Connection Entity**: Working with connection records to link entities
+- **ConnectionRole**: Creating and using connection roles
+- **EntityReference**: Using late-bound entity references
+- **RetrieveMultiple**: Executing queries to retrieve multiple records
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Setting up sample data...
+Created connection role: Example Connection Role
+Created a related Connection Role Object Type Code record for Account.
+Created a related Connection Role Object Type Code record for Contact.
+Created Example Account 1.
+Created Example Account 2.
+Created contact: Example Contact.
+Created a connection between account 1 and the contact.
+Created a connection between the contact and account 2.
+Setup complete.
+
+Query Connections By Record
+===============================
+Retrieved 2 connection instances for the contact.
+ Connection ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+ Connection ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
+===============================
+Cleaning up...
+Deleting 8 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Connection entities](https://learn.microsoft.com/power-apps/developer/data-platform/connection-entities)
+[Build queries with QueryExpression](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/build-queries-with-queryexpression)
+[Query data using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-query-data)
+[Create connections](https://learn.microsoft.com/power-apps/developer/data-platform/configure-connection-roles)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryHoursMultipleUsers/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/QueryHoursMultipleUsers/Program.cs
new file mode 100644
index 00000000..8d739daa
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryHoursMultipleUsers/Program.cs
@@ -0,0 +1,213 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates querying the working hours of multiple users
+ ///
+ ///
+ /// This sample shows how to retrieve the working hours of multiple users
+ /// by using the QueryMultipleSchedulesRequest message.
+ ///
+ /// IMPORTANT: This sample requires a user manually created in your environment:
+ /// First Name: Kevin
+ /// Last Name: Cook
+ /// Security Role: Sales Manager
+ /// UserName: kcook@yourorg.onmicrosoft.com
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ // Store user IDs used in the sample
+ private static Guid _currentUserId;
+ private static Guid _otherUserId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Get the current user's information
+ var userRequest = new WhoAmIRequest();
+ var userResponse = (WhoAmIResponse)service.Execute(userRequest);
+ _currentUserId = userResponse.UserId;
+ Console.WriteLine($"Current User ID: {_currentUserId}");
+
+ // Try to retrieve the manually created user (Kevin Cook, Sales Manager)
+ _otherUserId = RetrieveUserByName(service, "Kevin", "Cook");
+
+ if (_otherUserId == Guid.Empty)
+ {
+ throw new Exception(
+ "Required user not found. Please manually create a user in your environment:\n" +
+ "First Name: Kevin\n" +
+ "Last Name: Cook\n" +
+ "Security Role: Sales Manager\n" +
+ "UserName: kcook@yourorg.onmicrosoft.com");
+ }
+
+ Console.WriteLine($"Found user Kevin Cook with ID: {_otherUserId}");
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Querying working hours of multiple users...");
+ Console.WriteLine();
+
+ // Create the request to query multiple schedules
+ var scheduleRequest = new QueryMultipleSchedulesRequest
+ {
+ // Specify the resource IDs (users) to query
+ ResourceIds = new Guid[] { _currentUserId, _otherUserId },
+
+ // Set the time range for the query (now to 7 days from now)
+ Start = DateTime.Now,
+ End = DateTime.Today.AddDays(7),
+
+ // Specify we want available time blocks
+ TimeCodes = new TimeCode[] { TimeCode.Available }
+ };
+
+ // Execute the request
+ var scheduleResponse = (QueryMultipleSchedulesResponse)service.Execute(scheduleRequest);
+
+ // Verify if some data is returned for the availability of the users
+ if (scheduleResponse.TimeInfos.Length > 0)
+ {
+ Console.WriteLine($"Successfully queried the working hours of {scheduleRequest.ResourceIds.Length} users.");
+ Console.WriteLine($"Retrieved {scheduleResponse.TimeInfos.Length} time slot(s) with availability information.");
+ }
+ else
+ {
+ Console.WriteLine("No available time slots found for the users in the next 7 days.");
+ Console.WriteLine("This may indicate that the users have no working hours configured.");
+ }
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+
+ // Note: This sample does not create any records that need to be deleted.
+ // It only queries existing user working hours information.
+
+ Console.WriteLine("No records to delete.");
+ }
+
+ #endregion
+
+ #region Helper Methods
+
+ ///
+ /// Retrieves a user by first and last name
+ ///
+ /// The service client
+ /// The user's first name
+ /// The user's last name
+ /// The user's ID, or Guid.Empty if not found
+ private static Guid RetrieveUserByName(ServiceClient service, string firstName, string lastName)
+ {
+ // Query for the user by first and last name
+ var userQuery = new QueryExpression("systemuser")
+ {
+ ColumnSet = new ColumnSet("systemuserid", "firstname", "lastname", "domainname"),
+ Criteria = new FilterExpression(LogicalOperator.And)
+ };
+ userQuery.Criteria.AddCondition("firstname", ConditionOperator.Equal, firstName);
+ userQuery.Criteria.AddCondition("lastname", ConditionOperator.Equal, lastName);
+ userQuery.Criteria.AddCondition("isdisabled", ConditionOperator.Equal, false);
+
+ var results = service.RetrieveMultiple(userQuery);
+
+ if (results.Entities.Count > 0)
+ {
+ return results.Entities[0].Id;
+ }
+
+ return Guid.Empty;
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+
+ // Display additional details for common issues
+ if (ex.InnerException != null)
+ {
+ Console.WriteLine("Inner Exception: {0}", ex.InnerException.Message);
+ }
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryHoursMultipleUsers/QueryHoursMultipleUsers.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/QueryHoursMultipleUsers/QueryHoursMultipleUsers.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryHoursMultipleUsers/QueryHoursMultipleUsers.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryHoursMultipleUsers/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/QueryHoursMultipleUsers/README.md
new file mode 100644
index 00000000..39a33026
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryHoursMultipleUsers/README.md
@@ -0,0 +1,57 @@
+# Query the working hours of multiple users
+
+This sample shows how to retrieve the working hours of multiple users by using the [QueryMultipleSchedulesRequest](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.querymultipleschedulesrequest) message.
+
+This sample requires an additional user that is not present in your system. Create the required user manually **as is** shown below in **Office 365** before you run the sample.
+
+**First Name**: Kevin
+**Last Name**: Cook
+**Security Role**: Sales Manager
+**UserName**: kcook@yourorg.onmicrosoft.com
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `QueryMultipleSchedulesRequest` message is intended to be used in a scenario where it contains data that is needed to search multiple resources for available time blocks that match the specified parameters.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Retrieves the current user's information using `WhoAmIRequest`.
+2. Queries for the manually created user (Kevin Cook) by first and last name.
+3. Validates that the required user exists before proceeding.
+
+### Demonstrate
+
+1. Creates a `QueryMultipleSchedulesRequest` with:
+ - Resource IDs for both the current user and Kevin Cook
+ - Time range from now to 7 days in the future
+ - Time code set to `Available` to retrieve available time blocks
+2. Executes the request to retrieve working hours information.
+3. Displays the returned time information, including:
+ - Number of time info records
+ - Start and end times for each available block
+ - Resource (user) information
+
+### Clean up
+
+This sample does not create any records that need to be deleted. It only queries existing user working hours information.
+
+## Key Concepts
+
+- **QueryMultipleSchedulesRequest**: Used to query available time blocks for multiple users simultaneously
+- **TimeCode.Available**: Specifies that we want to retrieve available time slots
+- **Working Hours**: The query returns information about when users are scheduled to be available for work
+- **Resource Scheduling**: This functionality is useful for scheduling meetings, appointments, or resource allocation
+
+## See Also
+
+[Query schedules](https://learn.microsoft.com/dynamics365/customerengagement/on-premises/developer/schedule-collections-appointments)
+[QueryMultipleSchedulesRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.querymultipleschedulesrequest)
+[Service calendar and scheduling](https://learn.microsoft.com/dynamics365/customer-service/basics-service-service-scheduling)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryWorkingHours/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/QueryWorkingHours/Program.cs
new file mode 100644
index 00000000..640cd4af
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryWorkingHours/Program.cs
@@ -0,0 +1,166 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates querying working hours schedules for users
+ ///
+ ///
+ /// This sample shows how to use QueryScheduleRequest to retrieve
+ /// the working hours and availability of a user in Dataverse.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ // No setup required for this sample
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Querying working hours for current user...");
+ Console.WriteLine();
+
+ // Get the current user's information
+ var userRequest = new WhoAmIRequest();
+ var userResponse = (WhoAmIResponse)service.Execute(userRequest);
+
+ Console.WriteLine($"Current User ID: {userResponse.UserId}");
+ Console.WriteLine();
+
+ // Retrieve the working hours of the current user
+ var scheduleRequest = new QueryScheduleRequest
+ {
+ ResourceId = userResponse.UserId,
+ Start = DateTime.Now,
+ End = DateTime.Today.AddDays(7),
+ TimeCodes = new TimeCode[] { TimeCode.Available }
+ };
+
+ var scheduleResponse = (QueryScheduleResponse)service.Execute(scheduleRequest);
+
+ // Display the results
+ if (scheduleResponse.TimeInfos.Length > 0)
+ {
+ Console.WriteLine("Successfully queried the working hours of the current user.");
+ Console.WriteLine($"Found {scheduleResponse.TimeInfos.Length} time slot(s) with availability.");
+ Console.WriteLine();
+
+ // Display first few time slots as examples
+ int displayCount = Math.Min(5, scheduleResponse.TimeInfos.Length);
+ Console.WriteLine($"Displaying first {displayCount} available time slot(s):");
+ Console.WriteLine();
+
+ for (int i = 0; i < displayCount; i++)
+ {
+ var timeInfo = scheduleResponse.TimeInfos[i];
+ Console.WriteLine($"Time Slot {i + 1}:");
+ Console.WriteLine($" Start: {timeInfo.Start}");
+ Console.WriteLine($" End: {timeInfo.End}");
+ Console.WriteLine($" Time Code: {timeInfo.TimeCode}");
+ Console.WriteLine($" Sub Code: {timeInfo.SubCode}");
+ Console.WriteLine();
+ }
+
+ if (scheduleResponse.TimeInfos.Length > displayCount)
+ {
+ Console.WriteLine($"... and {scheduleResponse.TimeInfos.Length - displayCount} more time slot(s)");
+ Console.WriteLine();
+ }
+ }
+ else
+ {
+ Console.WriteLine("No available time slots found for the current user in the next 7 days.");
+ Console.WriteLine("This may indicate that the user has no working hours configured.");
+ Console.WriteLine();
+ }
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ // No records created in this sample, nothing to clean up
+ Console.WriteLine("No records to delete.");
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ if (ex.InnerException != null)
+ {
+ Console.WriteLine("Inner Exception: {0}", ex.InnerException.Message);
+ }
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryWorkingHours/QueryWorkingHours.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/QueryWorkingHours/QueryWorkingHours.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryWorkingHours/QueryWorkingHours.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/QueryWorkingHours/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/QueryWorkingHours/README.md
new file mode 100644
index 00000000..b66ddf9d
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/QueryWorkingHours/README.md
@@ -0,0 +1,119 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates querying working hours schedules for users in Dataverse"
+---
+
+# QueryWorkingHours
+
+Demonstrates querying working hours schedules for users in Dataverse
+
+## What this sample does
+
+This sample shows how to:
+- Use WhoAmIRequest to get the current user's information
+- Use QueryScheduleRequest to retrieve working hours and availability
+- Query available time slots for a specific time period
+- Process and display TimeInfo results from schedule queries
+
+QueryScheduleRequest enables applications to retrieve calendar and schedule information, which is useful for appointment scheduling, resource allocation, and availability checking.
+
+## How this sample works
+
+### Setup
+
+No setup is required for this sample. It queries the working hours of the currently authenticated user.
+
+### Run
+
+The main demonstration:
+1. Executes WhoAmIRequest to get the current user's ID
+2. Creates a QueryScheduleRequest with:
+ - ResourceId set to the current user's ID
+ - Start time set to now
+ - End time set to 7 days from now
+ - TimeCodes set to Available
+3. Executes the QueryScheduleRequest
+4. Displays available time slots, including:
+ - Start and end times
+ - Time codes (Available, Busy, etc.)
+ - Sub codes for additional detail
+5. Shows the first 5 available time slots as examples
+
+### Cleanup
+
+No cleanup is required. This sample does not create any records.
+
+## Demonstrates
+
+This sample demonstrates:
+- **WhoAmIRequest**: Getting the current authenticated user's information
+- **QueryScheduleRequest**: Querying calendar and schedule data
+- **TimeCode enumeration**: Specifying which types of time slots to retrieve
+- **TimeInfo processing**: Working with schedule query results
+- **Calendar/Schedule queries**: Retrieving working hours and availability information
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Setup complete.
+
+Querying working hours for current user...
+
+Current User ID: 12345678-1234-1234-1234-123456789012
+
+Successfully queried the working hours of the current user.
+Found 35 time slot(s) with availability.
+
+Displaying first 5 available time slot(s):
+
+Time Slot 1:
+ Start: 2/6/2026 9:00:00 AM
+ End: 2/6/2026 5:00:00 PM
+ Time Code: Available
+ Sub Code: Unspecified
+
+Time Slot 2:
+ Start: 2/7/2026 9:00:00 AM
+ End: 2/7/2026 5:00:00 PM
+ Time Code: Available
+ Sub Code: Unspecified
+
+Time Slot 3:
+ Start: 2/10/2026 9:00:00 AM
+ End: 2/10/2026 5:00:00 PM
+ Time Code: Available
+ Sub Code: Unspecified
+
+Time Slot 4:
+ Start: 2/11/2026 9:00:00 AM
+ End: 2/11/2026 5:00:00 PM
+ Time Code: Available
+ Sub Code: Unspecified
+
+Time Slot 5:
+ Start: 2/12/2026 9:00:00 AM
+ End: 2/12/2026 5:00:00 PM
+ Time Code: Available
+ Sub Code: Unspecified
+
+... and 30 more time slot(s)
+
+Cleaning up...
+No records to delete.
+
+Press any key to exit.
+```
+
+## See also
+
+[QueryScheduleRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.queryschedulerequest)
+[Schedule and appointment entities](https://learn.microsoft.com/power-apps/developer/data-platform/schedule-appointment-entities)
+[Service Scheduling entities](https://learn.microsoft.com/power-apps/developer/data-platform/schedule-collections-appointments-resources-services)
+[WhoAmIRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.whoamirequest)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/README.md
new file mode 100644
index 00000000..af0314ca
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/README.md
@@ -0,0 +1,58 @@
+# Query
+
+Samples demonstrating various query methods in Dataverse including QueryExpression, FetchXML, and LINQ.
+
+These samples show how to retrieve data using different query APIs, implement paging, use aggregation functions, work with saved queries, and query specialized data like intersect tables and working hours.
+
+More information: [Query data using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-query-data)
+
+## Samples
+
+|Sample folder|Description|Build target|
+|---|---|---|
+|Convertqueriesfetchqueryexpressions|Convert queries between FetchXML and QueryExpression|.NET 6|
+|QueriesUsingLINQ|Query data using LINQ|.NET 6|
+|RetrieveMultipleByQueryExpression|Retrieve multiple records using QueryExpression|.NET 6|
+|RetrieveMultipleQueryByAttribute|Retrieve multiple records using QueryByAttribute|.NET 6|
+|RetrieveRecordsFromIntersectTable|Query many-to-many relationship intersect tables|.NET 6|
+|UseAggregationInFetchXML|Use aggregate functions in FetchXML queries|.NET 6|
+|UseFetchXMLWithPaging|Implement paging with FetchXML|.NET 6|
+|UseQueryExpressionwithPaging|Implement paging with QueryExpression|.NET 6|
+|ValidateandExecuteSavedQuery|Work with saved queries (views)|.NET 6|
+|QueryByReciprocalRole|Query connection records by reciprocal role|.NET 6|
+|QueryByRecord|Query connections for specific records|.NET 6|
+|QueryHoursMultipleUsers|Query working hours for multiple users|.NET 6|
+|QueryWorkingHours|Query working hours schedules|.NET 6|
+|ExportDataUsingFetchXmlToAnnotation|Export query results to annotations|.NET 6|
+
+## Prerequisites
+
+- Visual Studio 2022 or later
+- .NET 6.0 SDK or later
+- Access to a Dataverse environment
+
+## How to run samples
+
+1. Clone the PowerApps-Samples repository
+2. Navigate to `dataverse/orgsvc/CSharp-NETCore/Query/`
+3. Open the desired sample folder
+4. Edit the `appsettings.json` file (located in the Query folder) with your environment connection details:
+ ```json
+ {
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+ }
+ ```
+5. Build and run the sample:
+ ```bash
+ cd SampleFolder
+ dotnet run
+ ```
+
+## See also
+
+[Query data using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-query-data)
+[Use FetchXML to construct a query](https://learn.microsoft.com/power-apps/developer/data-platform/use-fetchxml-construct-query)
+[Build queries with QueryExpression](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/build-queries-with-queryexpression)
+[Query data using LINQ](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/query-data-using-linq)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleByQueryExpression/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleByQueryExpression/Program.cs
new file mode 100644
index 00000000..1e52c9df
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleByQueryExpression/Program.cs
@@ -0,0 +1,188 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates querying data using QueryExpression with linked entities
+ ///
+ ///
+ /// This sample shows how to use QueryExpression with LinkEntity to retrieve
+ /// data from related entities using entity aliases and aliased values.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating sample data...");
+
+ // Create a contact
+ var contact = new Entity("contact")
+ {
+ ["firstname"] = "ContactFirstName",
+ ["lastname"] = "ContactLastName"
+ };
+ Guid contactId = service.Create(contact);
+ entityStore.Add(new EntityReference("contact", contactId));
+
+ // Create multiple accounts with the same primary contact
+ var account1 = new Entity("account")
+ {
+ ["name"] = "Test Account1",
+ ["primarycontactid"] = new EntityReference("contact", contactId)
+ };
+ Guid accountId1 = service.Create(account1);
+ entityStore.Add(new EntityReference("account", accountId1));
+
+ var account2 = new Entity("account")
+ {
+ ["name"] = "Test Account2",
+ ["primarycontactid"] = new EntityReference("contact", contactId)
+ };
+ Guid accountId2 = service.Create(account2);
+ entityStore.Add(new EntityReference("account", accountId2));
+
+ var account3 = new Entity("account")
+ {
+ ["name"] = "Test Account3",
+ ["primarycontactid"] = new EntityReference("contact", contactId)
+ };
+ Guid accountId3 = service.Create(account3);
+ entityStore.Add(new EntityReference("account", accountId3));
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Entering: RetrieveMultipleWithRelatedEntityColumns");
+ Console.WriteLine();
+
+ // Create a query expression with link entity
+ var qe = new QueryExpression
+ {
+ EntityName = "account",
+ ColumnSet = new ColumnSet("name")
+ };
+
+ // Add link to contact entity
+ var linkEntity = new LinkEntity(
+ "account",
+ "contact",
+ "primarycontactid",
+ "contactid",
+ JoinOperator.Inner
+ );
+ linkEntity.Columns.AddColumns("firstname", "lastname");
+ linkEntity.EntityAlias = "primarycontact";
+ qe.LinkEntities.Add(linkEntity);
+
+ // Execute query
+ EntityCollection ec = service.RetrieveMultiple(qe);
+
+ Console.WriteLine("Retrieved {0} entities", ec.Entities.Count);
+ Console.WriteLine();
+
+ foreach (Entity act in ec.Entities)
+ {
+ Console.WriteLine("Account name: {0}", act["name"]);
+ Console.WriteLine("Primary contact first name: {0}",
+ act.GetAttributeValue("primarycontact.firstname").Value);
+ Console.WriteLine("Primary contact last name: {0}",
+ act.GetAttributeValue("primarycontact.lastname").Value);
+ Console.WriteLine();
+ }
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleByQueryExpression/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleByQueryExpression/README.md
new file mode 100644
index 00000000..8bae1d40
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleByQueryExpression/README.md
@@ -0,0 +1,91 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates querying data using QueryExpression with linked entities"
+---
+
+# RetrieveMultipleByQueryExpression
+
+Demonstrates querying data using QueryExpression with linked entities
+
+## What this sample does
+
+This sample shows how to:
+- Use QueryExpression to build complex queries
+- Add LinkEntity to join related entities
+- Use entity aliases to identify columns from related entities
+- Retrieve and access aliased values from linked entities
+
+QueryExpression provides a powerful object model for building complex queries with joins, filters, and sorting.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates a contact record (ContactFirstName ContactLastName)
+2. Creates three account records, each with the contact as primary contact
+
+### Run
+
+The main demonstration:
+1. Creates a QueryExpression for the "account" entity
+2. Adds a LinkEntity to join with the "contact" entity via primarycontactid
+3. Specifies "primarycontact" as the entity alias for the linked entity
+4. Retrieves columns from both account and linked contact
+5. Executes RetrieveMultiple to get all matching records
+6. Displays account names and primary contact information using AliasedValue
+
+### Cleanup
+
+The cleanup process deletes all created accounts and contacts.
+
+## Demonstrates
+
+This sample demonstrates:
+- **QueryExpression**: Building complex queries using the object model
+- **LinkEntity**: Joining related entities in queries
+- **Entity aliases**: Identifying columns from linked entities
+- **AliasedValue**: Accessing columns from linked entities in results
+- **JoinOperator**: Using inner joins in queries
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating sample data...
+Setup complete.
+
+Entering: RetrieveMultipleWithRelatedEntityColumns
+
+Retrieved 3 entities
+
+Account name: Test Account1
+Primary contact first name: ContactFirstName
+Primary contact last name: ContactLastName
+
+Account name: Test Account2
+Primary contact first name: ContactFirstName
+Primary contact last name: ContactLastName
+
+Account name: Test Account3
+Primary contact first name: ContactFirstName
+Primary contact last name: ContactLastName
+
+Cleaning up...
+Deleting 4 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Build queries with QueryExpression](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/build-queries-with-queryexpression)
+[Join tables using QueryExpression](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/join-tables-using-queryexpression)
+[Query data using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-query-data)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleByQueryExpression/RetrieveMultipleByQueryExpression.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleByQueryExpression/RetrieveMultipleByQueryExpression.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleByQueryExpression/RetrieveMultipleByQueryExpression.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleQueryByAttribute/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleQueryByAttribute/Program.cs
new file mode 100644
index 00000000..08936f61
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleQueryByAttribute/Program.cs
@@ -0,0 +1,165 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates querying data using QueryByAttribute
+ ///
+ ///
+ /// This sample shows how to use QueryByAttribute to query records based on attribute values.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating sample accounts...");
+
+ var account1 = new Entity("account")
+ {
+ ["name"] = "A. Datum Corporation",
+ ["address1_stateorprovince"] = "Colorado",
+ ["address1_telephone1"] = "(206)555-5555",
+ ["emailaddress1"] = "info@datum.com"
+ };
+ Guid account1Id = service.Create(account1);
+ entityStore.Add(new EntityReference("account", account1Id));
+
+ var account2 = new Entity("account")
+ {
+ ["name"] = "Adventure Works Cycle",
+ ["address1_stateorprovince"] = "Washington",
+ ["address1_city"] = "Redmond",
+ ["address1_telephone1"] = "(206)555-5555",
+ ["emailaddress1"] = "contactus@adventureworkscycle.com"
+ };
+ Guid account2Id = service.Create(account2);
+ entityStore.Add(new EntityReference("account", account2Id));
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Query Using QueryByAttribute");
+ Console.WriteLine("===============================");
+
+ // Create query using QueryByAttribute
+ var querybyattribute = new QueryByAttribute("account");
+ querybyattribute.ColumnSet = new ColumnSet("name", "address1_city", "emailaddress1");
+
+ // Attribute to query
+ querybyattribute.Attributes.AddRange("address1_city");
+
+ // Value of queried attribute to return
+ querybyattribute.Values.AddRange("Redmond");
+
+ // Execute query
+ EntityCollection retrieved = service.RetrieveMultiple(querybyattribute);
+
+ // Iterate through returned collection
+ foreach (var account in retrieved.Entities)
+ {
+ Console.WriteLine("Name: " + account["name"]);
+
+ if (account.Contains("address1_city"))
+ Console.WriteLine("Address: " + account["address1_city"]);
+
+ if (account.Contains("emailaddress1"))
+ Console.WriteLine("E-mail: " + account["emailaddress1"]);
+ }
+ Console.WriteLine("===============================");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleQueryByAttribute/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleQueryByAttribute/README.md
new file mode 100644
index 00000000..a85033f7
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleQueryByAttribute/README.md
@@ -0,0 +1,79 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates querying data using QueryByAttribute"
+---
+
+# RetrieveMultipleQueryByAttribute
+
+Demonstrates querying data using QueryByAttribute
+
+## What this sample does
+
+This sample shows how to:
+- Use QueryByAttribute to query records based on attribute values
+- Specify columns to return using ColumnSet
+- Filter records by attribute name and value pairs
+- Iterate through query results
+
+QueryByAttribute provides a simple way to query records when you need to filter by specific attribute values without building complex QueryExpression criteria.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates account "A. Datum Corporation" with address in Colorado
+2. Creates account "Adventure Works Cycle" with address in Redmond, Washington
+
+### Run
+
+The main demonstration:
+1. Creates a QueryByAttribute for the "account" entity
+2. Specifies columns to return: name, address1_city, emailaddress1
+3. Adds filter criteria: address1_city = "Redmond"
+4. Executes RetrieveMultiple to get matching records
+5. Displays account information for all matching records
+
+### Cleanup
+
+The cleanup process deletes all created accounts.
+
+## Demonstrates
+
+This sample demonstrates:
+- **QueryByAttribute**: Simple attribute-based query construction
+- **ColumnSet**: Specifying which columns to retrieve
+- **RetrieveMultiple**: Executing queries to retrieve multiple records
+- **EntityCollection**: Working with query result collections
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating sample accounts...
+Setup complete.
+
+Query Using QueryByAttribute
+===============================
+Name: Adventure Works Cycle
+Address: Redmond
+E-mail: contactus@adventureworkscycle.com
+===============================
+Cleaning up...
+Deleting 2 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Query data using QueryByAttribute](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/use-querybyattribute-class)
+[Build queries with QueryExpression](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/build-queries-with-queryexpression)
+[Query data using the SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-query-data)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleQueryByAttribute/RetrieveMultipleQueryByAttribute.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleQueryByAttribute/RetrieveMultipleQueryByAttribute.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveMultipleQueryByAttribute/RetrieveMultipleQueryByAttribute.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveRecordsFromIntersectTable/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveRecordsFromIntersectTable/Program.cs
new file mode 100644
index 00000000..4536b4d6
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveRecordsFromIntersectTable/Program.cs
@@ -0,0 +1,347 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Query;
+using System.Text;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates retrieving records from an intersect table (many-to-many relationship)
+ ///
+ ///
+ /// This sample shows three different approaches to querying intersect tables:
+ /// 1. QueryExpression with LinkEntity
+ /// 2. FetchXML with link-entity and intersect="true"
+ /// 3. Direct query of the intersect table
+ ///
+ /// The sample creates a custom role and associates it with the current user,
+ /// then demonstrates how to retrieve the association records from the
+ /// systemuserroles intersect table.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid userId;
+ private static Guid roleId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating sample data...");
+
+ // Retrieve the default business unit needed to create the role
+ var queryDefaultBusinessUnit = new QueryExpression
+ {
+ EntityName = "businessunit",
+ ColumnSet = new ColumnSet("businessunitid"),
+ Criteria = new FilterExpression()
+ };
+
+ // Find the root business unit (parent is null)
+ queryDefaultBusinessUnit.Criteria.AddCondition(
+ "parentbusinessunitid",
+ ConditionOperator.Null);
+
+ EntityCollection businessUnits = service.RetrieveMultiple(queryDefaultBusinessUnit);
+
+ if (businessUnits.Entities.Count == 0)
+ {
+ throw new Exception("No default business unit found.");
+ }
+
+ Entity defaultBusinessUnit = businessUnits.Entities[0];
+ Guid businessUnitId = defaultBusinessUnit.Id;
+
+ // Get the GUID of the current user
+ var whoRequest = new WhoAmIRequest();
+ var whoResponse = (WhoAmIResponse)service.Execute(whoRequest);
+ userId = whoResponse.UserId;
+ Console.WriteLine($"Current User ID: {userId}");
+
+ // Create a custom role
+ var role = new Entity("role")
+ {
+ ["name"] = "ABC Management Role",
+ ["businessunitid"] = new EntityReference("businessunit", businessUnitId)
+ };
+
+ roleId = service.Create(role);
+ entityStore.Add(new EntityReference("role", roleId));
+ Console.WriteLine($"Created Role: {roleId}");
+
+ // Associate the user with the role using the systemuserroles_association relationship
+ var associateRequest = new AssociateRequest
+ {
+ Target = new EntityReference("systemuser", userId),
+ RelatedEntities = new EntityReferenceCollection
+ {
+ new EntityReference("role", roleId)
+ },
+ Relationship = new Relationship("systemuserroles_association")
+ };
+
+ service.Execute(associateRequest);
+ Console.WriteLine($"Associated User {userId} with Role {roleId}");
+ Console.WriteLine();
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("=== Retrieving Records from Intersect Table ===");
+ Console.WriteLine();
+
+ // Approach 1: QueryExpression with LinkEntity
+ Console.WriteLine("Approach 1: QueryExpression with LinkEntity");
+ Console.WriteLine("--------------------------------------------");
+ RetrieveWithQueryExpression(service);
+ Console.WriteLine();
+
+ // Approach 2: FetchXML
+ Console.WriteLine("Approach 2: FetchXML with intersect link-entity");
+ Console.WriteLine("------------------------------------------------");
+ RetrieveWithFetchXML(service);
+ Console.WriteLine();
+
+ // Approach 3: Direct query of intersect table
+ Console.WriteLine("Approach 3: Direct query of intersect table");
+ Console.WriteLine("--------------------------------------------");
+ RetrieveIntersectTableDirectly(service);
+ Console.WriteLine();
+ }
+
+ private static void RetrieveWithQueryExpression(ServiceClient service)
+ {
+ // Create QueryExpression that links from role to systemuserroles intersect table
+ var query = new QueryExpression
+ {
+ EntityName = "role",
+ ColumnSet = new ColumnSet("name")
+ };
+
+ // Add link to the systemuserroles intersect table
+ var linkEntity = new LinkEntity
+ {
+ LinkFromEntityName = "role",
+ LinkFromAttributeName = "roleid",
+ LinkToEntityName = "systemuserroles",
+ LinkToAttributeName = "roleid",
+ LinkCriteria = new FilterExpression
+ {
+ FilterOperator = LogicalOperator.And,
+ Conditions =
+ {
+ new ConditionExpression
+ {
+ AttributeName = "systemuserid",
+ Operator = ConditionOperator.Equal,
+ Values = { userId }
+ }
+ }
+ }
+ };
+ query.LinkEntities.Add(linkEntity);
+
+ // Execute the query
+ EntityCollection results = service.RetrieveMultiple(query);
+
+ // Display results
+ Console.WriteLine($"QueryExpression retrieved {results.Entities.Count} role(s):");
+ foreach (Entity role in results.Entities)
+ {
+ Console.WriteLine($" - Role Name: {role.GetAttributeValue("name")}");
+ }
+ }
+
+ private static void RetrieveWithFetchXML(ServiceClient service)
+ {
+ // Build FetchXML query with intersect link
+ var fetchXml = new StringBuilder();
+ fetchXml.Append("");
+ fetchXml.Append(" ");
+ fetchXml.Append(" ");
+ fetchXml.Append(" ");
+ fetchXml.Append(" ");
+ fetchXml.Append($" ");
+ fetchXml.Append(" ");
+ fetchXml.Append(" ");
+ fetchXml.Append(" ");
+ fetchXml.Append("");
+
+ // Execute the FetchXML query
+ var fetchRequest = new RetrieveMultipleRequest
+ {
+ Query = new FetchExpression(fetchXml.ToString())
+ };
+ var fetchResponse = (RetrieveMultipleResponse)service.Execute(fetchRequest);
+ EntityCollection results = fetchResponse.EntityCollection;
+
+ // Display results
+ Console.WriteLine($"FetchXML retrieved {results.Entities.Count} role(s):");
+ foreach (Entity role in results.Entities)
+ {
+ Console.WriteLine($" - Role Name: {role.GetAttributeValue("name")}");
+ }
+ }
+
+ private static void RetrieveIntersectTableDirectly(ServiceClient service)
+ {
+ // Query the systemuserroles intersect table directly
+ var query = new QueryExpression
+ {
+ EntityName = "systemuserroles",
+ ColumnSet = new ColumnSet("systemuserid", "roleid"),
+ Criteria = new FilterExpression
+ {
+ Conditions =
+ {
+ new ConditionExpression
+ {
+ AttributeName = "systemuserid",
+ Operator = ConditionOperator.Equal,
+ Values = { userId }
+ },
+ new ConditionExpression
+ {
+ AttributeName = "roleid",
+ Operator = ConditionOperator.Equal,
+ Values = { roleId }
+ }
+ }
+ }
+ };
+
+ EntityCollection results = service.RetrieveMultiple(query);
+
+ // Display results
+ Console.WriteLine($"Direct query retrieved {results.Entities.Count} association(s):");
+ foreach (Entity association in results.Entities)
+ {
+ Guid systemUserId = association.GetAttributeValue("systemuserid");
+ Guid associatedRoleId = association.GetAttributeValue("roleid");
+ Console.WriteLine($" - User ID: {systemUserId}, Role ID: {associatedRoleId}");
+ }
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ // Disassociate the user from the role before deleting
+ try
+ {
+ var disassociateRequest = new DisassociateRequest
+ {
+ Target = new EntityReference("systemuser", userId),
+ RelatedEntities = new EntityReferenceCollection
+ {
+ new EntityReference("role", roleId)
+ },
+ Relationship = new Relationship("systemuserroles_association")
+ };
+ service.Execute(disassociateRequest);
+ Console.WriteLine("Disassociated user from role.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error during disassociation: {ex.Message}");
+ }
+
+ // Delete created records
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ try
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error deleting {entityStore[i].LogicalName} {entityStore[i].Id}: {ex.Message}");
+ }
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ Console.WriteLine();
+ Console.WriteLine("Stack Trace:");
+ Console.WriteLine(ex.StackTrace);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveRecordsFromIntersectTable/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveRecordsFromIntersectTable/README.md
new file mode 100644
index 00000000..0086308b
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveRecordsFromIntersectTable/README.md
@@ -0,0 +1,141 @@
+# Retrieve records from an intersect table
+
+This sample demonstrates how to retrieve records from an intersect table in Microsoft Dataverse. Intersect tables are used to represent many-to-many relationships between entities.
+
+## How to run this sample
+
+1. Clone or download the [PowerApps-Samples](https://github.com/microsoft/PowerApps-Samples) repository.
+
+2. Navigate to the sample directory:
+ ```
+ cd dataverse/orgsvc/CSharp-NETCore/Query/RetrieveRecordsFromIntersectTable
+ ```
+
+3. Update the connection string in `../appsettings.json` with your Dataverse environment details:
+ ```json
+ {
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+ }
+ ```
+
+4. Build and run the sample:
+ ```
+ dotnet run
+ ```
+
+## What this sample does
+
+This sample demonstrates three different approaches for retrieving records from an intersect table:
+
+1. **QueryExpression with LinkEntity** - Uses a QueryExpression to link from the primary entity (role) through the intersect table (systemuserroles) with filter criteria.
+
+2. **FetchXML with intersect link-entity** - Uses FetchXML with the `intersect="true"` attribute to query through the many-to-many relationship.
+
+3. **Direct query of intersect table** - Directly queries the systemuserroles intersect table to retrieve association records.
+
+## How this sample works
+
+### Setup
+
+1. Retrieves the default business unit needed to create the role.
+2. Gets the GUID of the current user using `WhoAmIRequest`.
+3. Creates a custom role named "ABC Management Role".
+4. Associates the current user with the role using the `systemuserroles_association` relationship.
+
+### Demonstrate
+
+The sample demonstrates three different query approaches:
+
+#### 1. QueryExpression with LinkEntity
+Creates a QueryExpression that links from the role entity to the systemuserroles intersect table, filtering by the current user's ID. This approach retrieves role records that are associated with the user.
+
+```csharp
+var query = new QueryExpression
+{
+ EntityName = "role",
+ ColumnSet = new ColumnSet("name")
+};
+
+var linkEntity = new LinkEntity
+{
+ LinkFromEntityName = "role",
+ LinkFromAttributeName = "roleid",
+ LinkToEntityName = "systemuserroles",
+ LinkToAttributeName = "roleid",
+ LinkCriteria = new FilterExpression
+ {
+ Conditions =
+ {
+ new ConditionExpression("systemuserid", ConditionOperator.Equal, userId)
+ }
+ }
+};
+query.LinkEntities.Add(linkEntity);
+```
+
+#### 2. FetchXML with intersect attribute
+Uses FetchXML with `intersect="true"` to query through the many-to-many relationship. This is a more declarative approach.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+#### 3. Direct query of intersect table
+Directly queries the systemuserroles intersect table to retrieve the association records themselves, showing the raw relationship data.
+
+```csharp
+var query = new QueryExpression
+{
+ EntityName = "systemuserroles",
+ ColumnSet = new ColumnSet("systemuserid", "roleid"),
+ Criteria = new FilterExpression
+ {
+ Conditions =
+ {
+ new ConditionExpression("systemuserid", ConditionOperator.Equal, userId),
+ new ConditionExpression("roleid", ConditionOperator.Equal, roleId)
+ }
+ }
+};
+```
+
+### Clean up
+
+1. Disassociates the user from the role using `DisassociateRequest`.
+2. Deletes the custom role that was created.
+
+## Key concepts
+
+### Intersect Tables
+- Intersect tables represent many-to-many relationships in Dataverse
+- They contain only the IDs of the related entities (e.g., systemuserid and roleid)
+- The table name is typically a combination of both entity names (e.g., systemuserroles)
+
+### Querying Intersect Tables
+- **LinkEntity approach**: Best when you need data from the related entities
+- **FetchXML with intersect**: More declarative, easier to construct dynamically
+- **Direct query**: Useful when you need the raw association data or metadata
+
+### Association/Disassociation
+- Use `AssociateRequest` to create relationships
+- Use `DisassociateRequest` to remove relationships
+- The `Relationship` object specifies the schema name (e.g., "systemuserroles_association")
+
+## Related documentation
+
+- [QueryExpression class](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.query.queryexpression)
+- [FetchXML reference](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/overview)
+- [Many-to-many relationships](https://learn.microsoft.com/power-apps/developer/data-platform/create-retrieve-entity-relationships#many-to-many-relationships)
+- [AssociateRequest class](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.messages.associaterequest)
+- [DisassociateRequest class](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.messages.disassociaterequest)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveRecordsFromIntersectTable/RetrieveRecordsFromIntersectTable.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveRecordsFromIntersectTable/RetrieveRecordsFromIntersectTable.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/RetrieveRecordsFromIntersectTable/RetrieveRecordsFromIntersectTable.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/UseAggregationInFetchXML/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/UseAggregationInFetchXML/Program.cs
new file mode 100644
index 00000000..b17e4f42
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/UseAggregationInFetchXML/Program.cs
@@ -0,0 +1,651 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates using aggregation in FetchXML queries
+ ///
+ ///
+ /// This sample shows how to use aggregate functions (sum, avg, count, min, max)
+ /// in FetchXML queries, including grouping and date grouping features.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating sample account and opportunities...");
+
+ var OpportunityStatusCodes = new
+ {
+ Won = 3
+ };
+
+ // Create sample account
+ var setupAccount = new Entity("account")
+ {
+ ["name"] = "Example Account"
+ };
+
+ Guid accountId = service.Create(setupAccount);
+ entityStore.Add(new EntityReference("account", accountId));
+
+ Console.WriteLine("Created {0}.", setupAccount["name"]);
+
+ EntityReference setupCustomer = new EntityReference("account", accountId);
+
+ // Create 3 sample opportunities with different estimated values
+ var setupOpportunities = new[]
+ {
+ new Entity("opportunity")
+ {
+ ["name"] = "Sample Opp 1",
+ ["estimatedvalue"] = new Money(120000.00m),
+ ["customerid"] = setupCustomer
+ },
+ new Entity("opportunity")
+ {
+ ["name"] = "Sample Opp With Duplicate Name",
+ ["estimatedvalue"] = new Money(240000.00m),
+ ["customerid"] = setupCustomer
+ },
+ new Entity("opportunity")
+ {
+ ["name"] = "Sample Opp With Duplicate Name",
+ ["estimatedvalue"] = new Money(360000.00m),
+ ["customerid"] = setupCustomer
+ }
+ };
+
+ // Create opportunities and track their IDs
+ foreach (var opp in setupOpportunities)
+ {
+ Guid oppId = service.Create(opp);
+ entityStore.Add(new EntityReference("opportunity", oppId));
+
+ // Win the opportunity
+ var winRequest = new WinOpportunityRequest
+ {
+ OpportunityClose = new Entity("opportunityclose")
+ {
+ ["opportunityid"] = new EntityReference("opportunity", oppId),
+ // Mark these entities as won in 2009 to have testable results
+ ["actualend"] = new DateTime(2009, 11, 1, 12, 0, 0)
+ },
+ Status = new OptionSetValue(OpportunityStatusCodes.Won)
+ };
+ service.Execute(winRequest);
+ }
+
+ Console.WriteLine("Created {0} sample opportunity records and updated as won for this sample.", setupOpportunities.Length);
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ // *****************************************************************************************************************
+ // FetchXML estimatedvalue_avg Aggregate 1
+ // *****************************************************************************************************************
+ // Fetch the average of estimatedvalue for all opportunities. This is the equivalent of
+ // SELECT AVG(estimatedvalue) AS estimatedvalue_avg ... in SQL.
+ Console.WriteLine("===============================");
+ string estimatedvalue_avg = @"
+
+
+
+
+ ";
+
+ EntityCollection estimatedvalue_avg_result = service.RetrieveMultiple(new FetchExpression(estimatedvalue_avg));
+
+ foreach (var c in estimatedvalue_avg_result.Entities)
+ {
+ decimal aggregate1 = ((Money)((AliasedValue)c["estimatedvalue_avg"]).Value).Value;
+ Console.WriteLine("Average estimated value: " + aggregate1);
+ }
+ Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML opportunity_count Aggregate 2
+ // *****************************************************************************************************************
+ // Fetch the count of all opportunities. This is the equivalent of
+ // SELECT COUNT(*) AS opportunity_count ... in SQL.
+ string opportunity_count = @"
+
+
+
+
+ ";
+
+ EntityCollection opportunity_count_result = service.RetrieveMultiple(new FetchExpression(opportunity_count));
+
+ foreach (var c in opportunity_count_result.Entities)
+ {
+ Int32 aggregate2 = (Int32)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate2);
+ }
+ Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML opportunity_colcount Aggregate 3
+ // *****************************************************************************************************************
+ // Fetch the count of all opportunities. This is the equivalent of
+ // SELECT COUNT(name) AS opportunity_count ... in SQL.
+ string opportunity_colcount = @"
+
+
+
+
+ ";
+
+ EntityCollection opportunity_colcount_result = service.RetrieveMultiple(new FetchExpression(opportunity_colcount));
+
+ foreach (var c in opportunity_colcount_result.Entities)
+ {
+ Int32 aggregate3 = (Int32)((AliasedValue)c["opportunity_colcount"]).Value;
+ Console.WriteLine("Column count of all opportunities: " + aggregate3);
+ }
+ Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML opportunity_distcount Aggregate 4
+ // *****************************************************************************************************************
+ // Fetch the count of distinct names for opportunities. This is the equivalent of
+ // SELECT COUNT(DISTINCT name) AS opportunity_count ... in SQL.
+ string opportunity_distcount = @"
+
+
+
+
+ ";
+
+ EntityCollection opportunity_distcount_result = service.RetrieveMultiple(new FetchExpression(opportunity_distcount));
+
+ foreach (var c in opportunity_distcount_result.Entities)
+ {
+ Int32 aggregate4 = (Int32)((AliasedValue)c["opportunity_distcount"]).Value;
+ Console.WriteLine("Distinct name count of all opportunities: " + aggregate4);
+ }
+ Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML estimatedvalue_max Aggregate 5
+ // *****************************************************************************************************************
+ // Fetch the maximum estimatedvalue of all opportunities. This is the equivalent of
+ // SELECT MAX(estimatedvalue) AS estimatedvalue_max ... in SQL.
+ string estimatedvalue_max = @"
+
+
+
+
+ ";
+
+ EntityCollection estimatedvalue_max_result = service.RetrieveMultiple(new FetchExpression(estimatedvalue_max));
+
+ foreach (var c in estimatedvalue_max_result.Entities)
+ {
+ decimal aggregate5 = ((Money)((AliasedValue)c["estimatedvalue_max"]).Value).Value;
+ Console.WriteLine("Max estimated value of all opportunities: " + aggregate5);
+ }
+ Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML estimatedvalue_min Aggregate 6
+ // *****************************************************************************************************************
+ // Fetch the minimum estimatedvalue of all opportunities. This is the equivalent of
+ // SELECT MIN(estimatedvalue) AS estimatedvalue_min ... in SQL.
+ string estimatedvalue_min = @"
+
+
+
+
+ ";
+
+ EntityCollection estimatedvalue_min_result = service.RetrieveMultiple(new FetchExpression(estimatedvalue_min));
+
+ foreach (var c in estimatedvalue_min_result.Entities)
+ {
+ decimal aggregate6 = ((Money)((AliasedValue)c["estimatedvalue_min"]).Value).Value;
+ Console.WriteLine("Minimum estimated value of all opportunities: " + aggregate6);
+ }
+ Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML estimatedvalue_sum Aggregate 7
+ // *****************************************************************************************************************
+ // Fetch the sum of estimatedvalue for all opportunities. This is the equivalent of
+ // SELECT SUM(estimatedvalue) AS estimatedvalue_sum ... in SQL.
+ string estimatedvalue_sum = @"
+
+
+
+
+ ";
+
+ EntityCollection estimatedvalue_sum_result = service.RetrieveMultiple(new FetchExpression(estimatedvalue_sum));
+
+ foreach (var c in estimatedvalue_sum_result.Entities)
+ {
+ decimal aggregate7 = ((Money)((AliasedValue)c["estimatedvalue_sum"]).Value).Value;
+ Console.WriteLine("Sum of estimated value of all opportunities: " + aggregate7);
+ }
+ Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML estimatedvalue_avg, estimatedvalue_sum Aggregate 8
+ // *****************************************************************************************************************
+ // Fetch multiple aggregate values within a single query.
+ string estimatedvalue_avg2 = @"
+
+
+
+
+
+
+ ";
+
+ EntityCollection estimatedvalue_avg2_result = service.RetrieveMultiple(new FetchExpression(estimatedvalue_avg2));
+
+ foreach (var c in estimatedvalue_avg2_result.Entities)
+ {
+ Int32 aggregate8a = (Int32)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate8a);
+ decimal aggregate8b = ((Money)((AliasedValue)c["estimatedvalue_sum"]).Value).Value;
+ Console.WriteLine("Sum of estimated value of all opportunities: " + aggregate8b);
+ decimal aggregate8c = ((Money)((AliasedValue)c["estimatedvalue_avg"]).Value).Value;
+ Console.WriteLine("Average of estimated value of all opportunities: " + aggregate8c);
+ }
+ System.Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML groupby1 Aggregate 9
+ // *****************************************************************************************************************
+ // Fetch a list of users with a count of all the opportunities they own using groupby.
+ string groupby1 = @"
+
+
+
+
+
+ ";
+
+ EntityCollection groupby1_result = service.RetrieveMultiple(new FetchExpression(groupby1));
+
+ foreach (var c in groupby1_result.Entities)
+ {
+ Int32 aggregate9a = (Int32)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate9a + "\n");
+ string aggregate9b = ((EntityReference)((AliasedValue)c["ownerid"]).Value).Name;
+ Console.WriteLine("Owner: " + aggregate9b);
+ string aggregate9c = (string)((AliasedValue)c["ownerid_owneridyominame"]).Value;
+ Console.WriteLine("Owner: " + aggregate9c);
+ string aggregate9d = (string)((AliasedValue)c["ownerid_owneridyominame"]).Value;
+ Console.WriteLine("Owner: " + aggregate9d);
+ }
+ System.Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML groupby2 Aggregate 10
+ // *****************************************************************************************************************
+ // Fetch the number of opportunities each manager's direct reports
+ // own using a groupby within a link-entity.
+ string groupby2 = @"
+
+
+
+
+
+
+
+ ";
+
+ EntityCollection groupby2_result = service.RetrieveMultiple(new FetchExpression(groupby2));
+
+ foreach (var c in groupby2_result.Entities)
+ {
+ int? aggregate10a = (int?)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate10a + "\n");
+ }
+ Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML byyear Aggregate 11
+ // *****************************************************************************************************************
+ // Fetch aggregate information about the opportunities that have
+ // been won by year.
+ string byyear = @"
+
+
+
+
+
+
+
+
+
+
+ ";
+
+ EntityCollection byyear_result = service.RetrieveMultiple(new FetchExpression(byyear));
+
+ foreach (var c in byyear_result.Entities)
+ {
+ Int32 aggregate11 = (Int32)((AliasedValue)c["year"]).Value;
+ Console.WriteLine("Year: " + aggregate11);
+ Int32 aggregate11a = (Int32)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate11a);
+ decimal aggregate11b = ((Money)((AliasedValue)c["estimatedvalue_sum"]).Value).Value;
+ Console.WriteLine("Sum of estimated value of all opportunities: " + aggregate11b);
+ decimal aggregate11c = ((Money)((AliasedValue)c["estimatedvalue_avg"]).Value).Value;
+ Console.WriteLine("Average of estimated value of all opportunities: " + aggregate11c);
+ Console.WriteLine("----------------------------------------------");
+ }
+ System.Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML byquarter Aggregate 12
+ // *****************************************************************************************************************
+ // Fetch aggregate information about the opportunities that have
+ // been won by quarter.(returns 1-4)
+ string byquarter = @"
+
+
+
+
+
+
+
+
+
+
+ ";
+
+ EntityCollection byquarter_result = service.RetrieveMultiple(new FetchExpression(byquarter));
+
+ foreach (var c in byquarter_result.Entities)
+ {
+ Int32 aggregate12 = (Int32)((AliasedValue)c["quarter"]).Value;
+ Console.WriteLine("Quarter: " + aggregate12);
+ Int32 aggregate12a = (Int32)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate12a);
+ decimal aggregate12b = ((Money)((AliasedValue)c["estimatedvalue_sum"]).Value).Value;
+ Console.WriteLine("Sum of estimated value of all opportunities: " + aggregate12b);
+ decimal aggregate12c = ((Money)((AliasedValue)c["estimatedvalue_avg"]).Value).Value;
+ Console.WriteLine("Average of estimated value of all opportunities: " + aggregate12c);
+ Console.WriteLine("----------------------------------------------");
+ }
+ Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML bymonth Aggregate 13
+ // *****************************************************************************************************************
+ // Fetch aggregate information about the opportunities that have
+ // been won by month. (returns 1-12)
+ string bymonth = @"
+
+
+
+
+
+
+
+
+
+
+ ";
+
+ EntityCollection bymonth_result = service.RetrieveMultiple(new FetchExpression(bymonth));
+
+ foreach (var c in bymonth_result.Entities)
+ {
+ Int32 aggregate13 = (Int32)((AliasedValue)c["month"]).Value;
+ Console.WriteLine("Month: " + aggregate13);
+ Int32 aggregate13a = (Int32)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate13a);
+ decimal aggregate13b = ((Money)((AliasedValue)c["estimatedvalue_sum"]).Value).Value;
+ Console.WriteLine("Sum of estimated value of all opportunities: " + aggregate13b);
+ decimal aggregate13c = ((Money)((AliasedValue)c["estimatedvalue_avg"]).Value).Value;
+ Console.WriteLine("Average of estimated value of all opportunities: " + aggregate13c);
+ Console.WriteLine("----------------------------------------------");
+ }
+ System.Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML byweek Aggregate 14
+ // *****************************************************************************************************************
+ // Fetch aggregate information about the opportunities that have
+ // been won by week. (Returns 1-52)
+ string byweek = @"
+
+
+
+
+
+
+
+
+
+
+ ";
+
+ EntityCollection byweek_result = service.RetrieveMultiple(new FetchExpression(byweek));
+
+ foreach (var c in byweek_result.Entities)
+ {
+ Int32 aggregate14 = (Int32)((AliasedValue)c["week"]).Value;
+ Console.WriteLine("Week: " + aggregate14);
+ Int32 aggregate14a = (Int32)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate14a);
+ decimal aggregate14b = ((Money)((AliasedValue)c["estimatedvalue_sum"]).Value).Value;
+ Console.WriteLine("Sum of estimated value of all opportunities: " + aggregate14b);
+ decimal aggregate14c = ((Money)((AliasedValue)c["estimatedvalue_avg"]).Value).Value;
+ Console.WriteLine("Average of estimated value of all opportunities: " + aggregate14c);
+ Console.WriteLine("----------------------------------------------");
+ }
+ System.Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML byday Aggregate 15
+ // *****************************************************************************************************************
+ // Fetch aggregate information about the opportunities that have
+ // been won by day. (Returns 1-31)
+ string byday = @"
+
+
+
+
+
+
+
+
+
+
+ ";
+
+ EntityCollection byday_result = service.RetrieveMultiple(new FetchExpression(byday));
+
+ foreach (var c in byday_result.Entities)
+ {
+ Int32 aggregate15 = (Int32)((AliasedValue)c["day"]).Value;
+ Console.WriteLine("Day: " + aggregate15);
+ Int32 aggregate15a = (Int32)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate15a);
+ decimal aggregate15b = ((Money)((AliasedValue)c["estimatedvalue_sum"]).Value).Value;
+ Console.WriteLine("Sum of estimated value of all opportunities: " + aggregate15b);
+ decimal aggregate15c = ((Money)((AliasedValue)c["estimatedvalue_avg"]).Value).Value;
+ Console.WriteLine("Average of estimated value of all opportunities: " + aggregate15c);
+ Console.WriteLine("----------------------------------------------");
+ }
+ System.Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML byyrqtr Aggregate 16
+ // *****************************************************************************************************************
+ // Fetch aggregate information about the opportunities that have
+ // been won by year and quarter.
+ string byyrqtr = @"
+
+
+
+
+
+
+
+
+
+
+
+ ";
+
+ EntityCollection byyrqtr_result = service.RetrieveMultiple(new FetchExpression(byyrqtr));
+
+ foreach (var c in byyrqtr_result.Entities)
+ {
+ Int32 aggregate16d = (Int32)((AliasedValue)c["year"]).Value;
+ Console.WriteLine("Year: " + aggregate16d);
+ Int32 aggregate16 = (Int32)((AliasedValue)c["quarter"]).Value;
+ Console.WriteLine("Quarter: " + aggregate16);
+ Int32 aggregate16a = (Int32)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate16a);
+ decimal aggregate16b = ((Money)((AliasedValue)c["estimatedvalue_sum"]).Value).Value;
+ Console.WriteLine("Sum of estimated value of all opportunities: " + aggregate16b);
+ decimal aggregate16c = ((Money)((AliasedValue)c["estimatedvalue_avg"]).Value).Value;
+ Console.WriteLine("Average of estimated value of all opportunities: " + aggregate16c);
+ Console.WriteLine("----------------------------------------------");
+ }
+ Console.WriteLine("===============================");
+
+ // *****************************************************************************************************************
+ // FetchXML byyrqtr2 Aggregate 17
+ // *****************************************************************************************************************
+ // Specify the result order for the previous sample. Order by year, then quarter.
+ string byyrqtr2 = @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ";
+
+ EntityCollection byyrqtr2_result = service.RetrieveMultiple(new FetchExpression(byyrqtr2));
+
+ foreach (var c in byyrqtr2_result.Entities)
+ {
+ Int32 aggregate17 = (Int32)((AliasedValue)c["quarter"]).Value;
+ Console.WriteLine("Quarter: " + aggregate17);
+ Int32 aggregate17d = (Int32)((AliasedValue)c["year"]).Value;
+ Console.WriteLine("Year: " + aggregate17d);
+ Int32 aggregate17a = (Int32)((AliasedValue)c["opportunity_count"]).Value;
+ Console.WriteLine("Count of all opportunities: " + aggregate17a);
+ decimal aggregate17b = ((Money)((AliasedValue)c["estimatedvalue_sum"]).Value).Value;
+ Console.WriteLine("Sum of estimated value of all opportunities: " + aggregate17b);
+ decimal aggregate17c = ((Money)((AliasedValue)c["estimatedvalue_avg"]).Value).Value;
+ Console.WriteLine("Average of estimated value of all opportunities: " + aggregate17c);
+ Console.WriteLine("----------------------------------------------");
+ }
+ Console.WriteLine("===============================");
+
+ Console.WriteLine("Retrieved aggregate record data.");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Entity records have been deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/UseAggregationInFetchXML/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/UseAggregationInFetchXML/README.md
new file mode 100644
index 00000000..8170a4c6
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/UseAggregationInFetchXML/README.md
@@ -0,0 +1,104 @@
+# Use Aggregation in FetchXML
+
+This sample demonstrates how to use aggregate functions in FetchXML queries to perform calculations and grouping operations on Dataverse data.
+
+## What This Sample Does
+
+This sample creates sample opportunity records and then demonstrates 17 different aggregate query patterns using FetchXML, including:
+
+- **Basic Aggregate Functions**: AVG, COUNT, MIN, MAX, SUM
+- **Count Variations**: COUNT(*), COUNT(column), COUNT(DISTINCT column)
+- **Multiple Aggregates**: Combining multiple aggregate functions in a single query
+- **Grouping**: GROUP BY with ownerid and linked entities
+- **Date Grouping**: Grouping by year, quarter, month, week, and day
+- **Ordering**: Controlling the sort order of aggregate results
+
+## How This Sample Works
+
+### Setup
+
+The sample creates:
+1. One account record
+2. Three opportunity records with different estimated values ($120,000, $240,000, $360,000)
+3. Marks all opportunities as "Won" with a close date of November 1, 2009
+
+### Demonstrate
+
+The sample executes 17 different FetchXML queries demonstrating various aggregation scenarios:
+
+1. **AVG** - Average estimated value of all opportunities
+2. **COUNT(*)** - Total count of all opportunities
+3. **COUNT(column)** - Count of opportunities with non-null name values
+4. **COUNT(DISTINCT column)** - Count of distinct opportunity names
+5. **MAX** - Maximum estimated value
+6. **MIN** - Minimum estimated value
+7. **SUM** - Sum of all estimated values
+8. **Multiple Aggregates** - Count, sum, and average in one query
+9. **GROUP BY ownerid** - Opportunities grouped by owner
+10. **GROUP BY with link-entity** - Opportunities grouped by manager
+11. **Date Grouping by Year** - Won opportunities grouped by year
+12. **Date Grouping by Quarter** - Won opportunities grouped by quarter (1-4)
+13. **Date Grouping by Month** - Won opportunities grouped by month (1-12)
+14. **Date Grouping by Week** - Won opportunities grouped by week (1-52)
+15. **Date Grouping by Day** - Won opportunities grouped by day (1-31)
+16. **Multiple Date Groupings** - Grouped by both year and quarter
+17. **Ordered Results** - Same as #16 but with explicit ordering
+
+### Cleanup
+
+The sample deletes all created records (opportunities and account).
+
+## Key Concepts
+
+### Aggregate Attributes
+
+FetchXML supports these aggregate functions via the `aggregate` attribute:
+- `count` - Count all records
+- `countcolumn` - Count non-null values in a column
+- `sum` - Sum numeric values
+- `avg` - Average of numeric values
+- `min` - Minimum value
+- `max` - Maximum value
+
+### Aliased Values
+
+Aggregate results are returned as `AliasedValue` objects. To access the value:
+
+```csharp
+var result = (int)((AliasedValue)entity["alias_name"]).Value;
+```
+
+### Date Grouping
+
+FetchXML supports date grouping with the `dategrouping` attribute:
+- `year` - Group by year
+- `quarter` - Group by quarter (1-4)
+- `month` - Group by month (1-12)
+- `week` - Group by week (1-52)
+- `day` - Group by day of month (1-31)
+
+### Distinct Counts
+
+Use `distinct='true'` with `countcolumn` to count unique values:
+
+```xml
+
+```
+
+## Running the Sample
+
+1. Update the connection string in `appsettings.json` at the Query folder level
+2. Build the project: `dotnet build`
+3. Run the sample: `dotnet run`
+
+The sample will:
+- Create test data (1 account, 3 opportunities)
+- Execute all 17 aggregate queries and display results
+- Prompt to delete created records
+- Clean up all created data
+
+## More Information
+
+- [Use FetchXML aggregation](https://docs.microsoft.com/power-apps/developer/data-platform/use-fetchxml-aggregation)
+- [FetchXML reference](https://docs.microsoft.com/power-apps/developer/data-platform/fetchxml-reference)
+- [Build queries with FetchXML](https://docs.microsoft.com/power-apps/developer/data-platform/org-service/build-queries-fetchxml)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/UseAggregationInFetchXML/UseAggregationInFetchXML.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/UseAggregationInFetchXML/UseAggregationInFetchXML.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/UseAggregationInFetchXML/UseAggregationInFetchXML.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/UseFetchXMLWithPaging/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/UseFetchXMLWithPaging/Program.cs
new file mode 100644
index 00000000..17a65aec
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/UseFetchXMLWithPaging/Program.cs
@@ -0,0 +1,241 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+using Microsoft.Xrm.Sdk.Query;
+using System.Text;
+using System.Xml;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid parentAccountId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating sample account records...");
+
+ // Create parent account
+ var parentAccount = new Entity("account")
+ {
+ ["name"] = "Root Test Account",
+ ["emailaddress1"] = "root@root.com"
+ };
+ parentAccountId = service.Create(parentAccount);
+ entityStore.Add(new EntityReference("account", parentAccountId));
+
+ // Create 10 child accounts
+ for (int i = 1; i <= 10; i++)
+ {
+ var childAccount = new Entity("account")
+ {
+ ["name"] = $"Child Test Account {i}",
+ ["emailaddress1"] = $"child{i}@root.com",
+ ["emailaddress2"] = "same@root.com",
+ ["parentaccountid"] = new EntityReference("account", parentAccountId)
+ };
+ Guid childId = service.Create(childAccount);
+ entityStore.Add(new EntityReference("account", childId));
+ }
+
+ Console.WriteLine("Created 1 parent and 10 child accounts.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ // Define the fetch attributes.
+ // Set the number of records per page to retrieve.
+ int fetchCount = 3;
+ // Initialize the page number.
+ int pageNumber = 1;
+ // Initialize the number of records.
+ int recordCount = 0;
+ // Specify the current paging cookie. For retrieving the first page,
+ // pagingCookie should be null.
+ string? pagingCookie = null;
+
+ // Create the FetchXml string for retrieving all child accounts to a parent account.
+ // This fetch query is using 1 placeholder to specify the parent account id
+ // for filtering out required accounts. Filter query is optional.
+ // Fetch query also includes optional order criteria that, in this case, is used
+ // to order the results in ascending order on the name data column.
+ string fetchXml = string.Format(@"
+
+
+
+
+
+
+
+
+ ",
+ parentAccountId);
+
+ Console.WriteLine("Retrieving data in pages\n");
+ Console.WriteLine("#\tAccount Name\t\t\tEmail Address");
+
+ while (true)
+ {
+ // Build fetchXml string with the placeholders.
+ string xml = CreateXml(fetchXml, pagingCookie, pageNumber, fetchCount);
+
+ // Execute the fetch query and get the xml result.
+ var fetchRequest = new RetrieveMultipleRequest
+ {
+ Query = new FetchExpression(xml)
+ };
+
+ EntityCollection returnCollection = ((RetrieveMultipleResponse)service.Execute(fetchRequest)).EntityCollection;
+
+ foreach (var c in returnCollection.Entities)
+ {
+ string name = c.Contains("name") ? c["name"].ToString() ?? "" : "";
+ string email = c.Contains("emailaddress1") ? c["emailaddress1"].ToString() ?? "" : "";
+ Console.WriteLine("{0}.\t{1}\t\t{2}", ++recordCount, name, email);
+ }
+
+ // Check for morerecords, if it returns true.
+ if (returnCollection.MoreRecords)
+ {
+ Console.WriteLine("\n****************\nPage number {0}\n****************", pageNumber);
+ Console.WriteLine("#\tAccount Name\t\t\tEmail Address");
+
+ // Increment the page number to retrieve the next page.
+ pageNumber++;
+
+ // Set the paging cookie to the paging cookie returned from current results.
+ pagingCookie = returnCollection.PagingCookie;
+ }
+ else
+ {
+ // If no more records in the result nodes, exit the loop.
+ break;
+ }
+ }
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("\nCleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ ///
+ /// Creates an XML string with paging information added to the FetchXML query.
+ ///
+ /// The base FetchXML query string
+ /// The paging cookie from the previous page (null for first page)
+ /// The page number to retrieve
+ /// The number of records per page
+ /// The FetchXML string with paging attributes added
+ private static string CreateXml(string xml, string? cookie, int page, int count)
+ {
+ StringReader stringReader = new StringReader(xml);
+ XmlTextReader reader = new XmlTextReader(stringReader);
+
+ // Load document
+ XmlDocument doc = new XmlDocument();
+ doc.Load(reader);
+
+ XmlAttributeCollection attrs = doc.DocumentElement!.Attributes;
+
+ if (cookie != null)
+ {
+ XmlAttribute pagingAttr = doc.CreateAttribute("paging-cookie");
+ pagingAttr.Value = cookie;
+ attrs.Append(pagingAttr);
+ }
+
+ XmlAttribute pageAttr = doc.CreateAttribute("page");
+ pageAttr.Value = page.ToString();
+ attrs.Append(pageAttr);
+
+ XmlAttribute countAttr = doc.CreateAttribute("count");
+ countAttr.Value = count.ToString();
+ attrs.Append(countAttr);
+
+ StringBuilder sb = new StringBuilder(1024);
+ StringWriter stringWriter = new StringWriter(sb);
+
+ XmlTextWriter writer = new XmlTextWriter(stringWriter);
+ doc.WriteTo(writer);
+ writer.Close();
+
+ return sb.ToString();
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ IConfiguration Configuration { get; }
+
+ Program()
+ {
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/UseFetchXMLWithPaging/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/UseFetchXMLWithPaging/README.md
new file mode 100644
index 00000000..ea923432
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/UseFetchXMLWithPaging/README.md
@@ -0,0 +1,111 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates paging with FetchXML using page and paging-cookie attributes"
+---
+
+# UseFetchXMLWithPaging
+
+Demonstrates paging with FetchXML using page and paging-cookie attributes to retrieve records in pages
+
+## What this sample does
+
+This sample shows how to:
+- Use FetchXML with paging attributes (page, count, paging-cookie)
+- Execute FetchXML queries using RetrieveMultipleRequest
+- Use paging cookies to navigate through result pages
+- Handle the MoreRecords flag to determine when to stop paging
+- Display results from multiple pages
+
+Paging is essential when working with large datasets to improve performance and avoid timeouts.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates 1 parent account ("Root Test Account")
+2. Creates 10 child accounts linked to the parent account
+
+### Run
+
+The main demonstration:
+1. Creates a FetchXML query to retrieve child accounts
+2. Defines paging parameters (fetchCount=3, pageNumber=1, pagingCookie=null)
+3. Loops through pages:
+ - Calls CreateXml() to inject paging attributes into FetchXML
+ - Executes RetrieveMultipleRequest with FetchExpression
+ - Displays records with page separators
+ - Checks MoreRecords flag
+ - Updates pagingCookie from EntityCollection.PagingCookie
+ - Increments pageNumber
+4. Continues until all records are retrieved (MoreRecords = false)
+
+### Cleanup
+
+The cleanup process deletes all created accounts (parent and children) in reverse order.
+
+## Demonstrates
+
+This sample demonstrates:
+- **FetchXML**: Building XML-based queries with filters and ordering
+- **Paging Attributes**: Using page, count, and paging-cookie attributes
+- **XML Manipulation**: Dynamically adding attributes to FetchXML
+- **RetrieveMultipleRequest**: Executing FetchXML with request/response pattern
+- **PagingCookie**: Using cookies to maintain paging state
+- **MoreRecords**: Determining if more pages exist
+- **EntityCollection**: Working with paged result sets
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating sample account records...
+Created 1 parent and 10 child accounts.
+
+Retrieving data in pages
+
+# Account Name Email Address
+1. Child Test Account 1 child1@root.com
+2. Child Test Account 10 child10@root.com
+3. Child Test Account 2 child2@root.com
+
+****************
+Page number 1
+****************
+# Account Name Email Address
+4. Child Test Account 3 child3@root.com
+5. Child Test Account 4 child4@root.com
+6. Child Test Account 5 child5@root.com
+
+****************
+Page number 2
+****************
+# Account Name Email Address
+7. Child Test Account 6 child6@root.com
+8. Child Test Account 7 child7@root.com
+9. Child Test Account 8 child8@root.com
+
+****************
+Page number 3
+****************
+# Account Name Email Address
+10. Child Test Account 9 child9@root.com
+
+Cleaning up...
+Deleting 11 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Page large result sets with FetchXML](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/page-large-result-sets-with-fetchxml)
+[Build queries with FetchXML](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/use-fetchxml-construct-query)
+[FetchXML reference](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/reference/index)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/UseFetchXMLWithPaging/UseFetchXMLWithPaging.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/UseFetchXMLWithPaging/UseFetchXMLWithPaging.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/UseFetchXMLWithPaging/UseFetchXMLWithPaging.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/Program.cs
new file mode 100644
index 00000000..58df6424
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/Program.cs
@@ -0,0 +1,180 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid parentAccountId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating sample account records...");
+
+ var parentAccount = new Entity("account")
+ {
+ ["name"] = "Root Test Account",
+ ["emailaddress1"] = "root@root.com"
+ };
+ parentAccountId = service.Create(parentAccount);
+ entityStore.Add(new EntityReference("account", parentAccountId));
+
+ for (int i = 1; i <= 10; i++)
+ {
+ var childAccount = new Entity("account")
+ {
+ ["name"] = $"Child Test Account {i}",
+ ["emailaddress1"] = $"child{i}@root.com",
+ ["parentaccountid"] = new EntityReference("account", parentAccountId)
+ };
+ Guid childId = service.Create(childAccount);
+ entityStore.Add(new EntityReference("account", childId));
+ }
+
+ Console.WriteLine("Created 1 parent and 10 child accounts.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ int queryCount = 3;
+ int pageNumber = 1;
+ int recordCount = 0;
+
+ var pagecondition = new ConditionExpression
+ {
+ AttributeName = "parentaccountid",
+ Operator = ConditionOperator.Equal
+ };
+ pagecondition.Values.Add(parentAccountId);
+
+ var order = new OrderExpression
+ {
+ AttributeName = "name",
+ OrderType = OrderType.Ascending
+ };
+
+ var pagequery = new QueryExpression
+ {
+ EntityName = "account",
+ ColumnSet = new ColumnSet("name", "emailaddress1")
+ };
+ pagequery.Criteria.AddCondition(pagecondition);
+ pagequery.Orders.Add(order);
+
+ pagequery.PageInfo = new PagingInfo
+ {
+ Count = queryCount,
+ PageNumber = pageNumber,
+ PagingCookie = null
+ };
+
+ Console.WriteLine("Retrieving sample account records in pages...\n");
+ Console.WriteLine("#\tAccount Name\t\t\tEmail Address");
+
+ while (true)
+ {
+ EntityCollection results = service.RetrieveMultiple(pagequery);
+ if (results.Entities != null)
+ {
+ foreach (Entity acct in results.Entities)
+ {
+ string name = acct.Contains("name") ? acct["name"].ToString() : "";
+ string email = acct.Contains("emailaddress1") ? acct["emailaddress1"].ToString() : "";
+ Console.WriteLine("{0}.\t{1}\t{2}", ++recordCount, name, email);
+ }
+ }
+
+ if (results.MoreRecords)
+ {
+ Console.WriteLine("\n****************\nPage number {0}\n****************", pagequery.PageInfo.PageNumber);
+ Console.WriteLine("#\tAccount Name\t\t\tEmail Address");
+
+ pagequery.PageInfo.PageNumber++;
+ pagequery.PageInfo.PagingCookie = results.PagingCookie;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("\nCleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ IConfiguration Configuration { get; }
+
+ Program()
+ {
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/README.md
new file mode 100644
index 00000000..15d1d75e
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/README.md
@@ -0,0 +1,106 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates paging with QueryExpression using PagingInfo"
+---
+
+# UseQueryExpressionwithPaging
+
+Demonstrates paging with QueryExpression using PagingInfo and paging cookies
+
+## What this sample does
+
+This sample shows how to:
+- Use PagingInfo with QueryExpression to retrieve records in pages
+- Use paging cookies to navigate through result pages
+- Handle the MoreRecords flag to determine when to stop paging
+- Display results from multiple pages
+
+Paging is essential when working with large datasets to improve performance and avoid timeouts.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates 1 parent account ("Root Test Account")
+2. Creates 10 child accounts linked to the parent account
+
+### Run
+
+The main demonstration:
+1. Creates a QueryExpression to retrieve child accounts
+2. Sets PageInfo with Count=3 (page size)
+3. Loops through pages:
+ - Retrieves records for current page
+ - Displays records with page separators
+ - Checks MoreRecords flag
+ - Updates PagingCookie for next page
+ - Increments PageNumber
+4. Continues until all records are retrieved
+
+### Cleanup
+
+The cleanup process deletes all created accounts (parent and children).
+
+## Demonstrates
+
+This sample demonstrates:
+- **QueryExpression**: Building queries with filters and ordering
+- **PagingInfo**: Configuring page size and page number
+- **PagingCookie**: Using cookies to navigate through pages
+- **MoreRecords**: Determining if more pages exist
+- **EntityCollection**: Working with paged result sets
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating sample account records...
+Created 1 parent and 10 child accounts.
+
+Retrieving sample account records in pages...
+
+# Account Name Email Address
+1. Child Test Account 1 child1@root.com
+2. Child Test Account 10 child10@root.com
+3. Child Test Account 2 child2@root.com
+
+****************
+Page number 1
+****************
+# Account Name Email Address
+4. Child Test Account 3 child3@root.com
+5. Child Test Account 4 child4@root.com
+6. Child Test Account 5 child5@root.com
+
+****************
+Page number 2
+****************
+# Account Name Email Address
+7. Child Test Account 6 child6@root.com
+8. Child Test Account 7 child7@root.com
+9. Child Test Account 8 child8@root.com
+
+****************
+Page number 3
+****************
+# Account Name Email Address
+10. Child Test Account 9 child9@root.com
+
+Cleaning up...
+Deleting 11 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Page large result sets with QueryExpression](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/page-large-result-sets-with-queryexpression)
+[Build queries with QueryExpression](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/build-queries-with-queryexpression)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/UseQueryExpressionwithPaging.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/UseQueryExpressionwithPaging.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/UseQueryExpressionwithPaging.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/temp.txt b/dataverse/orgsvc/CSharp-NETCore/Query/UseQueryExpressionwithPaging/temp.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/ValidateandExecuteSavedQuery/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Query/ValidateandExecuteSavedQuery/Program.cs
new file mode 100644
index 00000000..d76ddb06
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/ValidateandExecuteSavedQuery/Program.cs
@@ -0,0 +1,315 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using System.Text;
+using System.Xml;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates validating and executing saved queries (views)
+ ///
+ ///
+ /// This sample shows how to:
+ /// 1. Create a saved query (system view) and user query (personal view)
+ /// 2. Validate the saved query using ValidateSavedQueryRequest
+ /// 3. Execute the saved query using ExecuteByIdSavedQueryRequest
+ /// 4. Execute a user query using ExecuteByIdUserQueryRequest
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating sample accounts...");
+
+ var account1 = new Entity("account")
+ {
+ ["name"] = "Coho Vineyard"
+ };
+ Guid account1Id = service.Create(account1);
+ entityStore.Add(new EntityReference("account", account1Id));
+ Console.WriteLine(" Created Account: {0}", account1["name"]);
+
+ var account2 = new Entity("account")
+ {
+ ["name"] = "Coho Winery"
+ };
+ Guid account2Id = service.Create(account2);
+ entityStore.Add(new EntityReference("account", account2Id));
+ Console.WriteLine(" Created Account: {0}", account2["name"]);
+
+ var account3 = new Entity("account")
+ {
+ ["name"] = "Coho Vineyard & Winery"
+ };
+ Guid account3Id = service.Create(account3);
+ entityStore.Add(new EntityReference("account", account3Id));
+ Console.WriteLine(" Created Account: {0}", account3["name"]);
+
+ Console.WriteLine();
+ Console.WriteLine("Creating a Saved Query that retrieves all Account names...");
+
+ var savedQuery = new Entity("savedquery")
+ {
+ ["name"] = "Fetch all Account ids",
+ ["returnedtypecode"] = "account",
+ ["fetchxml"] = @"
+
+
+
+
+ ",
+ ["querytype"] = 0
+ };
+ Guid savedQueryId = service.Create(savedQuery);
+ entityStore.Add(new EntityReference("savedquery", savedQueryId));
+
+ Console.WriteLine();
+ Console.WriteLine("Creating a User Query that retrieves Account 'Coho Winery'...");
+
+ var userQuery = new Entity("userquery")
+ {
+ ["name"] = "Fetch Coho Winery",
+ ["returnedtypecode"] = "account",
+ ["fetchxml"] = @"
+
+
+
+
+
+
+
+ ",
+ ["querytype"] = 0
+ };
+ Guid userQueryId = service.Create(userQuery);
+ entityStore.Add(new EntityReference("userquery", userQueryId));
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ // Get the saved query and user query from entityStore
+ var savedQueryRef = entityStore.First(e => e.LogicalName == "savedquery");
+ var userQueryRef = entityStore.First(e => e.LogicalName == "userquery");
+
+ // Retrieve the saved query to get its FetchXml
+ var savedQuery = service.Retrieve("savedquery", savedQueryRef.Id,
+ new Microsoft.Xrm.Sdk.Query.ColumnSet("fetchxml", "querytype"));
+
+ Console.WriteLine("Validating Saved Query");
+ Console.WriteLine("======================");
+
+ // Create the validate request
+ var validateRequest = new ValidateSavedQueryRequest()
+ {
+ FetchXml = savedQuery.GetAttributeValue("fetchxml"),
+ QueryType = savedQuery.GetAttributeValue("querytype")
+ };
+
+ try
+ {
+ // Execute the validate request (will throw if invalid)
+ var validateResponse = (ValidateSavedQueryResponse)service.Execute(validateRequest);
+ Console.WriteLine(" Saved Query validated successfully");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(" Invalid Saved Query: {0}", ex.Message);
+ throw;
+ }
+
+ Console.WriteLine();
+ Console.WriteLine("Executing Saved Query");
+ Console.WriteLine("=====================");
+
+ // Create the execute saved query request
+ var executeSavedQueryRequest = new ExecuteByIdSavedQueryRequest()
+ {
+ EntityId = savedQueryRef.Id
+ };
+
+ // Execute the saved query
+ var executeSavedQueryResponse =
+ (ExecuteByIdSavedQueryResponse)service.Execute(executeSavedQueryRequest);
+
+ // Check results
+ if (string.IsNullOrEmpty(executeSavedQueryResponse.String))
+ {
+ throw new Exception("Saved Query did not return any results");
+ }
+
+ PrintResults(executeSavedQueryResponse.String);
+
+ Console.WriteLine();
+ Console.WriteLine("Executing User Query");
+ Console.WriteLine("====================");
+
+ // Create the execute user query request
+ var executeUserQueryRequest = new ExecuteByIdUserQueryRequest()
+ {
+ EntityId = userQueryRef
+ };
+
+ // Execute the user query
+ var executeUserQueryResponse =
+ (ExecuteByIdUserQueryResponse)service.Execute(executeUserQueryRequest);
+
+ // Check results
+ if (string.IsNullOrEmpty(executeUserQueryResponse.String))
+ {
+ throw new Exception("User Query did not return any results");
+ }
+
+ PrintResults(executeUserQueryResponse.String);
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine();
+ Console.WriteLine("Cleaning up...");
+
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+
+ // Delete in reverse order (important for dependencies)
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ ///
+ /// Formats and prints the XML results from query execution
+ ///
+ private static void PrintResults(string response)
+ {
+ var output = new StringBuilder();
+ using (XmlReader reader = XmlReader.Create(new StringReader(response)))
+ {
+ XmlWriterSettings settings = new XmlWriterSettings
+ {
+ Indent = true,
+ OmitXmlDeclaration = true
+ };
+
+ using (XmlWriter writer = XmlWriter.Create(output, settings))
+ {
+ while (reader.Read())
+ {
+ switch (reader.NodeType)
+ {
+ case XmlNodeType.Element:
+ writer.WriteStartElement(reader.Name);
+ break;
+ case XmlNodeType.Text:
+ writer.WriteString(reader.Value);
+ break;
+ case XmlNodeType.XmlDeclaration:
+ case XmlNodeType.ProcessingInstruction:
+ writer.WriteProcessingInstruction(reader.Name, reader.Value);
+ break;
+ case XmlNodeType.Comment:
+ writer.WriteComment(reader.Value);
+ break;
+ case XmlNodeType.EndElement:
+ writer.WriteFullEndElement();
+ break;
+ }
+ }
+ }
+ }
+
+ Console.WriteLine(" Result of query:");
+ Console.WriteLine(output.ToString());
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine();
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+
+ if (ex.InnerException != null)
+ {
+ Console.WriteLine("Inner Exception: {0}", ex.InnerException.Message);
+ }
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/ValidateandExecuteSavedQuery/README.md b/dataverse/orgsvc/CSharp-NETCore/Query/ValidateandExecuteSavedQuery/README.md
new file mode 100644
index 00000000..93bf4c0a
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/ValidateandExecuteSavedQuery/README.md
@@ -0,0 +1,107 @@
+# Sample: Validate and execute a saved query
+
+This sample shows how to validate FetchXML queries and execute both saved queries (system views) and user queries (personal views) using the Dataverse SDK.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `ValidateSavedQueryRequest` message is used to validate that a saved query's FetchXML is well-formed and valid.
+
+The `ExecuteByIdSavedQueryRequest` message is used to execute a saved query (system view) by its ID and return the results as XML.
+
+The `ExecuteByIdUserQueryRequest` message is used to execute a user query (personal view) by its ID and return the results as XML.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates 3 sample account records:
+ - "Coho Vineyard"
+ - "Coho Winery"
+ - "Coho Vineyard & Winery"
+
+2. Creates a saved query (system view) that retrieves all account names using FetchXML:
+ ```xml
+
+
+
+
+
+ ```
+
+3. Creates a user query (personal view) that retrieves only the "Coho Winery" account using filtered FetchXML:
+ ```xml
+
+
+
+
+
+
+
+
+ ```
+
+### Demonstrate
+
+1. **Validate the saved query:**
+ - Uses `ValidateSavedQueryRequest` to validate the FetchXML syntax
+ - The validation will throw an exception if the FetchXML is malformed or invalid
+ - Confirms successful validation
+
+2. **Execute the saved query:**
+ - Uses `ExecuteByIdSavedQueryRequest` to execute the saved query by ID
+ - Returns results as XML string containing all account names
+ - Formats and displays the XML results
+
+3. **Execute the user query:**
+ - Uses `ExecuteByIdUserQueryRequest` to execute the user query by ID
+ - Returns results as XML string containing only "Coho Winery" account
+ - Formats and displays the XML results
+
+### Clean up
+
+Displays an option to delete all the data created in the sample. The deletion is optional in case you want to examine the data created by the sample. You can manually delete the data to achieve the same results.
+
+## Key concepts
+
+### Saved Query vs User Query
+
+- **Saved Query (savedquery)**: System-wide views visible to all users (requires appropriate privileges to create)
+- **User Query (userquery)**: Personal views created by and visible only to the user who created them
+
+### FetchXML Validation
+
+The `ValidateSavedQueryRequest` message validates:
+- XML syntax correctness
+- Entity and attribute names exist
+- Operators are appropriate for attribute types
+- Overall query structure is valid
+
+This is useful when building query editors or when constructing FetchXML programmatically to catch errors before execution.
+
+### Query Execution
+
+Both `ExecuteByIdSavedQueryRequest` and `ExecuteByIdUserQueryRequest`:
+- Execute queries by their unique ID
+- Return results as XML string (not EntityCollection)
+- Are useful when you need the raw XML response format
+
+For most scenarios, using `RetrieveMultiple` with `FetchExpression` is more common and returns structured `EntityCollection` objects.
+
+## Related samples
+
+- [Use QueryExpression with paging](../UseQueryExpressionwithPaging/)
+- [Retrieve multiple by QueryExpression](../RetrieveMultipleByQueryExpression/)
+- [Retrieve multiple by QueryByAttribute](../RetrieveMultipleQueryByAttribute/)
+
+## Learn more
+
+- [Use FetchXML to construct a query](https://learn.microsoft.com/power-apps/developer/data-platform/fetchxml/overview)
+- [ValidateSavedQueryRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.validatesavedqueryrequest)
+- [ExecuteByIdSavedQueryRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.executebyidsavedqueryrequest)
+- [ExecuteByIdUserQueryRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.executebyiduserqueryrequest)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/ValidateandExecuteSavedQuery/ValidateandExecuteSavedQuery.csproj b/dataverse/orgsvc/CSharp-NETCore/Query/ValidateandExecuteSavedQuery/ValidateandExecuteSavedQuery.csproj
new file mode 100644
index 00000000..ac33882c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/ValidateandExecuteSavedQuery/ValidateandExecuteSavedQuery.csproj
@@ -0,0 +1,21 @@
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Query/appsettings.json b/dataverse/orgsvc/CSharp-NETCore/Query/appsettings.json
new file mode 100644
index 00000000..037aca85
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Query/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/AddRecordToQueue/AddRecordToQueue.csproj b/dataverse/orgsvc/CSharp-NETCore/Queues/AddRecordToQueue/AddRecordToQueue.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/AddRecordToQueue/AddRecordToQueue.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/AddRecordToQueue/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Queues/AddRecordToQueue/Program.cs
new file mode 100644
index 00000000..548d1819
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/AddRecordToQueue/Program.cs
@@ -0,0 +1,143 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates adding records to queues and moving between queues
+ ///
+ ///
+ /// This sample shows how to add records to queues and move them between queues.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating queues and letter...");
+
+ var sourceQueue = new Entity("queue") { ["name"] = "Source Queue", ["queueviewtype"] = new OptionSetValue(1) };
+ Guid sourceQueueId = service.Create(sourceQueue);
+ entityStore.Add(new EntityReference("queue", sourceQueueId));
+
+ var destQueue = new Entity("queue") { ["name"] = "Destination Queue", ["queueviewtype"] = new OptionSetValue(1) };
+ Guid destQueueId = service.Create(destQueue);
+ entityStore.Add(new EntityReference("queue", destQueueId));
+
+ var letter = new Entity("letter") { ["description"] = "Example Letter" };
+ Guid letterId = service.Create(letter);
+ entityStore.Add(new EntityReference("letter", letterId));
+
+ service.Execute(new AddToQueueRequest
+ {
+ DestinationQueueId = sourceQueueId,
+ Target = new EntityReference("letter", letterId)
+ });
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Moving letter between queues...");
+
+ service.Execute(new AddToQueueRequest
+ {
+ SourceQueueId = entityStore[0].Id,
+ Target = new EntityReference("letter", entityStore[2].Id),
+ DestinationQueueId = entityStore[1].Id
+ });
+
+ Console.WriteLine("Letter moved to destination queue.");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/AddRecordToQueue/README.md b/dataverse/orgsvc/CSharp-NETCore/Queues/AddRecordToQueue/README.md
new file mode 100644
index 00000000..f1824a0f
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/AddRecordToQueue/README.md
@@ -0,0 +1,84 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates adding records to queues and moving between queues"
+---
+
+# AddRecordToQueue
+
+Demonstrates adding records to queues and moving records between queues
+
+## What this sample does
+
+This sample shows how to:
+- Create multiple queues (source and destination)
+- Create an activity record (letter)
+- Add a record to a queue using AddToQueueRequest
+- Move a record from one queue to another queue
+
+Queues are used to organize work items and activities that need attention. This sample demonstrates the routing of work items between queues.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates a "Source Queue" (private queue)
+2. Creates a "Destination Queue" (private queue)
+3. Creates a letter activity record
+4. Adds the letter to the Source Queue using AddToQueueRequest
+
+### Run
+
+The main demonstration:
+1. Retrieves the queue and letter IDs from the entity store
+2. Executes AddToQueueRequest with:
+ - SourceQueueId: The source queue containing the letter
+ - Target: EntityReference to the letter activity
+ - DestinationQueueId: The destination queue to move the letter to
+3. The letter is moved from the source queue to the destination queue
+
+### Cleanup
+
+The cleanup process deletes all created records:
+- Source queue
+- Destination queue
+- Letter activity
+
+## Demonstrates
+
+This sample demonstrates:
+- **AddToQueueRequest**: Adding records to queues and routing between queues
+- **Queue routing**: Moving work items from one queue to another
+- **Activity management**: Working with activity entities (letter) in queues
+- **EntityReference**: Referencing entities across operations
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating queues and letter...
+ Created Source Queue
+ Created Destination Queue
+ Created letter activity
+ Added letter to Source Queue
+Setup complete.
+
+Moving letter between queues...
+Letter moved to destination queue.
+Cleaning up...
+Deleting 3 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Work with queues](https://learn.microsoft.com/power-apps/developer/data-platform/work-with-queues)
+[AddToQueueRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.addtoqueuerequest)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/AddSecurityPrincipalToQueue/AddSecurityPrincipalToQueue.csproj b/dataverse/orgsvc/CSharp-NETCore/Queues/AddSecurityPrincipalToQueue/AddSecurityPrincipalToQueue.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/AddSecurityPrincipalToQueue/AddSecurityPrincipalToQueue.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/AddSecurityPrincipalToQueue/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Queues/AddSecurityPrincipalToQueue/Program.cs
new file mode 100644
index 00000000..1f793b5a
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/AddSecurityPrincipalToQueue/Program.cs
@@ -0,0 +1,151 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates adding security principals to queues
+ ///
+ ///
+ /// This sample shows how to add a team to a queue using AddPrincipalToQueueRequest.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid queueId;
+ private static Guid teamId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating queue and team...");
+
+ var queue = new Entity("queue") { ["name"] = "Example Queue" };
+ queueId = service.Create(queue);
+ entityStore.Add(new EntityReference("queue", queueId));
+
+ // Get default business unit
+ var query = new QueryExpression("businessunit")
+ {
+ ColumnSet = new ColumnSet("businessunitid"),
+ Criteria = new FilterExpression()
+ };
+ query.Criteria.AddCondition("parentbusinessunitid", ConditionOperator.Null);
+ var defaultBU = service.RetrieveMultiple(query).Entities[0];
+
+ var team = new Entity("team")
+ {
+ ["name"] = "Example Team",
+ ["businessunitid"] = new EntityReference("businessunit", defaultBU.Id)
+ };
+ teamId = service.Create(team);
+ entityStore.Add(new EntityReference("team", teamId));
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Adding team to queue...");
+
+ var teamEntity = service.Retrieve("team", teamId, new ColumnSet("name"));
+
+ service.Execute(new AddPrincipalToQueueRequest
+ {
+ Principal = teamEntity,
+ QueueId = queueId
+ });
+
+ Console.WriteLine("Team added to queue.");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/AddSecurityPrincipalToQueue/README.md b/dataverse/orgsvc/CSharp-NETCore/Queues/AddSecurityPrincipalToQueue/README.md
new file mode 100644
index 00000000..006f25e6
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/AddSecurityPrincipalToQueue/README.md
@@ -0,0 +1,79 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates adding security principals (teams/users) as queue members"
+---
+
+# AddSecurityPrincipalToQueue
+
+Demonstrates adding security principals (teams/users) as queue members
+
+## What this sample does
+
+This sample shows how to:
+- Create a queue and a team
+- Add a security principal (team) to a queue using AddPrincipalToQueueRequest
+- Configure queue membership for teams
+
+Adding security principals to queues makes them members of the queue, allowing them to work with queue items. This differs from sharing access - queue membership provides a direct association between the principal and the queue.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates a queue
+2. Retrieves the default business unit (where parentbusinessunitid is null)
+3. Creates a team associated with the default business unit
+
+### Run
+
+The main demonstration:
+1. Retrieves the team entity with its name column
+2. Executes AddPrincipalToQueueRequest with:
+ - Principal: The team entity (not just EntityReference)
+ - QueueId: The ID of the queue
+3. The team is added as a member of the queue
+4. Team members can now work with items in this queue
+
+### Cleanup
+
+The cleanup process deletes all created records:
+- Team
+- Queue
+
+## Demonstrates
+
+This sample demonstrates:
+- **AddPrincipalToQueueRequest**: Adding security principals to queue membership
+- **Queue membership**: Understanding the relationship between principals and queues
+- **Entity retrieval**: Retrieving full entity records for request parameters
+- **Business unit query**: Retrieving the default business unit
+- **Team management**: Creating and associating teams with queues
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating queue and team...
+Setup complete.
+
+Adding team to queue...
+Team added to queue.
+Cleaning up...
+Deleting 2 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Work with queues](https://learn.microsoft.com/power-apps/developer/data-platform/work-with-queues)
+[AddPrincipalToQueueRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.addprincipaltoqueuerequest)
+[Queue entity reference](https://learn.microsoft.com/power-apps/developer/data-platform/reference/entities/queue)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/CleanHistoryQueue/CleanHistoryQueue.csproj b/dataverse/orgsvc/CSharp-NETCore/Queues/CleanHistoryQueue/CleanHistoryQueue.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/CleanHistoryQueue/CleanHistoryQueue.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/CleanHistoryQueue/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Queues/CleanHistoryQueue/Program.cs
new file mode 100644
index 00000000..f9f9d9e2
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/CleanHistoryQueue/Program.cs
@@ -0,0 +1,146 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates removing completed items from queues
+ ///
+ ///
+ /// This sample shows how to remove completed/inactive items from queues using RemoveFromQueueRequest.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid queueItemId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating queue and phone call activity...");
+
+ var queue = new Entity("queue") { ["name"] = "Example Queue", ["queueviewtype"] = new OptionSetValue(1) };
+ Guid queueId = service.Create(queue);
+ entityStore.Add(new EntityReference("queue", queueId));
+
+ var phoneCall = new Entity("phonecall") { ["description"] = "Example Phone Call" };
+ Guid phoneCallId = service.Create(phoneCall);
+ entityStore.Add(new EntityReference("phonecall", phoneCallId));
+
+ var queueItem = new Entity("queueitem")
+ {
+ ["queueid"] = new EntityReference("queue", queueId),
+ ["objectid"] = new EntityReference("phonecall", phoneCallId)
+ };
+ queueItemId = service.Create(queueItem);
+
+ // Mark phone call as completed
+ service.Execute(new SetStateRequest
+ {
+ EntityMoniker = new EntityReference("phonecall", phoneCallId),
+ State = new OptionSetValue(1), // Completed
+ Status = new OptionSetValue(2) // Made
+ });
+
+ Console.WriteLine("Setup complete - phone call added to queue and marked as completed.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Removing completed phone call from queue...");
+
+ service.Execute(new RemoveFromQueueRequest { QueueItemId = queueItemId });
+
+ Console.WriteLine("Completed item removed from queue.");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/CleanHistoryQueue/README.md b/dataverse/orgsvc/CSharp-NETCore/Queues/CleanHistoryQueue/README.md
new file mode 100644
index 00000000..68a8097f
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/CleanHistoryQueue/README.md
@@ -0,0 +1,78 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates removing completed items from queue history"
+---
+
+# CleanHistoryQueue
+
+Demonstrates removing completed items from queue history
+
+## What this sample does
+
+This sample shows how to:
+- Create a queue and add an activity (phone call)
+- Mark an activity as completed using SetStateRequest
+- Remove the completed activity from the queue using RemoveFromQueueRequest
+
+Queues can accumulate completed work items over time. This sample demonstrates how to clean up queue history by removing completed items, helping maintain queue organization and performance.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates a private queue
+2. Creates a phone call activity record
+3. Marks the phone call as completed using SetStateRequest:
+ - State: Completed (1)
+ - Status: Made (2)
+4. Creates a queue item linking the completed phone call to the queue
+
+### Run
+
+The main demonstration:
+1. Executes RemoveFromQueueRequest with the queue item ID
+2. The completed phone call is removed from the queue
+3. The queue history is cleaned
+
+### Cleanup
+
+The cleanup process deletes all created records:
+- Queue
+- Phone call activity
+
+## Demonstrates
+
+This sample demonstrates:
+- **RemoveFromQueueRequest**: Removing items from queues
+- **SetStateRequest**: Changing activity state to completed
+- **Queue maintenance**: Managing queue history and completed items
+- **Activity lifecycle**: Understanding activity state transitions
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating queue and phone call...
+Setup complete.
+
+Removing completed item from queue...
+Completed item removed from queue.
+Cleaning up...
+Deleting 2 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Work with queues](https://learn.microsoft.com/power-apps/developer/data-platform/work-with-queues)
+[RemoveFromQueueRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.removefromqueuerequest)
+[SetStateRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.setstaterequest)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/CreateQueue/CreateQueue.csproj b/dataverse/orgsvc/CSharp-NETCore/Queues/CreateQueue/CreateQueue.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/CreateQueue/CreateQueue.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/CreateQueue/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Queues/CreateQueue/Program.cs
new file mode 100644
index 00000000..cdeb8a7e
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/CreateQueue/Program.cs
@@ -0,0 +1,155 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to create a queue with various configuration options
+ ///
+ ///
+ /// This sample shows how to:
+ /// - Create a queue entity
+ /// - Configure queue properties including email delivery and filtering methods
+ /// - Set queue view type (public/private)
+ ///
+ /// Prerequisites:
+ /// - System Administrator or System Customizer role
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ // No setup required for this sample
+ }
+
+ ///
+ /// Demonstrates creating a queue with configuration options
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Creating a queue...");
+
+ // Create a queue with various property values
+ var newQueue = new Entity("queue")
+ {
+ ["name"] = "Example Queue",
+ ["description"] = "This is an example queue.",
+ ["incomingemaildeliverymethod"] = new OptionSetValue(0), // None
+ ["incomingemailfilteringmethod"] = new OptionSetValue(0), // All Email Messages
+ ["outgoingemaildeliverymethod"] = new OptionSetValue(0), // None
+ ["queueviewtype"] = new OptionSetValue(1) // Private
+ };
+
+ Guid queueId = service.Create(newQueue);
+ entityStore.Add(new EntityReference("queue", queueId));
+
+ Console.WriteLine($"Created queue: {newQueue["name"]}");
+ Console.WriteLine($" Queue ID: {queueId}");
+ Console.WriteLine($" Description: {newQueue["description"]}");
+ Console.WriteLine($" Incoming Email Delivery Method: None (0)");
+ Console.WriteLine($" Incoming Email Filtering Method: All Email Messages (0)");
+ Console.WriteLine($" Outgoing Email Delivery Method: None (0)");
+ Console.WriteLine($" Queue View Type: Private (1)");
+ Console.WriteLine();
+
+ Console.WriteLine("Queue creation complete.");
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/CreateQueue/README.md b/dataverse/orgsvc/CSharp-NETCore/Queues/CreateQueue/README.md
new file mode 100644
index 00000000..de5525e6
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/CreateQueue/README.md
@@ -0,0 +1,81 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates how to create a queue with various configuration options"
+---
+
+# CreateQueue
+
+Demonstrates how to create a queue with various configuration options
+
+## What this sample does
+
+This sample shows how to:
+- Create a queue entity with configuration properties
+- Set queue properties including email delivery methods, filtering methods, and view type
+- Configure a private queue that can be used for work item routing
+
+Queues in Dataverse are used to organize and prioritize work items, enabling teams to manage activities, cases, and other records that require action.
+
+## How this sample works
+
+### Setup
+
+No setup is required for this sample.
+
+### Run
+
+The main demonstration:
+1. Creates a queue entity with the following properties:
+ - Name: "Example Queue"
+ - Description: "This is an example queue."
+ - Incoming email delivery method: None (0)
+ - Incoming email filtering method: All Email Messages (0)
+ - Outgoing email delivery method: None (0)
+ - Queue view type: Private (1)
+
+2. Displays the created queue information including ID and configuration settings
+
+### Cleanup
+
+The cleanup process deletes the created queue record.
+
+## Demonstrates
+
+This sample demonstrates:
+- **Entity creation**: Creating queue records using late-bound syntax
+- **OptionSetValue**: Setting option set values for queue configuration properties
+- **Queue configuration**: Understanding queue property options including:
+ - Email delivery methods (None, Email Router, Forward Mailbox)
+ - Email filtering methods (All Messages, Responses Only, From Leads/Contacts/Accounts)
+ - Queue view types (Public, Private)
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating a queue...
+Created queue: Example Queue (ID: a1234567-89ab-cdef-0123-456789abcdef)
+ Description: This is an example queue.
+ Incoming Email Delivery Method: None (0)
+ Incoming Email Filtering Method: All Email Messages (0)
+ Outgoing Email Delivery Method: None (0)
+ Queue View Type: Private (1)
+
+Queue creation complete.
+Cleaning up...
+Deleting 1 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Work with queues](https://learn.microsoft.com/power-apps/developer/data-platform/work-with-queues)
+[Queue entity reference](https://learn.microsoft.com/power-apps/developer/data-platform/reference/entities/queue)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/DeleteQueue/DeleteQueue.csproj b/dataverse/orgsvc/CSharp-NETCore/Queues/DeleteQueue/DeleteQueue.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/DeleteQueue/DeleteQueue.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/DeleteQueue/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Queues/DeleteQueue/Program.cs
new file mode 100644
index 00000000..f476b387
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/DeleteQueue/Program.cs
@@ -0,0 +1,136 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates creating and deleting a queue
+ ///
+ ///
+ /// This sample shows how to create and delete a queue entity.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ // No setup required
+ }
+
+ ///
+ /// Demonstrates creating and deleting a queue
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Creating a queue...");
+
+ var newQueue = new Entity("queue")
+ {
+ ["name"] = "Example Queue",
+ ["description"] = "This is an example queue."
+ };
+
+ Guid queueId = service.Create(newQueue);
+ entityStore.Add(new EntityReference("queue", queueId));
+
+ Console.WriteLine($"Created queue: {newQueue["name"]} (ID: {queueId})");
+ Console.WriteLine("Queue will be deleted during cleanup.");
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/DeleteQueue/README.md b/dataverse/orgsvc/CSharp-NETCore/Queues/DeleteQueue/README.md
new file mode 100644
index 00000000..fe6f8f22
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/DeleteQueue/README.md
@@ -0,0 +1,65 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates creating and deleting a queue"
+---
+
+# DeleteQueue
+
+Demonstrates creating and deleting a queue
+
+## What this sample does
+
+This sample shows how to:
+- Create a queue record
+- Delete a queue record using the Delete method
+
+This demonstrates the basic lifecycle of queue management, showing both creation and deletion operations.
+
+## How this sample works
+
+### Setup
+
+No setup is required for this sample.
+
+### Run
+
+The main demonstration:
+1. Creates a queue entity with name "Example Queue"
+2. Displays the created queue's ID
+3. Indicates the queue will be deleted during cleanup
+
+### Cleanup
+
+The cleanup process deletes the created queue record using the standard Delete method.
+
+## Demonstrates
+
+This sample demonstrates:
+- **Entity creation**: Creating queue records
+- **Entity deletion**: Using service.Delete() to remove queue records
+- **Queue lifecycle management**: Basic CRUD operations for queues
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating a queue...
+Created queue: Example Queue (ID: a1234567-89ab-cdef-0123-456789abcdef)
+Queue will be deleted during cleanup.
+Cleaning up...
+Deleting 1 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Work with queues](https://learn.microsoft.com/power-apps/developer/data-platform/work-with-queues)
+[Queue entity reference](https://learn.microsoft.com/power-apps/developer/data-platform/reference/entities/queue)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/Queues.slnx b/dataverse/orgsvc/CSharp-NETCore/Queues/Queues.slnx
new file mode 100644
index 00000000..32f431cf
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/Queues.slnx
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/README.md b/dataverse/orgsvc/CSharp-NETCore/Queues/README.md
new file mode 100644
index 00000000..3cf412f0
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/README.md
@@ -0,0 +1,60 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates queue operations including creating queues, adding records to queues, and managing queue items"
+---
+
+# Queues
+Demonstrates queue operations including creating queues, adding records to queues, and managing queue items
+
+## Samples
+
+This folder contains the following samples:
+
+|Sample folder|Description|Build target|
+|---|---|---|
+|[CreateQueue](CreateQueue)|Demonstrates creating a queue with configuration options|.NET 6|
+|[DeleteQueue](DeleteQueue)|Demonstrates creating and deleting a queue|.NET 6|
+|[AddRecordToQueue](AddRecordToQueue)|Demonstrates adding records to queues and moving between queues|.NET 6|
+|[ReleaseQueueItems](ReleaseQueueItems)|Demonstrates releasing queue items from workers|.NET 6|
+|[SpecifyQueueItem](SpecifyQueueItem)|Demonstrates assigning queue items to specific workers|.NET 6|
+|[CleanHistoryQueue](CleanHistoryQueue)|Demonstrates removing completed items from queues|.NET 6|
+|[ShareQueue](ShareQueue)|Demonstrates sharing queue access with teams|.NET 6|
+|[AddSecurityPrincipalToQueue](AddSecurityPrincipalToQueue)|Demonstrates adding security principals to queues|.NET 6|
+
+## Prerequisites
+
+- Microsoft Visual Studio 2022
+- Access to Dataverse with appropriate privileges for the operations demonstrated
+
+## How to run samples
+
+1. Clone or download the PowerApps-Samples repository
+2. Navigate to `/dataverse/orgsvc/CSharp-NETCore/Queues/`
+3. Open `Queues.sln` in Visual Studio 2022
+4. Edit the `appsettings.json` file in the category folder root with your Dataverse environment details:
+ - Set `Url` to your Dataverse environment URL
+ - Set `Username` to your user account
+5. Build and run the desired sample project
+
+## appsettings.json
+
+Each sample in this category references the shared `appsettings.json` file in the category root folder. The connection string format is:
+
+```json
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
+```
+
+You can also set the `DATAVERSE_APPSETTINGS` environment variable to point to a custom appsettings.json file location if you prefer to keep your connection string outside the repository.
+
+## See also
+
+[SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/overview)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/ReleaseQueueItems/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Queues/ReleaseQueueItems/Program.cs
new file mode 100644
index 00000000..984dd62b
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/ReleaseQueueItems/Program.cs
@@ -0,0 +1,148 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates releasing queue items from workers
+ ///
+ ///
+ /// This sample shows how to release a queue item from a worker using ReleaseToQueueRequest.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid queueItemId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating queue, letter, and queue item...");
+
+ var queue = new Entity("queue") { ["name"] = "Example Queue", ["queueviewtype"] = new OptionSetValue(1) };
+ Guid queueId = service.Create(queue);
+ entityStore.Add(new EntityReference("queue", queueId));
+
+ var letter = new Entity("letter") { ["description"] = "Example Letter" };
+ Guid letterId = service.Create(letter);
+ entityStore.Add(new EntityReference("letter", letterId));
+
+ var queueItem = new Entity("queueitem")
+ {
+ ["queueid"] = new EntityReference("queue", queueId),
+ ["objectid"] = new EntityReference("letter", letterId)
+ };
+ queueItemId = service.Create(queueItem);
+ entityStore.Add(new EntityReference("queueitem", queueItemId));
+
+ // Get current user and assign as worker
+ var whoAmI = (WhoAmIResponse)service.Execute(new WhoAmIRequest());
+ var updateItem = new Entity("queueitem")
+ {
+ Id = queueItemId,
+ ["workerid"] = new EntityReference("systemuser", whoAmI.UserId)
+ };
+ service.Update(updateItem);
+
+ Console.WriteLine("Setup complete - queue item assigned to worker.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Releasing queue item from worker...");
+
+ service.Execute(new ReleaseToQueueRequest { QueueItemId = queueItemId });
+
+ Console.WriteLine("Queue item released from worker.");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/ReleaseQueueItems/README.md b/dataverse/orgsvc/CSharp-NETCore/Queues/ReleaseQueueItems/README.md
new file mode 100644
index 00000000..dff41284
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/ReleaseQueueItems/README.md
@@ -0,0 +1,77 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates releasing queue items from workers back to the queue"
+---
+
+# ReleaseQueueItems
+
+Demonstrates releasing queue items from workers back to the queue
+
+## What this sample does
+
+This sample shows how to:
+- Create a queue and add a queue item
+- Assign a queue item to a worker (user)
+- Release the queue item from the worker back to the queue using ReleaseToQueueRequest
+
+When a queue item is assigned to a worker, they become responsible for handling it. This sample demonstrates how to release that assignment and return the item to the general queue pool, making it available for other workers to pick up.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates a private queue
+2. Creates a letter activity record
+3. Creates a queue item linking the letter to the queue
+4. Retrieves the current user's ID using WhoAmIRequest
+5. Assigns the queue item to the current user by updating the workerid field
+
+### Run
+
+The main demonstration:
+1. Executes ReleaseToQueueRequest with the queue item ID
+2. The queue item is released from the worker's assignment
+3. The item becomes available in the queue for other workers to pick
+
+### Cleanup
+
+The cleanup process deletes all created records:
+- Queue
+- Letter activity
+- Queue item (automatically deleted when queue or letter is deleted)
+
+## Demonstrates
+
+This sample demonstrates:
+- **ReleaseToQueueRequest**: Releasing queue items from worker assignments
+- **WhoAmIRequest**: Getting the current user's identity
+- **Queue item assignment**: Understanding worker assignment workflow
+- **Queue item lifecycle**: Managing work item states (assigned vs. available)
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating queue and queue item...
+Setup complete.
+
+Releasing queue item...
+Queue item released from worker.
+Cleaning up...
+Deleting 2 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Work with queues](https://learn.microsoft.com/power-apps/developer/data-platform/work-with-queues)
+[ReleaseToQueueRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.releasetoqueuerequest)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/ReleaseQueueItems/ReleaseQueueItems.csproj b/dataverse/orgsvc/CSharp-NETCore/Queues/ReleaseQueueItems/ReleaseQueueItems.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/ReleaseQueueItems/ReleaseQueueItems.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/ShareQueue/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Queues/ShareQueue/Program.cs
new file mode 100644
index 00000000..f4361758
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/ShareQueue/Program.cs
@@ -0,0 +1,153 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates sharing queue access with teams
+ ///
+ ///
+ /// This sample shows how to share a queue with a team using GrantAccessRequest.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid queueId;
+ private static Guid teamId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating queue and team...");
+
+ var queue = new Entity("queue") { ["name"] = "Example Queue", ["queueviewtype"] = new OptionSetValue(1) };
+ queueId = service.Create(queue);
+ entityStore.Add(new EntityReference("queue", queueId));
+
+ // Get default business unit
+ var query = new QueryExpression("businessunit")
+ {
+ ColumnSet = new ColumnSet("businessunitid"),
+ Criteria = new FilterExpression()
+ };
+ query.Criteria.AddCondition("parentbusinessunitid", ConditionOperator.Null);
+ var defaultBU = service.RetrieveMultiple(query).Entities[0];
+
+ var team = new Entity("team")
+ {
+ ["name"] = "Example Team",
+ ["businessunitid"] = new EntityReference("businessunit", defaultBU.Id)
+ };
+ teamId = service.Create(team);
+ entityStore.Add(new EntityReference("team", teamId));
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Sharing queue with team...");
+
+ service.Execute(new GrantAccessRequest
+ {
+ PrincipalAccess = new PrincipalAccess
+ {
+ Principal = new EntityReference("team", teamId),
+ AccessMask = AccessRights.ReadAccess | AccessRights.AppendToAccess
+ },
+ Target = new EntityReference("queue", queueId)
+ });
+
+ Console.WriteLine("Queue access granted to team.");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/ShareQueue/README.md b/dataverse/orgsvc/CSharp-NETCore/Queues/ShareQueue/README.md
new file mode 100644
index 00000000..60771b26
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/ShareQueue/README.md
@@ -0,0 +1,80 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates sharing queue access with teams using security permissions"
+---
+
+# ShareQueue
+
+Demonstrates sharing queue access with teams using security permissions
+
+## What this sample does
+
+This sample shows how to:
+- Create a private queue and a team
+- Share queue access with a team using GrantAccessRequest
+- Configure specific access rights (Read and AppendTo) for the team
+
+Private queues require explicit sharing to grant access to users or teams. This sample demonstrates how to programmatically configure queue sharing and access control.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates a private queue (queueviewtype = 1)
+2. Retrieves the default business unit (where parentbusinessunitid is null)
+3. Creates a team associated with the default business unit
+
+### Run
+
+The main demonstration:
+1. Executes GrantAccessRequest with:
+ - PrincipalAccess containing:
+ - Principal: EntityReference to the team
+ - AccessMask: ReadAccess | AppendToAccess (combined rights)
+ - Target: EntityReference to the queue
+2. The team is granted read and append-to access to the queue
+3. Team members can now view and add items to the queue
+
+### Cleanup
+
+The cleanup process deletes all created records:
+- Team
+- Queue
+
+## Demonstrates
+
+This sample demonstrates:
+- **GrantAccessRequest**: Sharing record access with security principals
+- **PrincipalAccess**: Configuring access rights for principals
+- **AccessRights**: Using AccessMask flags (ReadAccess, AppendToAccess)
+- **Security model**: Understanding Dataverse record-level security
+- **Business unit query**: Retrieving the default business unit
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating queue and team...
+Setup complete.
+
+Sharing queue with team...
+Queue access granted to team.
+Cleaning up...
+Deleting 2 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Work with queues](https://learn.microsoft.com/power-apps/developer/data-platform/work-with-queues)
+[GrantAccessRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.grantaccessrequest)
+[Security concepts in Dataverse](https://learn.microsoft.com/power-apps/developer/data-platform/security-concepts)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/ShareQueue/ShareQueue.csproj b/dataverse/orgsvc/CSharp-NETCore/Queues/ShareQueue/ShareQueue.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/ShareQueue/ShareQueue.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/SpecifyQueueItem/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Queues/SpecifyQueueItem/Program.cs
new file mode 100644
index 00000000..17e1c52f
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/SpecifyQueueItem/Program.cs
@@ -0,0 +1,146 @@
+using Microsoft.Crm.Sdk.Messages;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates assigning queue items to specific workers
+ ///
+ ///
+ /// This sample shows how to assign a queue item to a worker using PickFromQueueRequest.
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+ private static Guid queueItemId;
+
+ #region Sample Methods
+
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Creating queue, letter, and queue item...");
+
+ var queue = new Entity("queue") { ["name"] = "Example Queue", ["queueviewtype"] = new OptionSetValue(1) };
+ Guid queueId = service.Create(queue);
+ entityStore.Add(new EntityReference("queue", queueId));
+
+ var letter = new Entity("letter") { ["description"] = "Example Letter" };
+ Guid letterId = service.Create(letter);
+ entityStore.Add(new EntityReference("letter", letterId));
+
+ var queueItem = new Entity("queueitem")
+ {
+ ["queueid"] = new EntityReference("queue", queueId),
+ ["objectid"] = new EntityReference("letter", letterId)
+ };
+ queueItemId = service.Create(queueItem);
+ entityStore.Add(new EntityReference("queueitem", queueItemId));
+
+ Console.WriteLine("Setup complete.");
+ Console.WriteLine();
+ }
+
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Assigning queue item to current user...");
+
+ var whoAmI = (WhoAmIResponse)service.Execute(new WhoAmIRequest());
+ var currentUser = service.Retrieve("systemuser", whoAmI.UserId, new ColumnSet("fullname"));
+
+ service.Execute(new PickFromQueueRequest
+ {
+ QueueItemId = queueItemId,
+ WorkerId = whoAmI.UserId
+ });
+
+ Console.WriteLine($"Queue item assigned to {currentUser["fullname"]}.");
+ }
+
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created record(s)...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/SpecifyQueueItem/README.md b/dataverse/orgsvc/CSharp-NETCore/Queues/SpecifyQueueItem/README.md
new file mode 100644
index 00000000..52dcf9c2
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/SpecifyQueueItem/README.md
@@ -0,0 +1,79 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "Demonstrates assigning queue items to specific workers"
+---
+
+# SpecifyQueueItem
+
+Demonstrates assigning queue items to specific workers
+
+## What this sample does
+
+This sample shows how to:
+- Create a queue and add a queue item
+- Assign a queue item to a specific worker using PickFromQueueRequest
+- Retrieve the current user's information for assignment
+- Display the worker's name after successful assignment
+
+Queue items represent work that needs to be completed. This sample demonstrates how to assign specific queue items to designated workers who will be responsible for handling them.
+
+## How this sample works
+
+### Setup
+
+The setup process:
+1. Creates a private queue
+2. Creates a letter activity record
+3. Creates a queue item linking the letter to the queue
+
+### Run
+
+The main demonstration:
+1. Retrieves the current user's ID using WhoAmIRequest
+2. Retrieves the current user's full name from the systemuser entity
+3. Executes PickFromQueueRequest with:
+ - QueueItemId: The queue item to assign
+ - WorkerId: The ID of the user to assign it to (current user)
+4. Displays the worker's name confirming the assignment
+
+### Cleanup
+
+The cleanup process deletes all created records:
+- Queue
+- Letter activity
+- Queue item (automatically deleted when queue or letter is deleted)
+
+## Demonstrates
+
+This sample demonstrates:
+- **PickFromQueueRequest**: Assigning queue items to specific workers
+- **WhoAmIRequest**: Getting the current user's identity
+- **Entity retrieval**: Retrieving user information for display
+- **Queue item assignment**: Understanding worker assignment workflow
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Creating queue and queue item...
+Setup complete.
+
+Assigning queue item to worker...
+Queue item assigned to John Doe.
+Cleaning up...
+Deleting 2 created record(s)...
+Records deleted.
+
+Press any key to exit.
+```
+
+## See also
+
+[Work with queues](https://learn.microsoft.com/power-apps/developer/data-platform/work-with-queues)
+[PickFromQueueRequest Class](https://learn.microsoft.com/dotnet/api/microsoft.crm.sdk.messages.pickfromqueuerequest)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/SpecifyQueueItem/SpecifyQueueItem.csproj b/dataverse/orgsvc/CSharp-NETCore/Queues/SpecifyQueueItem/SpecifyQueueItem.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/SpecifyQueueItem/SpecifyQueueItem.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Queues/appsettings.json b/dataverse/orgsvc/CSharp-NETCore/Queues/appsettings.json
new file mode 100644
index 00000000..037aca85
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Queues/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/AssociateRecords/AssociateRecords.csproj b/dataverse/orgsvc/CSharp-NETCore/Relationships/AssociateRecords/AssociateRecords.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/AssociateRecords/AssociateRecords.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/AssociateRecords/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Relationships/AssociateRecords/Program.cs
new file mode 100644
index 00000000..4d80ad7e
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/AssociateRecords/Program.cs
@@ -0,0 +1,185 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to associate and disassociate table rows.
+ ///
+ ///
+ /// This sample shows how to associate and disassociate tables rows using the
+ /// IOrganizationService.Associate and IOrganizationService.Disassociate methods.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Create a contact
+ var contact = new Entity("contact")
+ {
+ ["firstname"] = "John",
+ ["lastname"] = "Doe"
+ };
+ Guid contactId = service.Create(contact);
+ entityStore.Add(new EntityReference("contact", contactId));
+ Console.WriteLine("Created contact: {0} {1}", contact["firstname"], contact["lastname"]);
+
+ // Create three accounts
+ var account1 = new Entity("account")
+ {
+ ["name"] = "Example Account 1"
+ };
+ Guid account1Id = service.Create(account1);
+ entityStore.Add(new EntityReference("account", account1Id));
+ Console.WriteLine("Created {0}", account1["name"]);
+
+ var account2 = new Entity("account")
+ {
+ ["name"] = "Example Account 2"
+ };
+ Guid account2Id = service.Create(account2);
+ entityStore.Add(new EntityReference("account", account2Id));
+ Console.WriteLine("Created {0}", account2["name"]);
+
+ var account3 = new Entity("account")
+ {
+ ["name"] = "Example Account 3"
+ };
+ Guid account3Id = service.Create(account3);
+ entityStore.Add(new EntityReference("account", account3Id));
+ Console.WriteLine("Created {0}", account3["name"]);
+ }
+
+ ///
+ /// Demonstrates how to associate and disassociate table rows
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nAssociating accounts to contact...");
+
+ // Get references from entityStore
+ Guid contactId = entityStore[0].Id;
+ Guid account1Id = entityStore[1].Id;
+ Guid account2Id = entityStore[2].Id;
+ Guid account3Id = entityStore[3].Id;
+
+ // Create a collection of the entities that will be associated to the contact
+ var relatedEntities = new EntityReferenceCollection
+ {
+ new EntityReference("account", account1Id),
+ new EntityReference("account", account2Id),
+ new EntityReference("account", account3Id)
+ };
+
+ // Create an object that defines the relationship between the contact and account
+ var relationship = new Relationship("account_primary_contact");
+
+ // Associate the contact with the 3 accounts
+ service.Associate("contact", contactId, relationship, relatedEntities);
+ Console.WriteLine("The entities have been associated.");
+
+ Console.WriteLine("\nDisassociating accounts from contact...");
+
+ // Disassociate the records
+ service.Disassociate("contact", contactId, relationship, relatedEntities);
+ Console.WriteLine("The entities have been disassociated.");
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/AssociateRecords/README.md b/dataverse/orgsvc/CSharp-NETCore/Relationships/AssociateRecords/README.md
new file mode 100644
index 00000000..2726f87d
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/AssociateRecords/README.md
@@ -0,0 +1,35 @@
+# Associate and disassociate table rows
+
+This sample shows how to associate and disassociate tables rows using the [IOrganizationService.Associate](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.associate) and [IOrganization.Disassociate](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.disassociate) methods.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+Table records are associated to each other using lookup columns on the related table. The simplest way to associate two table records in a one-to-many relationship is to use an `EntityReference` to set the value of a lookup column on the related table.
+
+The simplest way to disassociate two table records in a one-to-many relationship is to set the value of the lookup column to null.
+
+Relationships using a many-to-many relationship depend on an intersect table that supports the many-to-many relationship. These relationships are defined by the existence of table records in that intersect table. While you can interact with the intersect table directly, it is much easier to use the API to do this for you.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates a contact record
+2. Creates three account records
+
+### Demonstrate
+
+1. Creates a collection of entity references for the three accounts
+2. Creates a relationship object that defines the relationship between contact and account
+3. Uses the `Associate` method to associate the contact with the three accounts
+4. Uses the `Disassociate` method to disassociate the contact from the accounts
+
+### Clean up
+
+Displays an option to delete all the data created in the sample. The deletion is optional in case you want to examine the data created by the sample. You can manually delete the data to achieve the same results.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionEarlyBound/ConnectionEarlyBound.csproj b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionEarlyBound/ConnectionEarlyBound.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionEarlyBound/ConnectionEarlyBound.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionEarlyBound/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionEarlyBound/Program.cs
new file mode 100644
index 00000000..6acb53ca
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionEarlyBound/Program.cs
@@ -0,0 +1,210 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to create a connection between an account and a contact.
+ ///
+ ///
+ /// This sample shows how to create a connection between an account and a contact
+ /// that have matching connection roles.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Create a Connection Role for account and contact
+ var newConnectionRole = new Entity("connectionrole")
+ {
+ ["name"] = "Example Connection Role",
+ ["category"] = new OptionSetValue(1) // Business category
+ };
+ Guid connectionRoleId = service.Create(newConnectionRole);
+ entityStore.Add(new EntityReference("connectionrole", connectionRoleId));
+ Console.WriteLine("Created {0}.", newConnectionRole["name"]);
+
+ // Create a related Connection Role Object Type Code record for Account
+ var newAccountConnectionRoleTypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", connectionRoleId),
+ ["associatedobjecttypecode"] = "account"
+ };
+ service.Create(newAccountConnectionRoleTypeCode);
+ Console.WriteLine("Created a related Connection Role Object Type Code record for Account.");
+
+ // Create a related Connection Role Object Type Code record for Contact
+ var newContactConnectionRoleTypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", connectionRoleId),
+ ["associatedobjecttypecode"] = "contact"
+ };
+ service.Create(newContactConnectionRoleTypeCode);
+ Console.WriteLine("Created a related Connection Role Object Type Code record for Contact.");
+
+ // Associate the connection role with itself
+ var associateConnectionRoles = new AssociateRequest
+ {
+ Target = new EntityReference("connectionrole", connectionRoleId),
+ RelatedEntities = new EntityReferenceCollection()
+ {
+ new EntityReference("connectionrole", connectionRoleId)
+ },
+ // The name of the relationship connection role association
+ // relationship in MS CRM
+ Relationship = new Relationship()
+ {
+ PrimaryEntityRole = EntityRole.Referencing, // Referencing or Referenced based on N:1 or 1:N reflexive relationship
+ SchemaName = "connectionroleassociation_association"
+ }
+ };
+ service.Execute(associateConnectionRoles);
+ Console.WriteLine("Associated the connection role with itself.");
+
+ // Create an Account
+ var account = new Entity("account")
+ {
+ ["name"] = "Example Account"
+ };
+ Guid accountId = service.Create(account);
+ entityStore.Add(new EntityReference("account", accountId));
+ Console.WriteLine("Created {0}.", account["name"]);
+
+ // Create a Contact
+ var contact = new Entity("contact")
+ {
+ ["lastname"] = "Example Contact"
+ };
+ Guid contactId = service.Create(contact);
+ entityStore.Add(new EntityReference("contact", contactId));
+ Console.WriteLine("Created {0}.", contact["lastname"]);
+ }
+
+ ///
+ /// Demonstrates how to create a connection between an account and a contact
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nCreating connection between account and contact...");
+
+ // Get references from entityStore
+ Guid connectionRoleId = entityStore[0].Id;
+ Guid accountId = entityStore[1].Id;
+ Guid contactId = entityStore[2].Id;
+
+ // Create a connection between the account and the contact
+ // Assign a connection role to a record
+ var newConnection = new Entity("connection")
+ {
+ ["record1id"] = new EntityReference("account", accountId),
+ ["record1roleid"] = new EntityReference("connectionrole", connectionRoleId),
+ ["record2roleid"] = new EntityReference("connectionrole", connectionRoleId),
+ ["record2id"] = new EntityReference("contact", contactId)
+ };
+
+ Guid connectionId = service.Create(newConnection);
+ entityStore.Add(new EntityReference("connection", connectionId));
+
+ Console.WriteLine("Created a connection between the account and the contact.");
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ // Delete in reverse order to handle dependencies
+ for (int i = entityStore.Count - 1; i >= 0; i--)
+ {
+ service.Delete(entityStore[i].LogicalName, entityStore[i].Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionEarlyBound/README.md b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionEarlyBound/README.md
new file mode 100644
index 00000000..6947c4c6
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionEarlyBound/README.md
@@ -0,0 +1,30 @@
+# Create a connection
+
+This sample shows how to create a connection between an account and a contact table that have matching connection roles.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+This sample shows how to create a connection between an account and a contact that have matching connection roles.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Creates a connection role for account and contact table
+2. Creates a related connection role object type code for account and contact table
+3. Associates the connection role with itself
+
+### Demonstrate
+
+1. Creates a connection between account and contact table
+2. Assigns a connection role to a record
+
+### Clean up
+
+Display an option to delete the records created in [Setup](#setup). The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionRole/ConnectionRole.csproj b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionRole/ConnectionRole.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionRole/ConnectionRole.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionRole/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionRole/Program.cs
new file mode 100644
index 00000000..e5ff46c4
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionRole/Program.cs
@@ -0,0 +1,158 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to create a connection role.
+ ///
+ ///
+ /// This sample shows how to create a connection role that can be used for
+ /// accounts and contacts.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+ // No setup required for this sample
+ }
+
+ ///
+ /// Demonstrates how to create a connection role
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Creating connection role...");
+
+ // Define the connection role category - Business
+ const int BusinessCategory = 1;
+
+ // Create a Connection Role for account and contact
+ var newConnectionRole = new Entity("connectionrole")
+ {
+ ["name"] = "Example Connection Role",
+ ["category"] = new OptionSetValue(BusinessCategory)
+ };
+
+ Guid connectionRoleId = service.Create(newConnectionRole);
+ entityStore.Add(new EntityReference("connectionrole", connectionRoleId));
+ Console.WriteLine("Created {0}.", newConnectionRole["name"]);
+
+ // Create a related Connection Role Object Type Code record for Account
+ var newAccountConnectionRoleTypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", connectionRoleId),
+ ["associatedobjecttypecode"] = "account"
+ };
+
+ service.Create(newAccountConnectionRoleTypeCode);
+ Console.WriteLine("Created a related Connection Role Object Type Code record for Account.");
+
+ // Create a related Connection Role Object Type Code record for Contact
+ var newContactConnectionRoleTypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", connectionRoleId),
+ ["associatedobjecttypecode"] = "contact"
+ };
+
+ service.Create(newContactConnectionRoleTypeCode);
+ Console.WriteLine("Created a related Connection Role Object Type Code record for Contact.");
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionRole/README.md b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionRole/README.md
new file mode 100644
index 00000000..c26bffc7
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/ConnectionRole/README.md
@@ -0,0 +1,29 @@
+# Create a connection role
+
+This sample shows how to create a connection role that can be used for accounts and contacts.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+This sample shows how to create a connection role that can be used for accounts and contacts.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+Checks for the current version of the org.
+
+### Demonstrate
+
+1. Defines connection role category values
+2. Creates a connection role for account and contact table
+3. Creates a connection role object type code record for account and contact table
+
+### Clean up
+
+Display an option to delete the records in [Setup](#setup). The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/README.md b/dataverse/orgsvc/CSharp-NETCore/Relationships/README.md
new file mode 100644
index 00000000..31dae7ec
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/README.md
@@ -0,0 +1,48 @@
+# Relationships
+
+Samples demonstrating how to work with relationships between entities in Dataverse including associations, connections, and connection roles.
+
+These samples show how to associate and disassociate records, create and manage connections between records, work with connection roles, and handle reciprocal connections.
+
+More information: [Entity relationships](https://learn.microsoft.com/power-apps/developer/data-platform/entity-relationship-metadata)
+
+## Samples
+
+|Sample folder|Description|Build target|
+|---|---|---|
+|AssociateRecords|Associate and disassociate records using relationships|.NET 6|
+|ConnectionEarlyBound|Create connections between records using early-bound entities|.NET 6|
+|ConnectionRole|Create and manage connection roles|.NET 6|
+|ReciprocalConnection|Create reciprocal connections between records|.NET 6|
+|UpdateConnectionRole|Update connection role definitions|.NET 6|
+
+## Prerequisites
+
+- Visual Studio 2022 or later
+- .NET 6.0 SDK or later
+- Access to a Dataverse environment
+
+## How to run samples
+
+1. Clone the PowerApps-Samples repository
+2. Navigate to `dataverse/orgsvc/CSharp-NETCore/Relationships/`
+3. Open the desired sample folder
+4. Edit the `appsettings.json` file (located in the Relationships folder) with your environment connection details:
+ ```json
+ {
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+ }
+ ```
+5. Build and run the sample:
+ ```bash
+ cd SampleFolder
+ dotnet run
+ ```
+
+## See also
+
+[Entity relationships](https://learn.microsoft.com/power-apps/developer/data-platform/entity-relationship-metadata)
+[Connection entities](https://learn.microsoft.com/power-apps/developer/data-platform/connection-entities)
+[Associate and disassociate records](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/entity-operations-associate-disassociate)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/ReciprocalConnection/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Relationships/ReciprocalConnection/Program.cs
new file mode 100644
index 00000000..2079c3a5
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/ReciprocalConnection/Program.cs
@@ -0,0 +1,211 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Messages;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to create reciprocal connection roles.
+ ///
+ ///
+ /// This sample shows how to create the reciprocal connection roles. It creates
+ /// a connection role for an account and a connection role for a contact, and then
+ /// makes them reciprocal by associating them with each other.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+ // No setup required for this sample
+ }
+
+ ///
+ /// Demonstrates how to create reciprocal connection roles
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Creating reciprocal connection roles...");
+
+ // Define the connection role category - Business
+ const int BusinessCategory = 1;
+
+ // Create the Connection Role 1
+ var newConnectionRole1 = new Entity("connectionrole")
+ {
+ ["name"] = "Example Connection Role 1",
+ ["category"] = new OptionSetValue(BusinessCategory)
+ };
+
+ Guid connectionRole1Id = service.Create(newConnectionRole1);
+ entityStore.Add(new EntityReference("connectionrole", connectionRole1Id));
+ Console.WriteLine("Created {0}.", newConnectionRole1["name"]);
+
+ // Create a related Connection Role 1 Object Type Code record for Account
+ var newAccountConnectionRole1TypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", connectionRole1Id),
+ ["associatedobjecttypecode"] = "account"
+ };
+
+ service.Create(newAccountConnectionRole1TypeCode);
+ Console.WriteLine("Created a related Connection Role 1 Object Type Code record for Account.");
+
+ // Create a related Connection Role 1 Object Type Code record for Contact
+ var newContactConnectionRole1TypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", connectionRole1Id),
+ ["associatedobjecttypecode"] = "contact"
+ };
+
+ service.Create(newContactConnectionRole1TypeCode);
+ Console.WriteLine("Created a related Connection Role 1 Object Type Code record for Contact.");
+
+ // Create the Connection Role 2
+ var newConnectionRole2 = new Entity("connectionrole")
+ {
+ ["name"] = "Example Connection Role 2",
+ ["category"] = new OptionSetValue(BusinessCategory)
+ };
+
+ Guid connectionRole2Id = service.Create(newConnectionRole2);
+ entityStore.Add(new EntityReference("connectionrole", connectionRole2Id));
+ Console.WriteLine("Created {0}.", newConnectionRole2["name"]);
+
+ // Create a related Connection Role 2 Object Type Code record for Account
+ var newAccountConnectionRole2TypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", connectionRole2Id),
+ ["associatedobjecttypecode"] = "account"
+ };
+
+ service.Create(newAccountConnectionRole2TypeCode);
+ Console.WriteLine("Created a related Connection Role 2 Object Type Code record for Account.");
+
+ // Create a related Connection Role 2 Object Type Code record for Contact
+ var newContactConnectionRole2TypeCode = new Entity("connectionroleobjecttypecode")
+ {
+ ["connectionroleid"] = new EntityReference("connectionrole", connectionRole2Id),
+ ["associatedobjecttypecode"] = "contact"
+ };
+
+ service.Create(newContactConnectionRole2TypeCode);
+ Console.WriteLine("Created a related Connection Role 2 Object Type Code record for Contact.");
+
+ // Associate the connection roles with each other (make them reciprocal)
+ var associateConnectionRoles = new AssociateRequest
+ {
+ Target = new EntityReference("connectionrole", connectionRole1Id),
+ RelatedEntities = new EntityReferenceCollection()
+ {
+ new EntityReference("connectionrole", connectionRole2Id)
+ },
+ // The name of the relationship connection role association
+ // relationship in MS CRM
+ Relationship = new Relationship()
+ {
+ PrimaryEntityRole = EntityRole.Referencing, // Referencing or Referenced based on N:1 or 1:N reflexive relationship
+ SchemaName = "connectionroleassociation_association"
+ }
+ };
+
+ service.Execute(associateConnectionRoles);
+ Console.WriteLine("Associated the connection roles (made them reciprocal).");
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/ReciprocalConnection/README.md b/dataverse/orgsvc/CSharp-NETCore/Relationships/ReciprocalConnection/README.md
new file mode 100644
index 00000000..7c2c9c1d
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/ReciprocalConnection/README.md
@@ -0,0 +1,29 @@
+# Create a reciprocal connection role
+
+This sample shows how to create the reciprocal connection roles. It creates a connection role for an account and a connection role for a contact, and then makes them reciprocal by associating them with each other.
+
+## How to run this sample
+
+See [How to run samples](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The `connectionrole` and `connectionroleobjecttypecode` entities are intended to be used in a scenario where they contain data that is required to create a new connection role and connection role object type.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+Checks for the current version of the org.
+
+### Demonstrate
+
+1. Creates two connection roles required for the sample
+2. Creates the connection role object type code records for account and contact for both connection roles
+3. Uses the `AssociateRequest` to associate the connection roles with each other (making them reciprocal)
+
+### Clean up
+
+Display an option to delete the sample data that is created in [Setup](#setup). The deletion is optional in case you want to examine the entities and data created by the sample. You can manually delete the records to achieve the same result.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/ReciprocalConnection/ReciprocalConnection.csproj b/dataverse/orgsvc/CSharp-NETCore/Relationships/ReciprocalConnection/ReciprocalConnection.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/ReciprocalConnection/ReciprocalConnection.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/UpdateConnectionRole/Program.cs b/dataverse/orgsvc/CSharp-NETCore/Relationships/UpdateConnectionRole/Program.cs
new file mode 100644
index 00000000..1bc34584
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/UpdateConnectionRole/Program.cs
@@ -0,0 +1,157 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// Demonstrates how to update a connection role.
+ ///
+ ///
+ /// This sample shows how to modify the properties of the connection role, such as
+ /// a role name, description, and category.
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+
+ // Define the connection role category - Business
+ const int BusinessCategory = 1;
+
+ // Create a Connection Role
+ var setupConnectionRole = new Entity("connectionrole")
+ {
+ ["name"] = "Example Connection Role",
+ ["description"] = "This is an example one sided connection role.",
+ ["category"] = new OptionSetValue(BusinessCategory)
+ };
+
+ Guid connectionRoleId = service.Create(setupConnectionRole);
+ entityStore.Add(new EntityReference("connectionrole", connectionRoleId));
+ Console.WriteLine("Created {0}.", setupConnectionRole["name"]);
+ }
+
+ ///
+ /// Demonstrates how to update a connection role
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("\nUpdating connection role...");
+
+ // Get the connection role ID from entityStore
+ Guid connectionRoleId = entityStore[0].Id;
+
+ // Define the connection role category - Other
+ const int OtherCategory = 5;
+
+ // Update the connection role instance
+ var connectionRole = new Entity("connectionrole")
+ {
+ ["connectionroleid"] = connectionRoleId,
+ ["name"] = "Updated Connection Role",
+ ["description"] = "This is an updated connection role.",
+ ["category"] = new OptionSetValue(OtherCategory)
+ };
+
+ service.Update(connectionRole);
+
+ Console.WriteLine("Updated the connection role instance.");
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine("\nDeleting {0} created record(s)...", entityStore.Count);
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ Console.WriteLine("Records deleted.");
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/UpdateConnectionRole/README.md b/dataverse/orgsvc/CSharp-NETCore/Relationships/UpdateConnectionRole/README.md
new file mode 100644
index 00000000..56f3a515
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/UpdateConnectionRole/README.md
@@ -0,0 +1,28 @@
+# Update a connection role
+
+This sample shows how to modify the properties of the connection role, such as a role name, description, and category.
+
+## How to run this sample
+
+See [How to run this sample](https://github.com/microsoft/PowerApps-Samples/blob/master/dataverse/README.md) for information about how to run this sample.
+
+## What this sample does
+
+The [IOrganizationService.Update](https://learn.microsoft.com/dotnet/api/microsoft.xrm.sdk.iorganizationservice.update) method is intended to be used in a scenario where it contains the data that is needed to update existing record.
+
+## How this sample works
+
+In order to simulate the scenario described in [What this sample does](#what-this-sample-does), the sample will do the following:
+
+### Setup
+
+1. Checks for the current version of the org
+2. Creates a connection role that will be updated later
+
+### Demonstrate
+
+The `Update` method updates the connection role by modifying its name, description, and category.
+
+### Clean up
+
+Displays an option to delete all the data created in the sample. The deletion is optional in case you want to examine the data created by the sample. You can manually delete the data to achieve same results.
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/UpdateConnectionRole/UpdateConnectionRole.csproj b/dataverse/orgsvc/CSharp-NETCore/Relationships/UpdateConnectionRole/UpdateConnectionRole.csproj
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/UpdateConnectionRole/UpdateConnectionRole.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Relationships/appsettings.json b/dataverse/orgsvc/CSharp-NETCore/Relationships/appsettings.json
new file mode 100644
index 00000000..037aca85
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Relationships/appsettings.json
@@ -0,0 +1,5 @@
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Scripts/Convert-LegacySample.ps1 b/dataverse/orgsvc/CSharp-NETCore/Scripts/Convert-LegacySample.ps1
new file mode 100644
index 00000000..7d4cdce3
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Scripts/Convert-LegacySample.ps1
@@ -0,0 +1,151 @@
+# Convert-LegacySample.ps1
+# Assists with transforming legacy code to modern patterns
+# Performs automated text replacements and outputs transformed code for review
+
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory=$true)]
+ [string]$LegacySamplePath,
+
+ [Parameter(Mandatory=$false)]
+ [string]$OutputPath = ""
+)
+
+$ErrorActionPreference = "Stop"
+
+# Validate legacy sample path exists
+if (-not (Test-Path $LegacySamplePath)) {
+ Write-Error "Legacy sample path does not exist: $LegacySamplePath"
+ exit 1
+}
+
+Write-Host "Analyzing legacy sample: $LegacySamplePath" -ForegroundColor Cyan
+Write-Host ""
+
+# Find source files
+$csFiles = Get-ChildItem -Path $LegacySamplePath -Filter "*.cs" -Recurse | Where-Object {
+ $_.FullName -notlike "*\obj\*" -and
+ $_.FullName -notlike "*\bin\*" -and
+ $_.Name -ne "AssemblyInfo.cs"
+}
+
+if ($csFiles.Count -eq 0) {
+ Write-Error "No C# source files found in $LegacySamplePath"
+ exit 1
+}
+
+Write-Host "Found $($csFiles.Count) source file(s):"
+$csFiles | ForEach-Object { Write-Host " - $($_.Name)" }
+Write-Host ""
+
+# Function to transform code
+function Transform-Code {
+ param(
+ [string]$content
+ )
+
+ # Replace CrmServiceClient with ServiceClient
+ $content = $content -replace '\bCrmServiceClient\b', 'ServiceClient'
+
+ # Replace using Microsoft.Xrm.Tooling.Connector
+ $content = $content -replace 'using Microsoft\.Xrm\.Tooling\.Connector;', '// Removed: using Microsoft.Xrm.Tooling.Connector;'
+
+ # Replace namespace PowerApps.Samples with PowerPlatform.Dataverse.CodeSamples
+ $content = $content -replace '\bnamespace PowerApps\.Samples\b', 'namespace PowerPlatform.Dataverse.CodeSamples'
+
+ # Replace service.IsReady checks (common pattern)
+ $content = $content -replace 'if \(service\.IsReady\)', 'if (!service.IsReady)'
+
+ # Comment out SampleHelpers usage
+ $content = $content -replace '\bSampleHelpers\.Connect\b', '// TODO: Replace with ServiceClient initialization from appsettings.json - SampleHelpers.Connect'
+ $content = $content -replace '\bSampleHelpers\.\w+', '// TODO: Review and update - $&'
+
+ # Comment out SystemUserProvider usage
+ $content = $content -replace '\bSystemUserProvider\.\w+', '// TODO: Replace with modern pattern - $&'
+
+ # Add TODO markers for WhoAmIRequest pattern (common)
+ if ($content -match 'WhoAmIRequest') {
+ $content = "// TODO: Review WhoAmIRequest usage for modern pattern`r`n" + $content
+ }
+
+ return $content
+}
+
+# Process each file
+$transformedFiles = @()
+
+foreach ($file in $csFiles) {
+ Write-Host "Processing: $($file.Name)" -ForegroundColor Yellow
+
+ $content = Get-Content -Path $file.FullName -Raw -Encoding UTF8
+ $originalSize = $content.Length
+
+ $transformed = Transform-Code -content $content
+
+ $transformedFiles += @{
+ OriginalPath = $file.FullName
+ OriginalName = $file.Name
+ Content = $transformed
+ OriginalSize = $originalSize
+ NewSize = $transformed.Length
+ Reduction = [math]::Round((($originalSize - $transformed.Length) / $originalSize) * 100, 2)
+ }
+
+ Write-Host " Original size: $originalSize bytes"
+ Write-Host " Transformed size: $($transformed.Length) bytes"
+ Write-Host ""
+}
+
+# Display summary
+Write-Host ""
+Write-Host "Transformation Summary" -ForegroundColor Green
+Write-Host "=====================" -ForegroundColor Green
+Write-Host "Files processed: $($transformedFiles.Count)"
+Write-Host ""
+
+# Output transformed files
+if ($OutputPath) {
+ # Create output directory if specified
+ if (-not (Test-Path $OutputPath)) {
+ New-Item -ItemType Directory -Path $OutputPath | Out-Null
+ }
+
+ Write-Host "Writing transformed files to: $OutputPath" -ForegroundColor Cyan
+ Write-Host ""
+
+ foreach ($file in $transformedFiles) {
+ $outPath = Join-Path $OutputPath $file.OriginalName
+ Set-Content -Path $outPath -Value $file.Content -Encoding UTF8
+ Write-Host " Created: $outPath"
+ }
+
+ Write-Host ""
+ Write-Host "Transformation complete!" -ForegroundColor Green
+} else {
+ # Display transformed content to console
+ Write-Host "Transformed Code (review and manually apply changes):" -ForegroundColor Cyan
+ Write-Host "========================================================" -ForegroundColor Cyan
+ Write-Host ""
+
+ foreach ($file in $transformedFiles) {
+ Write-Host ""
+ Write-Host "// ========================================" -ForegroundColor Magenta
+ Write-Host "// File: $($file.OriginalName)" -ForegroundColor Magenta
+ Write-Host "// ========================================" -ForegroundColor Magenta
+ Write-Host $file.Content
+ Write-Host ""
+ }
+}
+
+Write-Host ""
+Write-Host "Manual Review Checklist:" -ForegroundColor Yellow
+Write-Host "========================" -ForegroundColor Yellow
+Write-Host "- [ ] Extract core logic to Setup/Run/Cleanup methods"
+Write-Host "- [ ] Replace SampleHelpers.Connect with ServiceClient + appsettings.json"
+Write-Host "- [ ] Remove WPF login UI code (ExampleLoginForm)"
+Write-Host "- [ ] Add entity tracking to entityStore for cleanup"
+Write-Host "- [ ] Update error handling to modern try-catch-finally pattern"
+Write-Host "- [ ] Convert early-bound types to late-bound if possible"
+Write-Host "- [ ] Test with 'dotnet build' and 'dotnet run'"
+Write-Host ""
+Write-Host "Use New-ModernSample.ps1 to create the target project structure first."
diff --git a/dataverse/orgsvc/CSharp-NETCore/Scripts/New-CategoryFolder.ps1 b/dataverse/orgsvc/CSharp-NETCore/Scripts/New-CategoryFolder.ps1
new file mode 100644
index 00000000..8ce80fd3
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Scripts/New-CategoryFolder.ps1
@@ -0,0 +1,139 @@
+# New-CategoryFolder.ps1
+# Creates a new category folder structure in CSharp-NETCore with standard files
+
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory=$true)]
+ [string]$CategoryName,
+
+ [Parameter(Mandatory=$true)]
+ [string]$Description,
+
+ [Parameter(Mandatory=$false)]
+ [string]$LearnMoreUrl = ""
+)
+
+$ErrorActionPreference = "Stop"
+
+# Get the script's directory and navigate to CSharp-NETCore root
+$scriptDir = Split-Path -Parent $PSCommandPath
+$csharpNETCoreRoot = Split-Path -Parent $scriptDir
+
+# Create category directory
+$categoryPath = Join-Path $csharpNETCoreRoot $CategoryName
+if (Test-Path $categoryPath) {
+ Write-Warning "Category folder '$CategoryName' already exists at $categoryPath"
+ $response = Read-Host "Do you want to continue? (y/n)"
+ if ($response -ne 'y') {
+ Write-Host "Operation cancelled."
+ exit
+ }
+} else {
+ Write-Host "Creating category folder: $categoryPath"
+ New-Item -ItemType Directory -Path $categoryPath | Out-Null
+}
+
+# Create appsettings.json
+$appsettingsPath = Join-Path $categoryPath "appsettings.json"
+if (-not (Test-Path $appsettingsPath)) {
+ Write-Host "Creating appsettings.json..."
+ $appsettingsContent = @"
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
+"@
+ Set-Content -Path $appsettingsPath -Value $appsettingsContent -Encoding UTF8
+}
+
+# Create README.md
+$readmePath = Join-Path $categoryPath "README.md"
+if (-not (Test-Path $readmePath)) {
+ Write-Host "Creating README.md..."
+
+ $learnMoreSection = ""
+ if ($LearnMoreUrl) {
+ $learnMoreSection = "`n`nMore information: [$CategoryName]($LearnMoreUrl)"
+ }
+
+ $readmeContent = @"
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "$Description"
+---
+
+# $CategoryName
+$Description$learnMoreSection
+
+## Samples
+
+This folder contains the following samples:
+
+|Sample folder|Description|Build target|
+|---|---|---|
+| | |.NET 6|
+
+## Prerequisites
+
+- Microsoft Visual Studio 2022
+- Access to Dataverse with appropriate privileges for the operations demonstrated
+
+## How to run samples
+
+1. Clone or download the PowerApps-Samples repository
+2. Navigate to ``/dataverse/orgsvc/CSharp-NETCore/$CategoryName/``
+3. Open ``$CategoryName.sln`` in Visual Studio 2022
+4. Edit the ``appsettings.json`` file in the category folder root with your Dataverse environment details:
+ - Set ``Url`` to your Dataverse environment URL
+ - Set ``Username`` to your user account
+5. Build and run the desired sample project
+
+## appsettings.json
+
+Each sample in this category references the shared ``appsettings.json`` file in the category root folder. The connection string format is:
+
+``````json
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
+``````
+
+You can also set the ``DATAVERSE_APPSETTINGS`` environment variable to point to a custom appsettings.json file location if you prefer to keep your connection string outside the repository.
+
+## See also
+
+[SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/overview)
+"@
+ Set-Content -Path $readmePath -Value $readmeContent -Encoding UTF8
+}
+
+# Create solution file
+$slnPath = Join-Path $categoryPath "$CategoryName.sln"
+if (-not (Test-Path $slnPath)) {
+ Write-Host "Creating solution file: $CategoryName.sln..."
+ Push-Location $categoryPath
+ try {
+ dotnet new sln -n $CategoryName | Out-Null
+ Write-Host "Solution file created successfully."
+ } catch {
+ Write-Error "Failed to create solution file: $_"
+ } finally {
+ Pop-Location
+ }
+}
+
+Write-Host ""
+Write-Host "Category '$CategoryName' created successfully at: $categoryPath" -ForegroundColor Green
+Write-Host ""
+Write-Host "Next steps:"
+Write-Host "1. Update the README.md with specific sample descriptions"
+Write-Host "2. Use New-ModernSample.ps1 to add samples to this category"
+Write-Host "3. Update appsettings.json with your environment details"
diff --git a/dataverse/orgsvc/CSharp-NETCore/Scripts/New-ModernSample.ps1 b/dataverse/orgsvc/CSharp-NETCore/Scripts/New-ModernSample.ps1
new file mode 100644
index 00000000..89d5f1cc
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Scripts/New-ModernSample.ps1
@@ -0,0 +1,300 @@
+# New-ModernSample.ps1
+# Creates a new modern sample project within a category folder
+
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory=$true)]
+ [string]$CategoryName,
+
+ [Parameter(Mandatory=$true)]
+ [string]$SampleName,
+
+ [Parameter(Mandatory=$true)]
+ [string]$Description,
+
+ [Parameter(Mandatory=$false)]
+ [string]$LearnMoreUrl = ""
+)
+
+$ErrorActionPreference = "Stop"
+
+# Get the script's directory and navigate to CSharp-NETCore root
+$scriptDir = Split-Path -Parent $PSCommandPath
+$csharpNETCoreRoot = Split-Path -Parent $scriptDir
+
+# Verify category exists
+$categoryPath = Join-Path $csharpNETCoreRoot $CategoryName
+if (-not (Test-Path $categoryPath)) {
+ Write-Error "Category folder '$CategoryName' does not exist. Run New-CategoryFolder.ps1 first."
+ exit 1
+}
+
+# Create sample directory
+$samplePath = Join-Path $categoryPath $SampleName
+if (Test-Path $samplePath) {
+ Write-Error "Sample folder '$SampleName' already exists in category '$CategoryName'"
+ exit 1
+}
+
+Write-Host "Creating sample folder: $samplePath"
+New-Item -ItemType Directory -Path $samplePath | Out-Null
+
+# Create .csproj file
+$csprojPath = Join-Path $samplePath "$SampleName.csproj"
+Write-Host "Creating $SampleName.csproj..."
+$csprojContent = @"
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+"@
+Set-Content -Path $csprojPath -Value $csprojContent -Encoding UTF8
+
+# Create Program.cs skeleton
+$programPath = Join-Path $samplePath "Program.cs"
+Write-Host "Creating Program.cs..."
+$programContent = @"
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// $Description
+ ///
+ ///
+ /// TODO: Add detailed description and prerequisites
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+ // TODO: Create any entities or data needed for the Run() method
+ // Add created entities to entityStore for cleanup
+ }
+
+ ///
+ /// Demonstrates the main sample functionality
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Running sample...");
+ // TODO: Add primary demonstration code here
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine(`$"Deleting {entityStore.Count} created records...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
+"@
+Set-Content -Path $programPath -Value $programContent -Encoding UTF8
+
+# Create README.md
+$readmePath = Join-Path $samplePath "README.md"
+Write-Host "Creating README.md..."
+
+$seeAlsoSection = ""
+if ($LearnMoreUrl) {
+ $seeAlsoSection = @"
+
+## See also
+
+[$Description]($LearnMoreUrl)
+"@
+}
+
+$readmeContent = @"
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "$Description"
+---
+
+# $SampleName
+
+$Description
+
+## What this sample does
+
+TODO: Describe the functionality demonstrated by this sample.
+
+## How this sample works
+
+### Setup
+
+TODO: Describe any setup operations performed.
+
+### Run
+
+TODO: Describe the main operations performed.
+
+### Cleanup
+
+TODO: Describe cleanup operations.
+
+## Demonstrates
+
+TODO: List the key SDK classes, messages, or patterns demonstrated:
+- Key class or message used
+- Important pattern demonstrated
+
+## Sample Output
+
+TODO: Include example console output
+
+``````
+Connected to Dataverse.
+
+Setting up sample data...
+Running sample...
+[Sample output here]
+Cleaning up...
+
+Press any key to exit.
+``````$seeAlsoSection
+"@
+Set-Content -Path $readmePath -Value $readmeContent -Encoding UTF8
+
+# Add project to solution
+$slnPath = Join-Path $categoryPath "$CategoryName.sln"
+if (Test-Path $slnPath) {
+ Write-Host "Adding project to solution..."
+ Push-Location $categoryPath
+ try {
+ dotnet sln add "$SampleName\$SampleName.csproj" | Out-Null
+ Write-Host "Project added to solution successfully."
+ } catch {
+ Write-Warning "Failed to add project to solution: $_"
+ } finally {
+ Pop-Location
+ }
+} else {
+ Write-Warning "Solution file not found. Project not added to solution."
+}
+
+Write-Host ""
+Write-Host "Sample '$SampleName' created successfully in category '$CategoryName'" -ForegroundColor Green
+Write-Host "Location: $samplePath" -ForegroundColor Green
+Write-Host ""
+Write-Host "Next steps:"
+Write-Host "1. Edit Program.cs to implement the sample logic"
+Write-Host "2. Update README.md with detailed documentation"
+Write-Host "3. Test with 'dotnet build' and 'dotnet run'"
+Write-Host "4. Update the category README.md to include this sample in the table"
diff --git a/dataverse/orgsvc/CSharp-NETCore/Scripts/Test-MigratedSamples.ps1 b/dataverse/orgsvc/CSharp-NETCore/Scripts/Test-MigratedSamples.ps1
new file mode 100644
index 00000000..c65c8a4e
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Scripts/Test-MigratedSamples.ps1
@@ -0,0 +1,203 @@
+# Test-MigratedSamples.ps1
+# Validates that all samples in a category build successfully
+
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory=$true)]
+ [string]$CategoryName,
+
+ [Parameter(Mandatory=$false)]
+ [switch]$TestSolution,
+
+ [Parameter(Mandatory=$false)]
+ [switch]$Verbose
+)
+
+$ErrorActionPreference = "Stop"
+
+# Get the script's directory and navigate to CSharp-NETCore root
+$scriptDir = Split-Path -Parent $PSCommandPath
+$csharpNETCoreRoot = Split-Path -Parent $scriptDir
+
+# Verify category exists
+$categoryPath = Join-Path $csharpNETCoreRoot $CategoryName
+if (-not (Test-Path $categoryPath)) {
+ Write-Error "Category folder '$CategoryName' does not exist at $categoryPath"
+ exit 1
+}
+
+Write-Host ""
+Write-Host "Testing samples in category: $CategoryName" -ForegroundColor Cyan
+Write-Host "Category path: $categoryPath"
+Write-Host ""
+
+$results = @()
+$totalTests = 0
+$passedTests = 0
+$failedTests = 0
+
+# Test solution file if requested
+if ($TestSolution) {
+ $slnPath = Join-Path $categoryPath "$CategoryName.sln"
+ if (Test-Path $slnPath) {
+ Write-Host "Testing solution: $CategoryName.sln" -ForegroundColor Yellow
+ Write-Host "Running: dotnet build `"$slnPath`""
+ Write-Host ""
+
+ $totalTests++
+ Push-Location $categoryPath
+ try {
+ if ($Verbose) {
+ $buildOutput = dotnet build "$slnPath" 2>&1
+ Write-Host $buildOutput
+ } else {
+ $buildOutput = dotnet build "$slnPath" 2>&1 | Out-String
+ }
+
+ if ($LASTEXITCODE -eq 0) {
+ $passedTests++
+ $results += @{
+ Name = "$CategoryName.sln"
+ Type = "Solution"
+ Status = "PASS"
+ Path = $slnPath
+ }
+ Write-Host "Solution build: PASS" -ForegroundColor Green
+ } else {
+ $failedTests++
+ $results += @{
+ Name = "$CategoryName.sln"
+ Type = "Solution"
+ Status = "FAIL"
+ Path = $slnPath
+ Error = $buildOutput
+ }
+ Write-Host "Solution build: FAIL" -ForegroundColor Red
+ if (-not $Verbose) {
+ Write-Host "Error output:" -ForegroundColor Red
+ Write-Host $buildOutput
+ }
+ }
+ } catch {
+ $failedTests++
+ $results += @{
+ Name = "$CategoryName.sln"
+ Type = "Solution"
+ Status = "FAIL"
+ Path = $slnPath
+ Error = $_.Exception.Message
+ }
+ Write-Host "Solution build: FAIL - $($_.Exception.Message)" -ForegroundColor Red
+ } finally {
+ Pop-Location
+ }
+ Write-Host ""
+ } else {
+ Write-Warning "Solution file not found: $CategoryName.sln"
+ Write-Host ""
+ }
+}
+
+# Find all .csproj files in sample directories
+$projects = Get-ChildItem -Path $categoryPath -Filter "*.csproj" -Recurse | Where-Object {
+ $_.FullName -notlike "*\bin\*" -and
+ $_.FullName -notlike "*\obj\*"
+}
+
+if ($projects.Count -eq 0) {
+ Write-Warning "No project files found in category '$CategoryName'"
+ exit 0
+}
+
+Write-Host "Found $($projects.Count) project(s) to test"
+Write-Host ""
+
+# Test each project
+foreach ($project in $projects) {
+ $projectName = $project.BaseName
+ $projectDir = $project.DirectoryName
+
+ Write-Host "Testing: $projectName" -ForegroundColor Yellow
+ Write-Host "Path: $($project.FullName)"
+ Write-Host "Running: dotnet build"
+ Write-Host ""
+
+ $totalTests++
+ Push-Location $projectDir
+ try {
+ if ($Verbose) {
+ $buildOutput = dotnet build 2>&1
+ Write-Host $buildOutput
+ } else {
+ $buildOutput = dotnet build 2>&1 | Out-String
+ }
+
+ if ($LASTEXITCODE -eq 0) {
+ $passedTests++
+ $results += @{
+ Name = $projectName
+ Type = "Project"
+ Status = "PASS"
+ Path = $project.FullName
+ }
+ Write-Host "Build result: PASS" -ForegroundColor Green
+ } else {
+ $failedTests++
+ $results += @{
+ Name = $projectName
+ Type = "Project"
+ Status = "FAIL"
+ Path = $project.FullName
+ Error = $buildOutput
+ }
+ Write-Host "Build result: FAIL" -ForegroundColor Red
+ if (-not $Verbose) {
+ Write-Host "Error output:" -ForegroundColor Red
+ Write-Host $buildOutput
+ }
+ }
+ } catch {
+ $failedTests++
+ $results += @{
+ Name = $projectName
+ Type = "Project"
+ Status = "FAIL"
+ Path = $project.FullName
+ Error = $_.Exception.Message
+ }
+ Write-Host "Build result: FAIL - $($_.Exception.Message)" -ForegroundColor Red
+ } finally {
+ Pop-Location
+ }
+
+ Write-Host ""
+ Write-Host ("=" * 80)
+ Write-Host ""
+}
+
+# Display summary
+Write-Host ""
+Write-Host "Test Summary for $CategoryName" -ForegroundColor Cyan
+Write-Host ("=" * 80) -ForegroundColor Cyan
+Write-Host "Total tests: $totalTests"
+Write-Host "Passed: $passedTests" -ForegroundColor Green
+Write-Host "Failed: $failedTests" -ForegroundColor $(if ($failedTests -gt 0) { "Red" } else { "Green" })
+Write-Host ""
+
+if ($passedTests -eq $totalTests) {
+ Write-Host "All tests passed!" -ForegroundColor Green
+ $exitCode = 0
+} else {
+ Write-Host "Some tests failed. Review the output above for details." -ForegroundColor Red
+ $exitCode = 1
+
+ Write-Host ""
+ Write-Host "Failed projects:" -ForegroundColor Red
+ foreach ($result in $results | Where-Object { $_.Status -eq "FAIL" }) {
+ Write-Host " - $($result.Name) ($($result.Type))"
+ }
+}
+
+Write-Host ""
+
+exit $exitCode
diff --git a/dataverse/orgsvc/CSharp-NETCore/Templates/CategoryREADME.md.template b/dataverse/orgsvc/CSharp-NETCore/Templates/CategoryREADME.md.template
new file mode 100644
index 00000000..1c6e9ea2
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Templates/CategoryREADME.md.template
@@ -0,0 +1,56 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "{{DESCRIPTION}}"
+---
+
+# {{CATEGORY_NAME}}
+
+{{DESCRIPTION}}
+
+{{LEARN_MORE_SECTION}}
+
+## Samples
+
+This folder contains the following samples:
+
+|Sample folder|Description|Build target|
+|---|---|---|
+| | |.NET 6|
+
+## Prerequisites
+
+- Microsoft Visual Studio 2022
+- Access to Dataverse with appropriate privileges for the operations demonstrated
+
+## How to run samples
+
+1. Clone or download the PowerApps-Samples repository
+2. Navigate to `/dataverse/orgsvc/CSharp-NETCore/{{CATEGORY_NAME}}/`
+3. Open `{{CATEGORY_NAME}}.sln` in Visual Studio 2022
+4. Edit the `appsettings.json` file in the category folder root with your Dataverse environment details:
+ - Set `Url` to your Dataverse environment URL
+ - Set `Username` to your user account
+5. Build and run the desired sample project
+
+## appsettings.json
+
+Each sample in this category references the shared `appsettings.json` file in the category root folder. The connection string format is:
+
+```json
+{
+ "ConnectionStrings": {
+ "default": "AuthType=OAuth;Url=https://yourorg.crm.dynamics.com;Username=youruser@yourdomain.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;LoginPrompt=Auto"
+ }
+}
+```
+
+You can also set the `DATAVERSE_APPSETTINGS` environment variable to point to a custom appsettings.json file location if you prefer to keep your connection string outside the repository.
+
+## See also
+
+[SDK for .NET](https://learn.microsoft.com/power-apps/developer/data-platform/org-service/overview)
diff --git a/dataverse/orgsvc/CSharp-NETCore/Templates/Program.cs.template b/dataverse/orgsvc/CSharp-NETCore/Templates/Program.cs.template
new file mode 100644
index 00000000..b8c9d92c
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Templates/Program.cs.template
@@ -0,0 +1,126 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerPlatform.Dataverse.Client;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace PowerPlatform.Dataverse.CodeSamples
+{
+ ///
+ /// {{DESCRIPTION}}
+ ///
+ ///
+ /// {{DETAILED_DESCRIPTION}}
+ ///
+ /// Set the appropriate Url and Username values for your test
+ /// environment in the appsettings.json file before running this program.
+ ///
+ class Program
+ {
+ private static readonly List entityStore = new();
+
+ #region Sample Methods
+
+ ///
+ /// Sets up sample data required for the demonstration
+ ///
+ private static void Setup(ServiceClient service)
+ {
+ Console.WriteLine("Setting up sample data...");
+ // Create any entities or data needed for the Run() method
+ // Add created entities to entityStore for cleanup
+ }
+
+ ///
+ /// Demonstrates {{SAMPLE_PURPOSE}}
+ ///
+ private static void Run(ServiceClient service)
+ {
+ Console.WriteLine("Running sample...");
+ // Primary demonstration code goes here
+ }
+
+ ///
+ /// Cleans up sample data created during execution
+ ///
+ private static void Cleanup(ServiceClient service, bool deleteCreatedRecords)
+ {
+ Console.WriteLine("Cleaning up...");
+ if (deleteCreatedRecords && entityStore.Count > 0)
+ {
+ Console.WriteLine($"Deleting {entityStore.Count} created records...");
+ foreach (var entityRef in entityStore)
+ {
+ service.Delete(entityRef.LogicalName, entityRef.Id);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Application Setup
+
+ ///
+ /// Contains the application's configuration settings.
+ ///
+ IConfiguration Configuration { get; }
+
+ ///
+ /// Constructor. Loads the application configuration settings from a JSON file.
+ ///
+ Program()
+ {
+ // Get the path to the appsettings file. If the environment variable is set,
+ // use that file path. Otherwise, use the runtime folder's settings file.
+ string? path = Environment.GetEnvironmentVariable("DATAVERSE_APPSETTINGS");
+ if (path == null) path = "appsettings.json";
+
+ // Load the app's configuration settings from the JSON file.
+ Configuration = new ConfigurationBuilder()
+ .AddJsonFile(path, optional: false, reloadOnChange: true)
+ .Build();
+ }
+
+ static void Main(string[] args)
+ {
+ Program app = new();
+
+ // Create a Dataverse service client using the default connection string.
+ ServiceClient serviceClient =
+ new(app.Configuration.GetConnectionString("default"));
+
+ if (!serviceClient.IsReady)
+ {
+ Console.WriteLine("Failed to connect to Dataverse.");
+ Console.WriteLine("Error: {0}", serviceClient.LastError);
+ return;
+ }
+
+ Console.WriteLine("Connected to Dataverse.");
+ Console.WriteLine();
+
+ bool deleteCreatedRecords = true;
+
+ try
+ {
+ Setup(serviceClient);
+ Run(serviceClient);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("An error occurred:");
+ Console.WriteLine(ex.Message);
+ }
+ finally
+ {
+ Cleanup(serviceClient, deleteCreatedRecords);
+
+ Console.WriteLine();
+ Console.WriteLine("Press any key to exit.");
+ Console.ReadKey();
+ serviceClient.Dispose();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/dataverse/orgsvc/CSharp-NETCore/Templates/Sample.csproj.template b/dataverse/orgsvc/CSharp-NETCore/Templates/Sample.csproj.template
new file mode 100644
index 00000000..d73af524
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Templates/Sample.csproj.template
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ net6.0
+ PowerPlatform.Dataverse.CodeSamples
+ enable
+ enable
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
diff --git a/dataverse/orgsvc/CSharp-NETCore/Templates/SampleREADME.md.template b/dataverse/orgsvc/CSharp-NETCore/Templates/SampleREADME.md.template
new file mode 100644
index 00000000..800d71cf
--- /dev/null
+++ b/dataverse/orgsvc/CSharp-NETCore/Templates/SampleREADME.md.template
@@ -0,0 +1,52 @@
+---
+languages:
+- csharp
+products:
+- power-platform
+- power-apps
+page_type: sample
+description: "{{DESCRIPTION}}"
+---
+
+# {{SAMPLE_NAME}}
+
+{{DESCRIPTION}}
+
+## What this sample does
+
+{{WHAT_IT_DOES}}
+
+## How this sample works
+
+### Setup
+
+{{SETUP_DESCRIPTION}}
+
+### Run
+
+{{RUN_DESCRIPTION}}
+
+### Cleanup
+
+{{CLEANUP_DESCRIPTION}}
+
+## Demonstrates
+
+{{DEMONSTRATES_LIST}}
+
+## Sample Output
+
+```
+Connected to Dataverse.
+
+Setting up sample data...
+Running sample...
+{{SAMPLE_OUTPUT}}
+Cleaning up...
+
+Press any key to exit.
+```
+
+## See also
+
+{{SEE_ALSO_LINKS}}