From ba17ac68a6f2c4da3124ae58a337870c130edf5a Mon Sep 17 00:00:00 2001 From: David Burg Date: Wed, 10 Jun 2026 22:52:24 -0700 Subject: [PATCH 1/7] feat: add Microsoft Dataverse (commondataservice) connector client Generate the current-environment Microsoft Dataverse connector client via the BPM CodefulSdkGenerator (--directClient --connectors=commondataservice). The generated CommondataserviceClient covers modern row operations (ListRecords, CreateRecord, GetItemCodeless, UpdateRecord, DeleteRecord), bound/unbound actions, relate/unrelate, relevance search, file/image column upload/download, and changeset transactions. Deprecated legacy dataset/table operations are excluded by the generator's existing deprecation filter. Adds CommondataserviceClientTests.cs (constructors, dispose, mocked API call, error handling, serialization round-trips). Updates ROADMAP (2.1 Dataverse -> Complete), CHANGELOG [Unreleased], and the connection-setup skill connector list. Registry files (ConnectorNames/ManagedConnectors) already contained commondataservice. --- .github/skills/connection-setup/SKILL.md | 2 +- CHANGELOG.md | 4 + ROADMAP.md | 2 +- .../Generated/CommondataserviceExtensions.cs | 1883 +++++++++++++++++ .../CommondataserviceClientTests.cs | 214 ++ 5 files changed, 2103 insertions(+), 2 deletions(-) create mode 100644 src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs create mode 100644 tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs diff --git a/.github/skills/connection-setup/SKILL.md b/.github/skills/connection-setup/SKILL.md index e3a8da6..8a5e5fc 100644 --- a/.github/skills/connection-setup/SKILL.md +++ b/.github/skills/connection-setup/SKILL.md @@ -65,7 +65,7 @@ Remove-Item $tempFile -ErrorAction SilentlyContinue ### Step 2: Create Connection -Supported SDK connector names: `arm`, `azuread`, `azureblob`, `azureeventgrid`, `azureiotcentral`, `azuremonitorlogs`, `azurequeues`, `azuretables`, `box`, `campfire`, `clicksendsms`, `cloudmersiveconvert`, `docusign`, `documentdb`, `docuware`, `dropbox`, `dynamicsax`, `elfsquaddata`, `etsy`, `eventbrite`, `eventhubs`, `excelonline`, `excelonlinebusiness`, `formstackforms`, `freshservice`, `ftp`, `github`, `googlecalendar`, `googledrive`, `googletasks`, `impexium`, `infusionsoft`, `insightly`, `jedoxodatahub`, `jira`, `kusto`, `mailchimp`, `meetingroommap`, `microsoftforms`, `monday`, `mq`, `msgraphgroupsanduser`, `office365`, `office365users`, `onedrive`, `onedriveforbusiness`, `orderful`, `outlook`, `pdfco`, `pipedrive`, `plivo`, `plumsail`, `projectplace`, `replicon`, `revai`, `rss`, `salesforce`, `seismicplanner`, `sendgrid`, `servicebus`, `sharepointonline`, `signinghub`, `slack`, `smtp`, `sql`, `starmind`, `starrezrestv1`, `tallyfy`, `teams`, `textrequest`, `ticketmaster`, `trello`, `twitter`, `typeform`, `universalprint`, `waywedo`, `wdatp`, `webex`, `wordpress`, `wordonlinebusiness`, `yammer`, `zendesk`, `azureautomation`, `azuredatafactory`, `azuredigitaltwins`, `azurevm`, `keyvault`, `microsoftbookings`, `office365groups`, `office365groupsmail`, `onenote`, `planner`, `powerbi`, `shifts`, `todo`, `zohosign` (and any `Microsoft.Web/connections` connector name). +Supported SDK connector names: `arm`, `azuread`, `azureblob`, `azureeventgrid`, `azureiotcentral`, `azuremonitorlogs`, `azurequeues`, `azuretables`, `box`, `campfire`, `clicksendsms`, `cloudmersiveconvert`, `commondataservice`, `docusign`, `documentdb`, `docuware`, `dropbox`, `dynamicsax`, `elfsquaddata`, `etsy`, `eventbrite`, `eventhubs`, `excelonline`, `excelonlinebusiness`, `formstackforms`, `freshservice`, `ftp`, `github`, `googlecalendar`, `googledrive`, `googletasks`, `impexium`, `infusionsoft`, `insightly`, `jedoxodatahub`, `jira`, `kusto`, `mailchimp`, `meetingroommap`, `microsoftforms`, `monday`, `mq`, `msgraphgroupsanduser`, `office365`, `office365users`, `onedrive`, `onedriveforbusiness`, `orderful`, `outlook`, `pdfco`, `pipedrive`, `plivo`, `plumsail`, `projectplace`, `replicon`, `revai`, `rss`, `salesforce`, `seismicplanner`, `sendgrid`, `servicebus`, `sharepointonline`, `signinghub`, `slack`, `smtp`, `sql`, `starmind`, `starrezrestv1`, `tallyfy`, `teams`, `textrequest`, `ticketmaster`, `trello`, `twitter`, `typeform`, `universalprint`, `waywedo`, `wdatp`, `webex`, `wordpress`, `wordonlinebusiness`, `yammer`, `zendesk`, `azureautomation`, `azuredatafactory`, `azuredigitaltwins`, `azurevm`, `keyvault`, `microsoftbookings`, `office365groups`, `office365groupsmail`, `onenote`, `planner`, `powerbi`, `shifts`, `todo`, `zohosign` (and any `Microsoft.Web/connections` connector name). ```powershell $connectorName = "" # e.g., "arm", "azureblob", "azuremonitorlogs", "kusto", "mq", "msgraphgroupsanduser", "office365", "office365users", "onedriveforbusiness", "sharepointonline", "smtp", "teams" diff --git a/CHANGELOG.md b/CHANGELOG.md index c835e96..7dae430 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the same content into release_notes.md for NuGet packaging. Do NOT put HTML comments in release_notes.md — it is packed verbatim into . --> +### Added + +- **Microsoft Dataverse client (`commondataservice`)** — generated typed `CommondataserviceClient` for the current-environment Microsoft Dataverse connector. Covers row operations (`ListRecordsAsync`, `CreateRecordAsync`, `GetItemCodelessAsync`, `UpdateRecordAsync`, `DeleteRecordAsync`), bound/unbound actions (`PerformBoundActionAsync`, `PerformUnboundActionAsync`), relate/unrelate (`AssociateEntitiesAsync`, `DisassociateEntitiesAsync`), relevance search (`GetRelevantRowsAsync`), file/image column upload & download, and changeset transactions (`ExecuteChangesetAsync`). Deprecated legacy dataset/table operations are excluded by the generator's deprecation filter. + ## [0.12.0-preview.1] - 2026-06-02 ### Breaking Changes diff --git a/ROADMAP.md b/ROADMAP.md index 608de3e..1d15850 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -42,7 +42,7 @@ High-volume connectors for enterprise data and integration scenarios. | Priority | Connector | Usage Rank | Partner Tier | 90-Day Executions | Notes | Status | |----------|-----------|------------|--------------|-------------------|-------|--------| -| 2.1 | **Dataverse (CDS)** | #12 | Tier 1 | 2.93B | Power Platform integration | ⬜ Not started | +| 2.1 | **Dataverse** (`commondataservice`) | #12 | Tier 1 | 2.93B | Current-environment Microsoft Dataverse — rows, bound/unbound actions, relate/unrelate, relevance search, changesets | ✅ Complete | | 2.2 | **Excel Online Business** | #9 | Tier 2 | 957M | Spreadsheet operations | ⬜ Not started | | 2.3 | **Dynamics 365** | — | Tier 2 | 315M | CRM record operations | ⬜ Not started | | 2.4 | **Salesforce** | — | Tier 2 | 127M | CRM operations | ⬜ Not started | diff --git a/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs b/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs new file mode 100644 index 0000000..df00e71 --- /dev/null +++ b/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs @@ -0,0 +1,1883 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +// CommondataserviceExtensions.cs - Auto-generated Connectors SDK +// Do not edit this file directly. + +#nullable disable +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Azure; +using Azure.Connectors.Sdk; +using Azure.Connectors.Sdk.Commondataservice.Models; +using Azure.Core; +using Azure.Identity; + +namespace Azure.Connectors.Sdk.Commondataservice.Models +{ + + #region Types + + /// + /// Response for Get table metadata + /// + public class EntityMetadata + { + /// Swagger schema + [JsonPropertyName("schema")] + public ObjectEntity Schema { get; set; } + } + + /// + /// Swagger schema + /// + public class ObjectEntity + { + /// + /// Arbitrary properties. This type has no static schema; any JSON properties will be captured here. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + } + + /// + /// Response for GetEntityRelationships + /// + public class EntityRelationshipsDynamicValuesList + { + /// List of table relationships as dynamic values. + [JsonPropertyName("value")] + public List Value { get; set; } + } + + /// + /// Response for GetOrganizations + /// + public class OrganizationsDynamicValuesList + { + /// List of organizations as dynamic values. + [JsonPropertyName("value")] + public List Value { get; set; } + } + + /// + /// Item in List of organizations as dynamic values. + /// + public class OrganizationsDynamicValuesListItem + { + /// The organization id. + [JsonPropertyName("Id")] + public string Id { get; set; } + + /// The name of the organization. + [JsonPropertyName("FriendlyName")] + public string FriendlyName { get; set; } + + /// The URL of the organization. + [JsonPropertyName("Url")] + public string Url { get; set; } + } + + /// + /// Response for GetEntityListEnum + /// + public class EntitiesDynamicValuesList + { + /// List of tables as dynamic values. + [JsonPropertyName("value")] + public List Value { get; set; } + } + + /// + /// Item in List of tables as dynamic values. + /// + public class EntitiesDynamicValuesListItem + { + /// The metadata id. + [JsonPropertyName("metadataId")] + public string MetadataId { get; set; } + + /// The name of the table. + [JsonPropertyName("entitySetName")] + public string EntitySetName { get; set; } + + /// The logical name of the table. + [JsonPropertyName("logicalName")] + public string LogicalName { get; set; } + + /// The display name of the table. + [JsonPropertyName("displayCollectionName")] + public DisplayCollectionName DisplayCollectionName { get; set; } + } + + /// + /// The display name of the table. + /// + public class DisplayCollectionName + { + /// The user localized display name label object. + [JsonPropertyName("userLocalizedLabel")] + public UserLocalizedLabel UserLocalizedLabel { get; set; } + } + + /// + /// The user localized display name label object. + /// + public class UserLocalizedLabel + { + /// The display name label of the table. + [JsonPropertyName("label")] + public string Label { get; set; } + } + + /// + /// Response for List rows + /// + public class EntityItemList : IPageable + { + /// List of Items + [JsonPropertyName("value")] + public List Value { get; set; } + + /// The url to fetch next page data. + [JsonPropertyName("@odata.nextLink")] + public string NextLink { get; set; } + } + + /// + /// Item in List of Items + /// + [DynamicSchema("GetMetadataForGetEntity")] + public class EntityItem + { + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + + /// dynamicProperties + [JsonPropertyName("dynamicProperties")] + public JsonElement? DynamicProperties { get; set; } + } + + /// + /// Add a new row + /// + [DynamicSchema("GetMetadataForPostEntity")] + public class CreateRecordInput + { + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + } + + /// + /// Response for Add a new row + /// + [DynamicSchema("GetMetadataForGetEntity")] + public class CreateRecordResponse + { + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + } + + /// + /// Response for Get a row by ID + /// + [DynamicSchema("GetMetadataForGetEntity")] + public class GetItemCodelessResponse + { + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + } + + /// + /// Update a row + /// + [DynamicSchema("GetMetadataForPatchEntity")] + public class UpdateRecordInput + { + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + } + + /// + /// Response for Update a row + /// + [DynamicSchema("GetMetadataForGetEntity")] + public class UpdateRecordResponse + { + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + } + + /// + /// Response for GetUnboundActions + /// + public class ActionsDynamicValuesList + { + /// List of actions as dynamic values + [JsonPropertyName("value")] + public List Value { get; set; } + } + + /// + /// Perform an unbound action + /// + [DynamicSchema("GetMetadataForUnboundActionInput")] + public class PerformUnboundActionInput + { + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + } + + /// + /// Response for Perform an unbound action + /// + [DynamicSchema("GetMetadataForUnboundActionResponse")] + public class PerformUnboundActionResponse + { + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + } + + /// + /// Perform a bound action + /// + [DynamicSchema("GetMetadataForBoundActionInput")] + public class PerformBoundActionInput + { + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + } + + /// + /// Response for Perform a bound action + /// + [DynamicSchema("GetMetadataForBoundActionResponse")] + public class PerformBoundActionResponse + { + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); + } + + /// + /// Response for Search rows + /// + public class SearchOutput + { + /// List of rows + [JsonPropertyName("value")] + public List ListOfRows { get; set; } + + /// Total count of results (-1 if returntotalrecordcount is set to false) + [JsonPropertyName("totalrecordcount")] + public long? TotalRowCount { get; set; } + + /// Facet results + [JsonPropertyName("facets")] + public JsonElement? FacetResults { get; set; } + } + + /// + /// Response for Get action metadata + /// + public class GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerResponse + { + /// Swagger schema + [JsonPropertyName("schema")] + public ObjectEntity Schema { get; set; } + } + + /// + /// Response for Get metadata for unbound action input + /// + public class GetMetadataForUnboundActionInputResponse + { + /// Swagger schema + [JsonPropertyName("schema")] + public ObjectEntity Schema { get; set; } + } + + /// + /// Response for Get metadata for unbound action response + /// + public class GetMetadataForUnboundActionResponseResponse + { + /// Swagger schema + [JsonPropertyName("schema")] + public ObjectEntity Schema { get; set; } + } + + /// + /// Response for Get metadata for bound action input + /// + public class GetMetadataForBoundActionInputResponse + { + /// Swagger schema + [JsonPropertyName("schema")] + public ObjectEntity Schema { get; set; } + } + + /// + /// Response for Get metadata for bound action response + /// + public class GetMetadataForBoundActionResponseResponse + { + /// Swagger schema + [JsonPropertyName("schema")] + public ObjectEntity Schema { get; set; } + } + + /// + /// CallbackRegistration + /// + public class CallbackRegistration + { + /// The callback registration table version + [JsonPropertyName("version")] + public int? TableVersion { get; set; } + + /// Callback url to the flow engine. Expected as part of the request and provided by Flow. + [JsonPropertyName("url")] + public string NotificationUrl { get; set; } + + /// Callback registration name. Value will be replaced with workflow id + [JsonPropertyName("name")] + public string Name { get; set; } + + /// Choose a table + [JsonPropertyName("entityname")] + public string TableName { get; set; } + + /// Choose when the flow triggers + [JsonPropertyName("message")] + public int? ChangeType { get; set; } + + /// Choose an option or add your own + [JsonPropertyName("sdkmessagename")] + public string ActionName { get; set; } + + /// Choose a scope to limit which rows can trigger the flow + [JsonPropertyName("scope")] + public int? Scope { get; set; } + + /// Enter a comma-separated list of column unique names. The flow triggers if any of them are modified + [JsonPropertyName("filteringattributes")] + public string SelectColumns { get; set; } + + /// Enter an OData style filter expression to determine which rows can trigger the flow + [JsonPropertyName("filterexpression")] + public string FilterRows { get; set; } + + /// Enter a time to delay the trigger evaluation, eg. 2020-01-01T10:10:00Z + [JsonPropertyName("postponeuntil")] + public string DelayUntil { get; set; } + + /// Choose the running user for steps where invoker connections are used + [JsonPropertyName("runas")] + public int? RunAs { get; set; } + } + + /// + /// AssociateEntityRequest + /// + public class AssociateEntityRequest + { + /// Enter the row URL using OData ID from a previous step or typing the full URL (eg. https://org0.crm.dynamics.com/api/data/v9.0/faxes(3ce6c728-3c8a-4b55-a4ee-a251b253c3ee) + [JsonPropertyName("@odata.id")] + public string RelateWith { get; set; } + } + + /// + /// SearchRequestBody + /// + public class SearchRequestBody + { + /// Enter a search term, eg. Contoso. Searches modifiers like boolean operators, wildcards, fuzzy search, proximity search etc. require the search type full + [JsonPropertyName("search")] + public string SearchTerm { get; set; } + + /// Enter whether simple or full search syntax should be used (default is simple) + [JsonPropertyName("searchtype")] + public string SearchType { get; set; } + + /// Enter whether any or all of the search terms must be matched (default is any) + [JsonPropertyName("searchmode")] + public string SearchMode { get; set; } + + /// Enter the number of search results to be listed (default = 50) + [JsonPropertyName("top")] + public int? RowCount { get; set; } + + /// Enter an Odata style filter expression to narrow the search + [JsonPropertyName("filter")] + public string RowFilter { get; set; } + + /// Enter a comma-separated list of tables to be searched (default is all tables) + [JsonPropertyName("entities")] + public List TableFilter { get; set; } + + /// Enter a comma-separated list of column unique names followed by asc or desc + [JsonPropertyName("orderby")] + public List SortBy { get; set; } + + /// Enter a comma-separated list of facet queries to narrow the search + [JsonPropertyName("facets")] + public List FacetQuery { get; set; } + + /// Enter the number of search results to be skipped + [JsonPropertyName("skip")] + public int? SkipRows { get; set; } + + /// Choose an option + [JsonPropertyName("returntotalrecordcount")] + public bool? ReturnRowCount { get; set; } + } + + /// + /// WhenAnActionIsPerformedSubscriptionRequest + /// + public class WhenAnActionIsPerformedSubscriptionRequest + { + /// The callback registration table version. + [JsonPropertyName("version")] + public int? TableVersion { get; set; } + + /// Callback url to the flow engine. Expected as part of the request and provided by Flow. + [JsonPropertyName("url")] + public string NotificationUrl { get; set; } + + /// Callback registration name. Value will be replaced with workflow id. + [JsonPropertyName("name")] + public string Name { get; set; } + + /// Choose a scope to limit which rows can trigger the flow. + [JsonPropertyName("scope")] + public int? Scope { get; set; } + + /// Choose a table to filter actions. + [JsonPropertyName("entityname")] + public string TableName { get; set; } + + /// Choose an action. + [JsonPropertyName("sdkmessagename")] + public string ActionName { get; set; } + } + + #endregion Types + + #region Model Factory + + /// + /// Model factory for creating instances of Commondataservice models. + /// Use these factory methods to construct model instances in tests and scenarios + /// where output-only properties (with init-only setters) need to be populated. + /// + public static class CommondataserviceModelFactory + { + /// + /// Creates a new instance of . + /// + public static EntityMetadata EntityMetadata( + ObjectEntity schema = default) + { + return new EntityMetadata + { + Schema = schema, + }; + } + + /// + /// Creates a new instance of . + /// + public static EntityRelationshipsDynamicValuesList EntityRelationshipsDynamicValuesList( + List value = default) + { + return new EntityRelationshipsDynamicValuesList + { + Value = value, + }; + } + + /// + /// Creates a new instance of . + /// + public static OrganizationsDynamicValuesList OrganizationsDynamicValuesList( + List value = default) + { + return new OrganizationsDynamicValuesList + { + Value = value, + }; + } + + /// + /// Creates a new instance of . + /// + public static OrganizationsDynamicValuesListItem OrganizationsDynamicValuesListItem( + string id = default, + string friendlyName = default, + string url = default) + { + return new OrganizationsDynamicValuesListItem + { + Id = id, + FriendlyName = friendlyName, + Url = url, + }; + } + + /// + /// Creates a new instance of . + /// + public static EntitiesDynamicValuesList EntitiesDynamicValuesList( + List value = default) + { + return new EntitiesDynamicValuesList + { + Value = value, + }; + } + + /// + /// Creates a new instance of . + /// + public static EntitiesDynamicValuesListItem EntitiesDynamicValuesListItem( + string metadataId = default, + string entitySetName = default, + string logicalName = default, + DisplayCollectionName displayCollectionName = default) + { + return new EntitiesDynamicValuesListItem + { + MetadataId = metadataId, + EntitySetName = entitySetName, + LogicalName = logicalName, + DisplayCollectionName = displayCollectionName, + }; + } + + /// + /// Creates a new instance of . + /// + public static DisplayCollectionName DisplayCollectionName( + UserLocalizedLabel userLocalizedLabel = default) + { + return new DisplayCollectionName + { + UserLocalizedLabel = userLocalizedLabel, + }; + } + + /// + /// Creates a new instance of . + /// + public static UserLocalizedLabel UserLocalizedLabel( + string label = default) + { + return new UserLocalizedLabel + { + Label = label, + }; + } + + /// + /// Creates a new instance of . + /// + public static EntityItemList EntityItemList( + List value = default, + string nextLink = default) + { + return new EntityItemList + { + Value = value, + NextLink = nextLink, + }; + } + + /// + /// Creates a new instance of . + /// + public static EntityItem EntityItem( + JsonElement? dynamicProperties = default) + { + return new EntityItem + { + DynamicProperties = dynamicProperties, + }; + } + + /// + /// Creates a new instance of . + /// + public static ActionsDynamicValuesList ActionsDynamicValuesList( + List value = default) + { + return new ActionsDynamicValuesList + { + Value = value, + }; + } + + /// + /// Creates a new instance of . + /// + public static SearchOutput SearchOutput( + List listOfRows = default, + long? totalRowCount = default, + JsonElement? facetResults = default) + { + return new SearchOutput + { + ListOfRows = listOfRows, + TotalRowCount = totalRowCount, + FacetResults = facetResults, + }; + } + + /// + /// Creates a new instance of . + /// + public static GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerResponse GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerResponse( + ObjectEntity schema = default) + { + return new GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerResponse + { + Schema = schema, + }; + } + + /// + /// Creates a new instance of . + /// + public static GetMetadataForUnboundActionInputResponse GetMetadataForUnboundActionInputResponse( + ObjectEntity schema = default) + { + return new GetMetadataForUnboundActionInputResponse + { + Schema = schema, + }; + } + + /// + /// Creates a new instance of . + /// + public static GetMetadataForUnboundActionResponseResponse GetMetadataForUnboundActionResponseResponse( + ObjectEntity schema = default) + { + return new GetMetadataForUnboundActionResponseResponse + { + Schema = schema, + }; + } + + /// + /// Creates a new instance of . + /// + public static GetMetadataForBoundActionInputResponse GetMetadataForBoundActionInputResponse( + ObjectEntity schema = default) + { + return new GetMetadataForBoundActionInputResponse + { + Schema = schema, + }; + } + + /// + /// Creates a new instance of . + /// + public static GetMetadataForBoundActionResponseResponse GetMetadataForBoundActionResponseResponse( + ObjectEntity schema = default) + { + return new GetMetadataForBoundActionResponseResponse + { + Schema = schema, + }; + } + + /// + /// Creates a new instance of . + /// + public static CallbackRegistration CallbackRegistration( + int? tableVersion = default, + string notificationUrl = default, + string name = default, + string tableName = default, + int? changeType = default, + string actionName = default, + int? scope = default, + string selectColumns = default, + string filterRows = default, + string delayUntil = default, + int? runAs = default) + { + return new CallbackRegistration + { + TableVersion = tableVersion, + NotificationUrl = notificationUrl, + Name = name, + TableName = tableName, + ChangeType = changeType, + ActionName = actionName, + Scope = scope, + SelectColumns = selectColumns, + FilterRows = filterRows, + DelayUntil = delayUntil, + RunAs = runAs, + }; + } + + /// + /// Creates a new instance of . + /// + public static AssociateEntityRequest AssociateEntityRequest( + string relateWith = default) + { + return new AssociateEntityRequest + { + RelateWith = relateWith, + }; + } + + /// + /// Creates a new instance of . + /// + public static SearchRequestBody SearchRequestBody( + string searchTerm = default, + string searchType = default, + string searchMode = default, + int? rowCount = default, + string rowFilter = default, + List tableFilter = default, + List sortBy = default, + List facetQuery = default, + int? skipRows = default, + bool? returnRowCount = default) + { + return new SearchRequestBody + { + SearchTerm = searchTerm, + SearchType = searchType, + SearchMode = searchMode, + RowCount = rowCount, + RowFilter = rowFilter, + TableFilter = tableFilter, + SortBy = sortBy, + FacetQuery = facetQuery, + SkipRows = skipRows, + ReturnRowCount = returnRowCount, + }; + } + + /// + /// Creates a new instance of . + /// + public static WhenAnActionIsPerformedSubscriptionRequest WhenAnActionIsPerformedSubscriptionRequest( + int? tableVersion = default, + string notificationUrl = default, + string name = default, + int? scope = default, + string tableName = default, + string actionName = default) + { + return new WhenAnActionIsPerformedSubscriptionRequest + { + TableVersion = tableVersion, + NotificationUrl = notificationUrl, + Name = name, + Scope = scope, + TableName = tableName, + ActionName = actionName, + }; + } + } + + #endregion Model Factory + +} + +namespace Azure.Connectors.Sdk.Commondataservice +{ + + #region Trigger Operation Constants + + /// + /// Trigger operation name constants for the Commondataservice connector. + /// Use these constants with the [ConnectorTrigger] attribute's OperationName property + /// and with the Connector Namespace TriggerConfig operationName field. + /// + public static class CommondataserviceTriggerOperations + { + /// + /// When a row is added, modified or deleted. + /// + public const string OnSubscribeWebhookTrigger = "SubscribeWebhookTrigger"; + + /// + /// When an action is performed. + /// + public const string OnBusinessEventsTrigger = "BusinessEventsTrigger"; + + } + + #endregion Trigger Operation Constants + + #region Trigger Parameter Metadata + + /// + /// Trigger input parameter name constants for the Commondataservice connector. + /// These correspond to the Connector Namespace TriggerConfig parameters array. + /// + public static class CommondataserviceTriggerParameters + { + /// + /// Input parameters for the OnSubscribeWebhookTrigger trigger operation (operationId: SubscribeWebhookTrigger). + /// + public static class OnSubscribeWebhookTrigger + { + /// + /// The header parameter required to make Microsoft Dataverse by pass the cache and hit DB + /// Required. + /// Default: Strong. + /// + public const string Consistency = "Consistency"; + + /// + /// Using this header parameter to save catalog in flow definition and Microsoft Dataverse will ignore as they don't use this info + /// Required. + /// Default: all. + /// + public const string Catalog = "catalog"; + + /// + /// Using this header parameter to save category in flow definition and Microsoft Dataverse will ignore as they don't use this info + /// Required. + /// Default: all. + /// + public const string Category = "category"; + + /// + /// Select an Environment + /// Required. + /// Dynamic values from: GetOrganizations. + /// + public const string Organization = "organization"; + + } + + /// + /// Input parameters for the OnBusinessEventsTrigger trigger operation (operationId: BusinessEventsTrigger). + /// + public static class OnBusinessEventsTrigger + { + /// + /// The header parameter required to make CDS by pass the cache and hit DB. + /// Required. + /// Default: Strong. + /// + public const string Consistency = "Consistency"; + + /// + /// Select an Environment + /// Required. + /// Dynamic values from: GetOrganizations. + /// + public const string Organization = "organization"; + + /// + /// Choose an option to filter tables and actions. + /// Required. + /// Dynamic values from: GetCatalogs. + /// + public const string Catalog = "catalog"; + + /// + /// Choose an option to filter tables and actions. + /// Required. + /// Dynamic values from: GetEntityListEnum. + /// + public const string Category = "category"; + + } + + } + + #endregion Trigger Parameter Metadata + + #region Client + + /// + /// Typed client for commondataservice connector. + /// + public class CommondataserviceClient : ConnectorClientBase + { + /// + /// Creates a new CommondataserviceClient with the specified connection runtime URL. + /// Uses by default. + /// + /// The connection runtime URL from Azure Portal. + public CommondataserviceClient(Uri connectionRuntimeUrl) + : base(connectionRuntimeUrl) + { + } + + /// + /// Creates a new CommondataserviceClient with the specified connection runtime URL and credential. + /// + /// The connection runtime URL from Azure Portal. + /// The Azure credential for authentication. + /// Optional client options for retry, timeout, etc. + public CommondataserviceClient(Uri connectionRuntimeUrl, TokenCredential credential, ConnectorClientOptions options = null) + : base(connectionRuntimeUrl, credential, options) + { + } + + /// + /// Creates a new CommondataserviceClient with the specified connection runtime URL and credential. + /// + /// The connection runtime URL from Azure Portal. + /// The Azure credential for authentication. + public CommondataserviceClient(Uri connectionRuntimeUrl, TokenCredential credential) + : base(connectionRuntimeUrl, credential) + { + } + + /// + /// Creates a new CommondataserviceClient with the specified connection runtime URL string. + /// Uses by default. + /// + /// The connection runtime URL from Azure Portal. + public CommondataserviceClient(string connectionRuntimeUrl) + : base(connectionRuntimeUrl) + { + } + + protected CommondataserviceClient() : base() { } + + public override string ConnectorName => "commondataservice"; + + private static readonly System.Diagnostics.ActivitySource ConnectorActivitySource = new System.Diagnostics.ActivitySource("Azure.Connectors.Sdk.commondataservice"); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) => base.Equals(obj); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => base.GetHashCode(); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() => base.ToString(); + + /// + /// Get table metadata + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Table name + /// Select Query + /// Expand Query + /// Cancellation token. + /// The Get table metadata response. + public virtual async Task GetMetadataForGetEntityAsync(string tableName, string selectQuery = default, string expandQuery = default, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForGetEntityAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + var queryParams = new List(); + if (selectQuery != default) + queryParams.Add($"selectedEntityAttributes={Uri.EscapeDataString(selectQuery.ToString())}"); + if (expandQuery != default) + queryParams.Add($"expandEntityAttributes={Uri.EscapeDataString(expandQuery.ToString())}"); + var path = $"/api/flow/$metadata.json/entities/{Uri.EscapeDataString(tableName.ToString())}" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get table metadata + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Table name + /// Cancellation token. + /// The Get table metadata response. + public virtual async Task GetMetadataForGetEntityCUDTriggerAsync(string tableName, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForGetEntityCUDTriggerAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + var path = $"/api/flow/$metadata.json/entities/{Uri.EscapeDataString(tableName.ToString())}/cudtrigger"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get table metadata - Post + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Table name + /// Cancellation token. + /// The Get table metadata - Post response. + public virtual async Task GetMetadataForPostEntityAsync(string tableName, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForPostEntityAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + var path = $"/api/flow/$metadata.json/entities/{Uri.EscapeDataString(tableName.ToString())}/postitem"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get table metadata - Patch + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Table name + /// Cancellation token. + /// The Get table metadata - Patch response. + public virtual async Task GetMetadataForPatchEntityAsync(string tableName, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForPatchEntityAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + var path = $"/api/flow/$metadata.json/entities/{Uri.EscapeDataString(tableName.ToString())}/patchitem"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get attribute filters + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Cancellation token. + /// The Get attribute filters response. + public virtual async Task GetAttributeFiltersCodelessAsync(CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetAttributeFiltersCodelessAsync"); + try + { + var path = $"/api/flow/$metadata.json/entities/GetAttributeFilters"; + return await this + .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// GetEntityRelationships + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Cancellation token. + /// The GetEntityRelationships response. + public virtual async Task GetEntityRelationshipsAsync(CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetEntityRelationshipsAsync"); + try + { + var path = $"/api/flow/$metadata.json/GetEntityRelationships"; + return await this + .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// GetOrganizations + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Cancellation token. + /// The GetOrganizations response. + public virtual async Task GetOrganizationsAsync(CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetOrganizationsAsync"); + try + { + var path = $"/getorgs"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// GetEntityListEnum + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Cancellation token. + /// The GetEntityListEnum response. + public virtual async Task GetEntityListEnumAsync(CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetEntityListEnumAsync"); + try + { + var path = $"/api/flow/$metadata.json/GetEntityListEnum"; + return await this + .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// List rows + /// + /// This action allows you to list the rows in a Microsoft Dataverse table that match the selected options. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Table name + /// Select columns + /// Filter rows + /// Sort By + /// Expand Query + /// Fetch Xml Query + /// Row count + /// Skip token + /// Partition ID + /// Cancellation token. + /// An async enumerable of items across all pages. + public virtual AsyncPageable ListRecordsAsync([DynamicValues("GetEntityListEnum")] string tableName, string selectColumns = default, string filterRows = default, string sortBy = default, string expandQuery = default, string fetchXmlQuery = default, int? rowCount = default, string skipToken = default, string partitionId = default, CancellationToken cancellationToken = default) + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + var queryParams = new List(); + if (selectColumns != default) + queryParams.Add($"$select={Uri.EscapeDataString(selectColumns.ToString())}"); + if (filterRows != default) + queryParams.Add($"$filter={Uri.EscapeDataString(filterRows.ToString())}"); + if (sortBy != default) + queryParams.Add($"$orderby={Uri.EscapeDataString(sortBy.ToString())}"); + if (expandQuery != default) + queryParams.Add($"$expand={Uri.EscapeDataString(expandQuery.ToString())}"); + if (fetchXmlQuery != default) + queryParams.Add($"fetchXml={Uri.EscapeDataString(fetchXmlQuery.ToString())}"); + if (rowCount.HasValue) + queryParams.Add($"$top={Uri.EscapeDataString(rowCount.Value.ToString())}"); + if (skipToken != default) + queryParams.Add($"$skiptoken={Uri.EscapeDataString(skipToken.ToString())}"); + if (partitionId != default) + queryParams.Add($"partitionId={Uri.EscapeDataString(partitionId.ToString())}"); + var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); + return this.CreatePageable( + ct => this.CallConnectorAsync(HttpMethod.Get, path, cancellationToken: ct), + (nextLink, ct) => this.CallConnectorAsync(HttpMethod.Get, nextLink, cancellationToken: ct), + cancellationToken); + } + + /// + /// Add a new row + /// + /// This action allows you to add a new row in the selected Microsoft Dataverse table. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Table name + /// The request body. + /// Cancellation token. + /// The Add a new row response. + public virtual async Task CreateRecordAsync([DynamicValues("GetEntityListEnum")] string tableName, CreateRecordInput input, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.CreateRecordAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}"; + return await this + .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get a row by ID + /// + /// This action allows you to get the row that matches an ID in a Microsoft Dataverse table. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Table name + /// Row ID + /// Select columns + /// Expand Query + /// Partition Id + /// Cancellation token. + /// The Get a row by ID response. + public virtual async Task GetItemCodelessAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, string selectColumns = default, string expandQuery = default, string partitionId = default, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetItemCodelessAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (rowId is null) + throw new ArgumentNullException(nameof(rowId)); + var queryParams = new List(); + if (selectColumns != default) + queryParams.Add($"$select={Uri.EscapeDataString(selectColumns.ToString())}"); + if (expandQuery != default) + queryParams.Add($"$expand={Uri.EscapeDataString(expandQuery.ToString())}"); + if (partitionId != default) + queryParams.Add($"partitionId={Uri.EscapeDataString(partitionId.ToString())}"); + var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Delete a row + /// + /// This action allows you to delete a row from a Microsoft Dataverse table. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Table name + /// Row ID + /// Partition Id + /// Cancellation token. + public virtual async Task DeleteRecordAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, string partitionId = default, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.DeleteRecordAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (rowId is null) + throw new ArgumentNullException(nameof(rowId)); + var queryParams = new List(); + if (partitionId != default) + queryParams.Add($"partitionId={Uri.EscapeDataString(partitionId.ToString())}"); + var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); + await this + .CallConnectorAsync(HttpMethod.Delete, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Update a row + /// + /// This action allows you to modify any selected row in a Microsoft Dataverse table, or adds a new row if it doesn�t exist. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Table name + /// Row ID + /// The request body. + /// Cancellation token. + /// The Update a row response. + public virtual async Task UpdateRecordAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, UpdateRecordInput input, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.UpdateRecordAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (rowId is null) + throw new ArgumentNullException(nameof(rowId)); + var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})"; + return await this + .CallConnectorAsync(HttpMethod.Patch, path, input, cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Upload a file or an image + /// + /// This action allows you to upload a file or an image content to a Microsoft Dataverse table with a compatible column type. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Table name + /// Row ID + /// Column name + /// The request body. + /// Content name + /// Cancellation token. + public virtual async Task UpdateEntityFileImageFieldContentAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, [DynamicValues("GetAttributeFiltersCodeless")] string columnName, byte[] input, string contentName, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.UpdateEntityFileImageFieldContentAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (rowId is null) + throw new ArgumentNullException(nameof(rowId)); + if (columnName is null) + throw new ArgumentNullException(nameof(columnName)); + var queryParams = new List(); + if (contentName is null) + throw new ArgumentNullException(nameof(contentName)); + queryParams.Add($"x-ms-file-name={Uri.EscapeDataString(contentName.ToString())}"); + var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})/{Uri.EscapeDataString(columnName.ToString())}" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); + await this + .CallConnectorAsync(HttpMethod.Put, path, input, cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Download a file or an image + /// + /// This action allows you to download a file or an image content from a Microsoft Dataverse table. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Table name + /// Row ID + /// Column name + /// Image size + /// Cancellation token. + /// The Download a file or an image response. + public virtual async Task GetEntityFileImageFieldContentAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, [DynamicValues("GetAttributeFiltersCodeless")] string columnName, string imageSize = default, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetEntityFileImageFieldContentAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (rowId is null) + throw new ArgumentNullException(nameof(rowId)); + if (columnName is null) + throw new ArgumentNullException(nameof(columnName)); + var queryParams = new List(); + if (imageSize != default) + queryParams.Add($"size={Uri.EscapeDataString(imageSize.ToString())}"); + var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})/{Uri.EscapeDataString(columnName.ToString())}/$value" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// GetUnboundActions + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Cancellation token. + /// The GetUnboundActions response. + public virtual async Task GetUnboundActionsAsync(CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetUnboundActionsAsync"); + try + { + var path = $"/api/flow/$metadata.json/actionListEnum/unboundactions"; + return await this + .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// GetBoundActions + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Cancellation token. + /// The GetBoundActions response. + public virtual async Task GetBoundActionsAsync(CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetBoundActionsAsync"); + try + { + var path = $"/api/flow/$metadata.json/actionListEnum/boundactions"; + return await this + .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Perform an unbound action + /// + /// This action allows you to perform Microsoft Dataverse actions available in the environment that are not associated with any table. + /// Action Name + /// The request body. + /// Cancellation token. + /// The Perform an unbound action response. + public virtual async Task PerformUnboundActionAsync([DynamicValues("GetUnboundActions")] string actionName, PerformUnboundActionInput input, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.PerformUnboundActionAsync"); + try + { + if (actionName is null) + throw new ArgumentNullException(nameof(actionName)); + var path = $"/api/data/v9.2/{Uri.EscapeDataString(actionName.ToString())}"; + return await this + .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Perform a bound action + /// + /// This action allows you to perform Microsoft Dataverse actions associated with a selected table. + /// Table name + /// Action Name + /// Row ID + /// The request body. + /// Cancellation token. + /// The Perform a bound action response. + public virtual async Task PerformBoundActionAsync([DynamicValues("GetEntityListEnum")] string tableName, [DynamicValues("GetBoundActions")] string actionName, string rowId, PerformBoundActionInput input, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.PerformBoundActionAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (actionName is null) + throw new ArgumentNullException(nameof(actionName)); + if (rowId is null) + throw new ArgumentNullException(nameof(rowId)); + var path = $"/api/data/v9.2/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})/{Uri.EscapeDataString(actionName.ToString())}"; + return await this + .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Relate rows + /// + /// This action allows you to link a row in one Microsoft Dataverse table to another if the tables have a one-to-many or many-to-many relationship. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Table name + /// Row ID + /// Relationship + /// The request body. + /// Cancellation token. + public virtual async Task AssociateEntitiesAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, [DynamicValues("GetEntityRelationships")] string relationship, AssociateEntityRequest input, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.AssociateEntitiesAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (rowId is null) + throw new ArgumentNullException(nameof(rowId)); + if (relationship is null) + throw new ArgumentNullException(nameof(relationship)); + var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})/{Uri.EscapeDataString(relationship.ToString())}/$ref"; + await this + .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Unrelate rows + /// + /// This action allows you to remove the link between a row in one Microsoft Dataverse table to another if the tables have a one-to-many or many-to-many relationship. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Table name + /// Row ID + /// Relationship + /// Unrelate with + /// Cancellation token. + public virtual async Task DisassociateEntitiesAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, [DynamicValues("GetEntityRelationships")] string relationship, string unrelateWith, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.DisassociateEntitiesAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (rowId is null) + throw new ArgumentNullException(nameof(rowId)); + if (relationship is null) + throw new ArgumentNullException(nameof(relationship)); + var queryParams = new List(); + if (unrelateWith is null) + throw new ArgumentNullException(nameof(unrelateWith)); + queryParams.Add($"$id={Uri.EscapeDataString(unrelateWith.ToString())}"); + var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})/{Uri.EscapeDataString(relationship.ToString())}/$ref" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); + await this + .CallConnectorAsync(HttpMethod.Delete, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Search rows + /// + /// This action allows you to search a Microsoft Dataverse environment using Relevance Search, and returns the rows that match the search term most closely. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// The request body. + /// Cancellation token. + /// The Search rows response. + public virtual async Task GetRelevantRowsAsync(SearchRequestBody input, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetRelevantRowsAsync"); + try + { + var path = $"/api/search/v1.0/query"; + return await this + .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get catalogs + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Cancellation token. + /// The Get catalogs response. + public virtual async Task GetCatalogsAsync(CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetCatalogsAsync"); + try + { + var path = $"/api/data/v9.2/catalogs"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Perform a changeset request + /// + /// This action allows you to perform a group of Microsoft Dataverse connector operations as a single transaction. If one of the operations fails, all the successful actions are rolled back. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Cancellation token. + public virtual async Task ExecuteChangesetAsync(CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.ExecuteChangesetAsync"); + try + { + var path = $"/api/data/v9.1/$batch"; + await this + .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get action metadata + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Table name + /// Action name + /// Cancellation token. + /// The Get action metadata response. + public virtual async Task GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerAsync(string tableName, string actionName, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (actionName is null) + throw new ArgumentNullException(nameof(actionName)); + var path = $"/api/flow/$metadata.json/whenAnActionIsPerformedEntity/{Uri.EscapeDataString(tableName.ToString())}/whenAnActionIsPerformedAction/{Uri.EscapeDataString(actionName.ToString())}"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get metadata for unbound action input + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Action Name + /// Cancellation token. + /// The Get metadata for unbound action input response. + public virtual async Task GetMetadataForUnboundActionInputAsync(string actionName, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForUnboundActionInputAsync"); + try + { + if (actionName is null) + throw new ArgumentNullException(nameof(actionName)); + var path = $"/api/flow/$metadata.json/flow/api/data/v9.1/{Uri.EscapeDataString(actionName.ToString())}/inputs"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get metadata for unbound action response + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Action Name + /// Cancellation token. + /// The Get metadata for unbound action response response. + public virtual async Task GetMetadataForUnboundActionResponseAsync(string actionName, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForUnboundActionResponseAsync"); + try + { + if (actionName is null) + throw new ArgumentNullException(nameof(actionName)); + var path = $"/api/flow/$metadata.json/flow/api/data/v9.1/{Uri.EscapeDataString(actionName.ToString())}/response"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get metadata for bound action input + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Table name + /// Action Name + /// Cancellation token. + /// The Get metadata for bound action input response. + public virtual async Task GetMetadataForBoundActionInputAsync(string tableName, string actionName, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForBoundActionInputAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (actionName is null) + throw new ArgumentNullException(nameof(actionName)); + var path = $"/api/flow/$metadata.json/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}/{Uri.EscapeDataString(actionName.ToString())}/inputs"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + /// + /// Get metadata for bound action response + /// + /// Discovery method used to populate dynamic parameter values at design time. + /// Table name + /// Action Name + /// Cancellation token. + /// The Get metadata for bound action response response. + public virtual async Task GetMetadataForBoundActionResponseAsync(string tableName, string actionName, CancellationToken cancellationToken = default) + { + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForBoundActionResponseAsync"); + try + { + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (actionName is null) + throw new ArgumentNullException(nameof(actionName)); + var path = $"/api/flow/$metadata.json/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}/{Uri.EscapeDataString(actionName.ToString())}/response"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + } + catch (Exception ex) when (!ex.IsFatal()) + { + activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); + throw; + } + } + + } + + #endregion Client +} + diff --git a/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs b/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs new file mode 100644 index 0000000..dccb003 --- /dev/null +++ b/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs @@ -0,0 +1,214 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System; +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Azure.Connectors.Sdk.Commondataservice; +using Azure.Connectors.Sdk.Commondataservice.Models; +using global::Azure.Core; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Azure.Connectors.Sdk.Tests +{ + /// + /// Tests for the generated class (Microsoft Dataverse). + /// + [TestClass] + public class CommondataserviceClientTests + { + private static CommondataserviceClient CreateMockedClient(Func responseFactory) + { + var (credential, options) = ConnectorTestHelpers.CreateMockedClientSetup(responseFactory); + return new CommondataserviceClient( + connectionRuntimeUrl: new Uri("https://test.azure.com/connection"), + credential: credential, + options: options); + } + + [TestMethod] + public void Constructor_WithValidConnectionRuntimeUrl_ShouldCreateInstance() + { + // Arrange & Act + using var client = new CommondataserviceClient("https://test.azure.com/connection"); + + // Assert + Assert.IsNotNull(client); + } + + [TestMethod] + public void Constructor_WithNullConnectionRuntimeUrl_ShouldThrowArgumentNullException() + { + // Arrange & Act & Assert + Assert.ThrowsExactly(() => new CommondataserviceClient((string)null!)); + } + + [TestMethod] + public void ConnectorName_ReturnsCommondataservice() + { + // Arrange + using var client = new CommondataserviceClient("https://test.azure.com/connection"); + + // Act & Assert + Assert.AreEqual("commondataservice", client.ConnectorName, ignoreCase: false); + } + + [TestMethod] + public void Dispose_ShouldNotThrow() + { + // Arrange + var client = new CommondataserviceClient("https://test.azure.com/connection"); + + // Act & Assert - should not throw + client.Dispose(); + } + + [TestMethod] + public void Dispose_CalledTwice_ShouldNotThrow() + { + // Arrange + var mockCredential = new Mock(); + var client = new CommondataserviceClient( + connectionRuntimeUrl: new Uri("https://test.azure.com/connection"), + credential: mockCredential.Object); + + // Act & Assert - calling Dispose twice should not throw (idempotent) + client.Dispose(); + client.Dispose(); + } + + [TestMethod] + public void Dispose_WithInternallyCreatedHttpClient_ShouldDisposeIt() + { + // Arrange - no httpClient provided, so client creates its own + var mockCredential = new Mock(); + var client = new CommondataserviceClient( + connectionRuntimeUrl: new Uri("https://test.azure.com/connection"), + credential: mockCredential.Object); + + // Act + client.Dispose(); + + // Assert - calling Dispose again should not throw (idempotent) + client.Dispose(); + } + + [TestMethod] + public async Task GetOrganizationsAsync_WithMockedResponse_ReturnsExpectedResult() + { + // Arrange + var expectedResponse = new OrganizationsDynamicValuesList + { + Value = + [ + new OrganizationsDynamicValuesListItem + { + Id = "org-1", + FriendlyName = "Contoso", + Url = "https://contoso.crm.dynamics.com" + }, + new OrganizationsDynamicValuesListItem + { + Id = "org-2", + FriendlyName = "Fabrikam", + Url = "https://fabrikam.crm.dynamics.com" + } + ] + }; + + using var client = CreateMockedClient(() => new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(JsonSerializer.Serialize(expectedResponse)) + }); + + // Act + var result = await client + .GetOrganizationsAsync(cancellationToken: CancellationToken.None) + .ConfigureAwait(continueOnCapturedContext: false); + + // Assert + Assert.IsNotNull(result); + Assert.IsNotNull(result.Value); + Assert.AreEqual(2, result.Value.Count); + Assert.AreEqual("Contoso", result.Value[0].FriendlyName); + Assert.AreEqual("https://fabrikam.crm.dynamics.com", result.Value[1].Url); + } + + [TestMethod] + public async Task GetItemCodelessAsync_WithErrorResponse_ThrowsConnectorException() + { + // Arrange + using var client = CreateMockedClient(() => new HttpResponseMessage + { + StatusCode = HttpStatusCode.BadRequest, + Content = new StringContent("{\"error\": \"Invalid request\"}") + }); + + // Act & Assert + var exception = await Assert + .ThrowsExactlyAsync(async () => + await client + .GetItemCodelessAsync( + tableName: "accounts", + rowId: "00000000-0000-0000-0000-000000000000", + cancellationToken: CancellationToken.None) + .ConfigureAwait(continueOnCapturedContext: false)) + .ConfigureAwait(continueOnCapturedContext: false); + + Assert.AreEqual(400, exception.Status); + Assert.IsTrue(exception.ResponseBody.Contains("Invalid request", StringComparison.Ordinal)); + } + + [TestMethod] + public void OrganizationsDynamicValuesListItem_JsonSerialization_RoundTrips() + { + // Arrange + var organization = new OrganizationsDynamicValuesListItem + { + Id = "org-abc", + FriendlyName = "Contoso", + Url = "https://contoso.crm.dynamics.com" + }; + + // Act + var json = JsonSerializer.Serialize(organization); + var deserialized = JsonSerializer.Deserialize(json); + + // Assert + Assert.IsNotNull(deserialized); + Assert.AreEqual(organization.Id, deserialized.Id); + Assert.AreEqual(organization.FriendlyName, deserialized.FriendlyName); + Assert.AreEqual(organization.Url, deserialized.Url); + } + + [TestMethod] + public void SearchRequestBody_JsonSerialization_RoundTrips() + { + // Arrange + var request = new SearchRequestBody + { + SearchTerm = "Contoso", + SearchType = "simple", + RowCount = 25, + ReturnRowCount = true + }; + + // Act + var json = JsonSerializer.Serialize(request); + var deserialized = JsonSerializer.Deserialize(json); + + // Assert + Assert.IsNotNull(deserialized); + Assert.AreEqual(request.SearchTerm, deserialized.SearchTerm); + Assert.AreEqual(request.SearchType, deserialized.SearchType); + Assert.AreEqual(request.RowCount, deserialized.RowCount); + Assert.AreEqual(request.ReturnRowCount, deserialized.ReturnRowCount); + } + } +} From 696f394d9d5786a8ea5a3f9bf7a0997b1a7e4c3d Mon Sep 17 00:00:00 2001 From: David Burg Date: Thu, 11 Jun 2026 12:28:33 -0700 Subject: [PATCH 2/7] Address Copilot review: regenerate Dataverse client after generator U+FFFD sanitization fix Regenerated CommondataserviceExtensions.cs with the BPM CodefulSdkGenerator fix that normalizes the Unicode replacement character (U+FFFD) in swagger text to an apostrophe. The 'Upsert a row' XML doc remark now reads 'doesn't exist' instead of containing a corrupted replacement character. --- .../Generated/CommondataserviceExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs b/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs index df00e71..37a3885 100644 --- a/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs +++ b/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs @@ -1377,7 +1377,7 @@ await this /// /// Update a row /// - /// This action allows you to modify any selected row in a Microsoft Dataverse table, or adds a new row if it doesn�t exist. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// This action allows you to modify any selected row in a Microsoft Dataverse table, or adds a new row if it doesn't exist. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. /// Table name /// Row ID /// The request body. From 4c010f4595733624ce85bedfa6c3f309bad33f05 Mon Sep 17 00:00:00 2001 From: David Burg Date: Thu, 11 Jun 2026 12:40:41 -0700 Subject: [PATCH 3/7] Address Copilot re-review: regenerate after generator by pass/Odata text fixes Regenerated CommondataserviceExtensions.cs after adding 'by pass'->'bypass' and 'Odata'->'OData' corrections to the generator's SwaggerTextCorrections (BPM PR 16059042). --- .../Generated/CommondataserviceExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs b/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs index 37a3885..36a4053 100644 --- a/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs +++ b/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs @@ -458,7 +458,7 @@ public class SearchRequestBody [JsonPropertyName("top")] public int? RowCount { get; set; } - /// Enter an Odata style filter expression to narrow the search + /// Enter an OData style filter expression to narrow the search [JsonPropertyName("filter")] public string RowFilter { get; set; } @@ -885,7 +885,7 @@ public static class CommondataserviceTriggerParameters public static class OnSubscribeWebhookTrigger { /// - /// The header parameter required to make Microsoft Dataverse by pass the cache and hit DB + /// The header parameter required to make Microsoft Dataverse bypass the cache and hit DB /// Required. /// Default: Strong. /// @@ -920,7 +920,7 @@ public static class OnSubscribeWebhookTrigger public static class OnBusinessEventsTrigger { /// - /// The header parameter required to make CDS by pass the cache and hit DB. + /// The header parameter required to make CDS bypass the cache and hit DB. /// Required. /// Default: Strong. /// From d1025d80c537cd9fbc236ee3644f275eedfdbef9 Mon Sep 17 00:00:00 2001 From: David Burg Date: Thu, 11 Jun 2026 12:49:03 -0700 Subject: [PATCH 4/7] Address Copilot review: remove misleading/redundant dispose test ConnectorClientBase.Dispose only sets a disposed flag (no internal HttpClient field or post-dispose guard), so Dispose_WithInternallyCreatedHttpClient_ShouldDisposeIt did not verify disposal and duplicated Dispose_CalledTwice_ShouldNotThrow. Removed it. --- .../CommondataserviceClientTests.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs b/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs index dccb003..2db64d9 100644 --- a/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs +++ b/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs @@ -82,22 +82,6 @@ public void Dispose_CalledTwice_ShouldNotThrow() client.Dispose(); } - [TestMethod] - public void Dispose_WithInternallyCreatedHttpClient_ShouldDisposeIt() - { - // Arrange - no httpClient provided, so client creates its own - var mockCredential = new Mock(); - var client = new CommondataserviceClient( - connectionRuntimeUrl: new Uri("https://test.azure.com/connection"), - credential: mockCredential.Object); - - // Act - client.Dispose(); - - // Assert - calling Dispose again should not throw (idempotent) - client.Dispose(); - } - [TestMethod] public async Task GetOrganizationsAsync_WithMockedResponse_ReturnsExpectedResult() { From 1f72d9b3fac90fcf017660cc7c64797f6ff27cf3 Mon Sep 17 00:00:00 2001 From: David Burg Date: Thu, 11 Jun 2026 13:47:12 -0700 Subject: [PATCH 5/7] Address Copilot review: regenerate after generator skips multipart changeset op ExecuteChangeset consumes multipart/mixed and the swagger models no request body; ConnectorClientBase cannot compose multipart payloads, so the generated ExecuteChangesetAsync was unusable. Added a generator skip for multipart-consuming operations (BPM PR 16059042) and regenerated; ExecuteChangesetAsync is no longer emitted. --- .../Generated/CommondataserviceExtensions.cs | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs b/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs index 36a4053..8aee9bf 100644 --- a/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs +++ b/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs @@ -1709,29 +1709,6 @@ public virtual async Task GetCatalogsAsync(CancellationToken cance } } - /// - /// Perform a changeset request - /// - /// This action allows you to perform a group of Microsoft Dataverse connector operations as a single transaction. If one of the operations fails, all the successful actions are rolled back. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. - /// Cancellation token. - public virtual async Task ExecuteChangesetAsync(CancellationToken cancellationToken = default) - { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.ExecuteChangesetAsync"); - try - { - var path = $"/api/data/v9.1/$batch"; - await this - .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) - .ConfigureAwait(continueOnCapturedContext: false); - - } - catch (Exception ex) when (!ex.IsFatal()) - { - activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); - throw; - } - } - /// /// Get action metadata /// From 97f30ea0dd4714febedac6f3476310f885703597 Mon Sep 17 00:00:00 2001 From: David Burg Date: Thu, 11 Jun 2026 13:53:26 -0700 Subject: [PATCH 6/7] Address Copilot review: drop changeset claims from CHANGELOG and ROADMAP ExecuteChangesetAsync is no longer generated (multipart op skipped), so remove the now-inaccurate changeset mentions from the Unreleased changelog entry and the ROADMAP Dataverse row. --- CHANGELOG.md | 2 +- ROADMAP.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dae430..4151fba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- **Microsoft Dataverse client (`commondataservice`)** — generated typed `CommondataserviceClient` for the current-environment Microsoft Dataverse connector. Covers row operations (`ListRecordsAsync`, `CreateRecordAsync`, `GetItemCodelessAsync`, `UpdateRecordAsync`, `DeleteRecordAsync`), bound/unbound actions (`PerformBoundActionAsync`, `PerformUnboundActionAsync`), relate/unrelate (`AssociateEntitiesAsync`, `DisassociateEntitiesAsync`), relevance search (`GetRelevantRowsAsync`), file/image column upload & download, and changeset transactions (`ExecuteChangesetAsync`). Deprecated legacy dataset/table operations are excluded by the generator's deprecation filter. +- **Microsoft Dataverse client (`commondataservice`)** — generated typed `CommondataserviceClient` for the current-environment Microsoft Dataverse connector. Covers row operations (`ListRecordsAsync`, `CreateRecordAsync`, `GetItemCodelessAsync`, `UpdateRecordAsync`, `DeleteRecordAsync`), bound/unbound actions (`PerformBoundActionAsync`, `PerformUnboundActionAsync`), relate/unrelate (`AssociateEntitiesAsync`, `DisassociateEntitiesAsync`), relevance search (`GetRelevantRowsAsync`), and file/image column upload & download. Deprecated legacy dataset/table operations are excluded by the generator's deprecation filter. ## [0.12.0-preview.1] - 2026-06-02 diff --git a/ROADMAP.md b/ROADMAP.md index 1d15850..1d832a8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -42,7 +42,7 @@ High-volume connectors for enterprise data and integration scenarios. | Priority | Connector | Usage Rank | Partner Tier | 90-Day Executions | Notes | Status | |----------|-----------|------------|--------------|-------------------|-------|--------| -| 2.1 | **Dataverse** (`commondataservice`) | #12 | Tier 1 | 2.93B | Current-environment Microsoft Dataverse — rows, bound/unbound actions, relate/unrelate, relevance search, changesets | ✅ Complete | +| 2.1 | **Dataverse** (`commondataservice`) | #12 | Tier 1 | 2.93B | Current-environment Microsoft Dataverse — rows, bound/unbound actions, relate/unrelate, relevance search | ✅ Complete | | 2.2 | **Excel Online Business** | #9 | Tier 2 | 957M | Spreadsheet operations | ⬜ Not started | | 2.3 | **Dynamics 365** | — | Tier 2 | 315M | CRM record operations | ⬜ Not started | | 2.4 | **Salesforce** | — | Tier 2 | 127M | CRM operations | ⬜ Not started | From 6c878027076b375e30c80e9afba5672f0184c484 Mon Sep 17 00:00:00 2001 From: David Burg Date: Thu, 11 Jun 2026 16:45:06 -0700 Subject: [PATCH 7/7] refactor(commondataservice): regenerate with legacy CDM v2 paths Regenerated the Dataverse client using only the legacy CDM v2 API paths (e.g., /v2/datasets/{dataset}/tables/{table}/items) that are supported through the Connector Namespace routing layer. Key changes: - Replaced modern Dataverse Web API paths (which return 404 via Connector Namespace) with working legacy v2/CDM paths - Added DoubleEscape helper for x-ms-url-encoding: double support (dataset URLs contain slashes/colons that need double encoding) - Client now exposes 22 operations: CRUD, metadata, attachments, relationships, triggers, and pagination - Updated tests to match new API surface (DataSet/DataSetsList instead of OrganizationsDynamicValuesList) E2E validated against devrelprod.crm.dynamics.com: - GetDataSetsAsync: 1330 environments returned - GetTablesAsync: 884 tables listed - GetItemsAsync: account records successfully read --- .../Generated/CommondataserviceExtensions.cs | 1830 +++++++---------- .../CommondataserviceClientTests.cs | 71 +- 2 files changed, 824 insertions(+), 1077 deletions(-) diff --git a/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs b/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs index 8aee9bf..bb640e2 100644 --- a/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs +++ b/src/Azure.Connectors.Sdk/Generated/CommondataserviceExtensions.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Net.Http; using System.Net.Http.Headers; @@ -18,7 +19,6 @@ using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; -using Azure; using Azure.Connectors.Sdk; using Azure.Connectors.Sdk.Commondataservice.Models; using Azure.Core; @@ -30,234 +30,314 @@ namespace Azure.Connectors.Sdk.Commondataservice.Models #region Types /// - /// Response for Get table metadata + /// Response for GetDataSetsMetadata /// - public class EntityMetadata + public class DataSetsMetadata { - /// Swagger schema - [JsonPropertyName("schema")] - public ObjectEntity Schema { get; set; } + /// tabular + [JsonPropertyName("tabular")] + public TabularDataSetsMetadata Tabular { get; set; } + + /// blob + [JsonPropertyName("blob")] + public BlobDataSetsMetadata Blob { get; set; } } /// - /// Swagger schema + /// tabular /// - public class ObjectEntity + public class TabularDataSetsMetadata { - /// - /// Arbitrary properties. This type has no static schema; any JSON properties will be captured here. - /// - [JsonExtensionData] - public Dictionary AdditionalProperties { get; set; } = new(); + /// Dataset source + [JsonPropertyName("source")] + public string Source { get; set; } + + /// Dataset display name + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } + + /// Dataset url encoding + [JsonPropertyName("urlEncoding")] + public string UrlEncoding { get; set; } + + /// Table display name + [JsonPropertyName("tableDisplayName")] + public string TableDisplayName { get; set; } + + /// Table plural display name + [JsonPropertyName("tablePluralName")] + public string TablePluralName { get; set; } } /// - /// Response for GetEntityRelationships + /// blob /// - public class EntityRelationshipsDynamicValuesList + public class BlobDataSetsMetadata { - /// List of table relationships as dynamic values. - [JsonPropertyName("value")] - public List Value { get; set; } + /// Blob dataset source + [JsonPropertyName("source")] + public string Source { get; set; } + + /// Blob dataset display name + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } + + /// Blob dataset url encoding + [JsonPropertyName("urlEncoding")] + public string UrlEncoding { get; set; } } /// - /// Response for GetOrganizations + /// Associates one row to another on the provided relationship /// - public class OrganizationsDynamicValuesList + [DynamicSchema("GetMetadataForPostItem_V2")] + public class AssociateRecordsPatchItemInput { - /// List of organizations as dynamic values. - [JsonPropertyName("value")] - public List Value { get; set; } + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); } /// - /// Item in List of organizations as dynamic values. + /// Response for Associates one row to another on the provided relationship /// - public class OrganizationsDynamicValuesListItem + [DynamicSchema("GetTable")] + public class Item { - /// The organization id. - [JsonPropertyName("Id")] - public string Id { get; set; } - - /// The name of the organization. - [JsonPropertyName("FriendlyName")] - public string FriendlyName { get; set; } + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); - /// The URL of the organization. - [JsonPropertyName("Url")] - public string Url { get; set; } + /// dynamicProperties + [JsonPropertyName("dynamicProperties")] + public JsonElement? DynamicProperties { get; set; } } /// - /// Response for GetEntityListEnum + /// Response for Retrieves all collection valued relationship items as an expand would /// - public class EntitiesDynamicValuesList + public class ItemsList { - /// List of tables as dynamic values. + /// List of Items [JsonPropertyName("value")] - public List Value { get; set; } + public List Value { get; set; } } /// - /// Item in List of tables as dynamic values. + /// Response for GetDataSets_V2 /// - public class EntitiesDynamicValuesListItem + public class DataSetsList { - /// The metadata id. - [JsonPropertyName("metadataId")] - public string MetadataId { get; set; } + /// List of datasets + [JsonPropertyName("value")] + public List Value { get; set; } + } - /// The name of the table. - [JsonPropertyName("entitySetName")] - public string EntitySetName { get; set; } + /// + /// Item in List of datasets + /// + public class DataSet + { + /// Dataset name + [JsonPropertyName("Name")] + public string Name { get; set; } - /// The logical name of the table. - [JsonPropertyName("logicalName")] - public string LogicalName { get; set; } + /// Dataset display name + [JsonPropertyName("DisplayName")] + public string DisplayName { get; set; } - /// The display name of the table. - [JsonPropertyName("displayCollectionName")] - public DisplayCollectionName DisplayCollectionName { get; set; } + /// Pass-through Native Queries + [JsonPropertyName("query")] + [JsonInclude] + public List Query { get; init; } } /// - /// The display name of the table. + /// Item in Pass-through Native Queries /// - public class DisplayCollectionName + public class PassThroughNativeQuery { - /// The user localized display name label object. - [JsonPropertyName("userLocalizedLabel")] - public UserLocalizedLabel UserLocalizedLabel { get; set; } + /// Query language + [JsonPropertyName("Language")] + public string Language { get; set; } } /// - /// The user localized display name label object. + /// Response for Get row (legacy) /// - public class UserLocalizedLabel + [DynamicSchema("GetTable_V2")] + public class GetItemResponse { - /// The display name label of the table. - [JsonPropertyName("label")] - public string Label { get; set; } + /// + /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. + /// Populate this dictionary with the properties returned by the schema API. + /// + [JsonExtensionData] + public Dictionary AdditionalProperties { get; set; } = new(); } /// - /// Response for List rows + /// Response for Get table metadata - Patch /// - public class EntityItemList : IPageable + public class TableMetadata { - /// List of Items - [JsonPropertyName("value")] - public List Value { get; set; } + /// Table name + [JsonPropertyName("name")] + public string Name { get; set; } + + /// Table title + [JsonPropertyName("title")] + public string Title { get; set; } - /// The url to fetch next page data. - [JsonPropertyName("@odata.nextLink")] - public string NextLink { get; set; } + /// Table permission + [JsonPropertyName("x-ms-permission")] + public string XMsPermission { get; set; } + + /// x-ms-capabilities + [JsonPropertyName("x-ms-capabilities")] + public TableCapabilitiesMetadata XMsCapabilities { get; set; } + + /// schema + [JsonPropertyName("schema")] + public ObjectEntity Schema { get; set; } + + /// referencedEntities + [JsonPropertyName("referencedEntities")] + public ObjectEntity ReferencedEntities { get; set; } + + /// Url link + [JsonPropertyName("webUrl")] + public string WebUrl { get; set; } } /// - /// Item in List of Items + /// x-ms-capabilities /// - [DynamicSchema("GetMetadataForGetEntity")] - public class EntityItem + public class TableCapabilitiesMetadata { - /// - /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. - /// Populate this dictionary with the properties returned by the schema API. - /// - [JsonExtensionData] - public Dictionary AdditionalProperties { get; set; } = new(); + /// sortRestrictions + [JsonPropertyName("sortRestrictions")] + public TableSortRestrictionsMetadata SortRestrictions { get; set; } - /// dynamicProperties - [JsonPropertyName("dynamicProperties")] - public JsonElement? DynamicProperties { get; set; } + /// filterRestrictions + [JsonPropertyName("filterRestrictions")] + public TableFilterRestrictionsMetadata FilterRestrictions { get; set; } + + /// selectRestrictions + [JsonPropertyName("selectRestrictions")] + public TableSelectRestrictionsMetadata SelectRestrictions { get; set; } + + /// Server paging restrictions + [JsonPropertyName("isOnlyServerPagable")] + public bool? IsOnlyServerPagable { get; set; } + + /// List of supported filter capabilities + [JsonPropertyName("filterFunctionSupport")] + public List FilterFunctionSupport { get; set; } + + /// List of supported server-driven paging capabilities + [JsonPropertyName("serverPagingOptions")] + public List ServerPagingOptions { get; set; } } /// - /// Add a new row + /// sortRestrictions /// - [DynamicSchema("GetMetadataForPostEntity")] - public class CreateRecordInput + public class TableSortRestrictionsMetadata { - /// - /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. - /// Populate this dictionary with the properties returned by the schema API. - /// - [JsonExtensionData] - public Dictionary AdditionalProperties { get; set; } = new(); + /// Indicates whether this table has sortable columns + [JsonPropertyName("sortable")] + public bool? Sortable { get; set; } + + /// List of unsortable properties + [JsonPropertyName("unsortableProperties")] + public List UnsortableProperties { get; set; } + + /// List of properties which support ascending order only + [JsonPropertyName("ascendingOnlyProperties")] + public List AscendingOnlyProperties { get; set; } } /// - /// Response for Add a new row + /// filterRestrictions /// - [DynamicSchema("GetMetadataForGetEntity")] - public class CreateRecordResponse + public class TableFilterRestrictionsMetadata { - /// - /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. - /// Populate this dictionary with the properties returned by the schema API. - /// - [JsonExtensionData] - public Dictionary AdditionalProperties { get; set; } = new(); + /// Indicates whether this table has filterable columns + [JsonPropertyName("filterable")] + public bool? Filterable { get; set; } + + /// List of non filterable properties + [JsonPropertyName("nonFilterableProperties")] + public List NonFilterableProperties { get; set; } + + /// List of required properties + [JsonPropertyName("requiredProperties")] + public List RequiredProperties { get; set; } } /// - /// Response for Get a row by ID + /// selectRestrictions /// - [DynamicSchema("GetMetadataForGetEntity")] - public class GetItemCodelessResponse + public class TableSelectRestrictionsMetadata { - /// - /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. - /// Populate this dictionary with the properties returned by the schema API. - /// - [JsonExtensionData] - public Dictionary AdditionalProperties { get; set; } = new(); + /// Indicates whether this table has selectable columns + [JsonPropertyName("selectable")] + public bool? Selectable { get; set; } } /// - /// Update a row + /// schema /// - [DynamicSchema("GetMetadataForPatchEntity")] - public class UpdateRecordInput + public class ObjectEntity { /// - /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. - /// Populate this dictionary with the properties returned by the schema API. + /// Arbitrary properties. This type has no static schema; any JSON properties will be captured here. /// [JsonExtensionData] public Dictionary AdditionalProperties { get; set; } = new(); } /// - /// Response for Update a row + /// Response for GetTables_V2 /// - [DynamicSchema("GetMetadataForGetEntity")] - public class UpdateRecordResponse + public class TablesList { - /// - /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. - /// Populate this dictionary with the properties returned by the schema API. - /// - [JsonExtensionData] - public Dictionary AdditionalProperties { get; set; } = new(); + /// List of Tables + [JsonPropertyName("value")] + public List Value { get; set; } } /// - /// Response for GetUnboundActions + /// Item in List of Tables /// - public class ActionsDynamicValuesList + public class Table { - /// List of actions as dynamic values - [JsonPropertyName("value")] - public List Value { get; set; } + /// The name of the table. The name is used at runtime. + [JsonPropertyName("Name")] + public string Name { get; set; } + + /// The display name of the table. + [JsonPropertyName("DisplayName")] + public string DisplayName { get; set; } + + /// Additional table properties provided by the connector to the clients. + [JsonPropertyName("DynamicProperties")] + [JsonInclude] + public JsonElement? DynamicProperties { get; init; } } /// - /// Perform an unbound action + /// Update a row (legacy) /// - [DynamicSchema("GetMetadataForUnboundActionInput")] - public class PerformUnboundActionInput + [DynamicSchema("GetMetadataForPatchItem_V2")] + public class PatchItemInput { /// /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. @@ -268,10 +348,10 @@ public class PerformUnboundActionInput } /// - /// Response for Perform an unbound action + /// Response for Update a row (legacy) /// - [DynamicSchema("GetMetadataForUnboundActionResponse")] - public class PerformUnboundActionResponse + [DynamicSchema("GetMetadataForPatchItem_V2")] + public class PatchItemResponse { /// /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. @@ -282,10 +362,10 @@ public class PerformUnboundActionResponse } /// - /// Perform a bound action + /// Add a new row (legacy) /// - [DynamicSchema("GetMetadataForBoundActionInput")] - public class PerformBoundActionInput + [DynamicSchema("GetMetadataForPostItem_V2")] + public class PostItemInput { /// /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. @@ -296,10 +376,10 @@ public class PerformBoundActionInput } /// - /// Response for Perform a bound action + /// Response for Add a new row (legacy) /// - [DynamicSchema("GetMetadataForBoundActionResponse")] - public class PerformBoundActionResponse + [DynamicSchema("GetMetadataForPostItem_V2")] + public class PostItemResponse { /// /// Dynamic properties determined at runtime by the connector's schema discovery endpoint. @@ -309,210 +389,6 @@ public class PerformBoundActionResponse public Dictionary AdditionalProperties { get; set; } = new(); } - /// - /// Response for Search rows - /// - public class SearchOutput - { - /// List of rows - [JsonPropertyName("value")] - public List ListOfRows { get; set; } - - /// Total count of results (-1 if returntotalrecordcount is set to false) - [JsonPropertyName("totalrecordcount")] - public long? TotalRowCount { get; set; } - - /// Facet results - [JsonPropertyName("facets")] - public JsonElement? FacetResults { get; set; } - } - - /// - /// Response for Get action metadata - /// - public class GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerResponse - { - /// Swagger schema - [JsonPropertyName("schema")] - public ObjectEntity Schema { get; set; } - } - - /// - /// Response for Get metadata for unbound action input - /// - public class GetMetadataForUnboundActionInputResponse - { - /// Swagger schema - [JsonPropertyName("schema")] - public ObjectEntity Schema { get; set; } - } - - /// - /// Response for Get metadata for unbound action response - /// - public class GetMetadataForUnboundActionResponseResponse - { - /// Swagger schema - [JsonPropertyName("schema")] - public ObjectEntity Schema { get; set; } - } - - /// - /// Response for Get metadata for bound action input - /// - public class GetMetadataForBoundActionInputResponse - { - /// Swagger schema - [JsonPropertyName("schema")] - public ObjectEntity Schema { get; set; } - } - - /// - /// Response for Get metadata for bound action response - /// - public class GetMetadataForBoundActionResponseResponse - { - /// Swagger schema - [JsonPropertyName("schema")] - public ObjectEntity Schema { get; set; } - } - - /// - /// CallbackRegistration - /// - public class CallbackRegistration - { - /// The callback registration table version - [JsonPropertyName("version")] - public int? TableVersion { get; set; } - - /// Callback url to the flow engine. Expected as part of the request and provided by Flow. - [JsonPropertyName("url")] - public string NotificationUrl { get; set; } - - /// Callback registration name. Value will be replaced with workflow id - [JsonPropertyName("name")] - public string Name { get; set; } - - /// Choose a table - [JsonPropertyName("entityname")] - public string TableName { get; set; } - - /// Choose when the flow triggers - [JsonPropertyName("message")] - public int? ChangeType { get; set; } - - /// Choose an option or add your own - [JsonPropertyName("sdkmessagename")] - public string ActionName { get; set; } - - /// Choose a scope to limit which rows can trigger the flow - [JsonPropertyName("scope")] - public int? Scope { get; set; } - - /// Enter a comma-separated list of column unique names. The flow triggers if any of them are modified - [JsonPropertyName("filteringattributes")] - public string SelectColumns { get; set; } - - /// Enter an OData style filter expression to determine which rows can trigger the flow - [JsonPropertyName("filterexpression")] - public string FilterRows { get; set; } - - /// Enter a time to delay the trigger evaluation, eg. 2020-01-01T10:10:00Z - [JsonPropertyName("postponeuntil")] - public string DelayUntil { get; set; } - - /// Choose the running user for steps where invoker connections are used - [JsonPropertyName("runas")] - public int? RunAs { get; set; } - } - - /// - /// AssociateEntityRequest - /// - public class AssociateEntityRequest - { - /// Enter the row URL using OData ID from a previous step or typing the full URL (eg. https://org0.crm.dynamics.com/api/data/v9.0/faxes(3ce6c728-3c8a-4b55-a4ee-a251b253c3ee) - [JsonPropertyName("@odata.id")] - public string RelateWith { get; set; } - } - - /// - /// SearchRequestBody - /// - public class SearchRequestBody - { - /// Enter a search term, eg. Contoso. Searches modifiers like boolean operators, wildcards, fuzzy search, proximity search etc. require the search type full - [JsonPropertyName("search")] - public string SearchTerm { get; set; } - - /// Enter whether simple or full search syntax should be used (default is simple) - [JsonPropertyName("searchtype")] - public string SearchType { get; set; } - - /// Enter whether any or all of the search terms must be matched (default is any) - [JsonPropertyName("searchmode")] - public string SearchMode { get; set; } - - /// Enter the number of search results to be listed (default = 50) - [JsonPropertyName("top")] - public int? RowCount { get; set; } - - /// Enter an OData style filter expression to narrow the search - [JsonPropertyName("filter")] - public string RowFilter { get; set; } - - /// Enter a comma-separated list of tables to be searched (default is all tables) - [JsonPropertyName("entities")] - public List TableFilter { get; set; } - - /// Enter a comma-separated list of column unique names followed by asc or desc - [JsonPropertyName("orderby")] - public List SortBy { get; set; } - - /// Enter a comma-separated list of facet queries to narrow the search - [JsonPropertyName("facets")] - public List FacetQuery { get; set; } - - /// Enter the number of search results to be skipped - [JsonPropertyName("skip")] - public int? SkipRows { get; set; } - - /// Choose an option - [JsonPropertyName("returntotalrecordcount")] - public bool? ReturnRowCount { get; set; } - } - - /// - /// WhenAnActionIsPerformedSubscriptionRequest - /// - public class WhenAnActionIsPerformedSubscriptionRequest - { - /// The callback registration table version. - [JsonPropertyName("version")] - public int? TableVersion { get; set; } - - /// Callback url to the flow engine. Expected as part of the request and provided by Flow. - [JsonPropertyName("url")] - public string NotificationUrl { get; set; } - - /// Callback registration name. Value will be replaced with workflow id. - [JsonPropertyName("name")] - public string Name { get; set; } - - /// Choose a scope to limit which rows can trigger the flow. - [JsonPropertyName("scope")] - public int? Scope { get; set; } - - /// Choose a table to filter actions. - [JsonPropertyName("entityname")] - public string TableName { get; set; } - - /// Choose an action. - [JsonPropertyName("sdkmessagename")] - public string ActionName { get; set; } - } - #endregion Types #region Model Factory @@ -525,323 +401,289 @@ public class WhenAnActionIsPerformedSubscriptionRequest public static class CommondataserviceModelFactory { /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static EntityMetadata EntityMetadata( - ObjectEntity schema = default) + public static DataSetsMetadata DataSetsMetadata( + TabularDataSetsMetadata tabular = default, + BlobDataSetsMetadata blob = default) { - return new EntityMetadata + return new DataSetsMetadata { - Schema = schema, + Tabular = tabular, + Blob = blob, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static EntityRelationshipsDynamicValuesList EntityRelationshipsDynamicValuesList( - List value = default) + public static TabularDataSetsMetadata TabularDataSetsMetadata( + string source = default, + string displayName = default, + string urlEncoding = default, + string tableDisplayName = default, + string tablePluralName = default) { - return new EntityRelationshipsDynamicValuesList + return new TabularDataSetsMetadata { - Value = value, + Source = source, + DisplayName = displayName, + UrlEncoding = urlEncoding, + TableDisplayName = tableDisplayName, + TablePluralName = tablePluralName, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static OrganizationsDynamicValuesList OrganizationsDynamicValuesList( - List value = default) + public static BlobDataSetsMetadata BlobDataSetsMetadata( + string source = default, + string displayName = default, + string urlEncoding = default) { - return new OrganizationsDynamicValuesList + return new BlobDataSetsMetadata { - Value = value, + Source = source, + DisplayName = displayName, + UrlEncoding = urlEncoding, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static OrganizationsDynamicValuesListItem OrganizationsDynamicValuesListItem( - string id = default, - string friendlyName = default, - string url = default) + public static Item Item( + JsonElement? dynamicProperties = default) { - return new OrganizationsDynamicValuesListItem + return new Item { - Id = id, - FriendlyName = friendlyName, - Url = url, + DynamicProperties = dynamicProperties, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static EntitiesDynamicValuesList EntitiesDynamicValuesList( - List value = default) + public static ItemsList ItemsList( + List value = default) { - return new EntitiesDynamicValuesList + return new ItemsList { Value = value, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static EntitiesDynamicValuesListItem EntitiesDynamicValuesListItem( - string metadataId = default, - string entitySetName = default, - string logicalName = default, - DisplayCollectionName displayCollectionName = default) + public static DataSetsList DataSetsList( + List value = default) { - return new EntitiesDynamicValuesListItem - { - MetadataId = metadataId, - EntitySetName = entitySetName, - LogicalName = logicalName, - DisplayCollectionName = displayCollectionName, - }; - } - - /// - /// Creates a new instance of . - /// - public static DisplayCollectionName DisplayCollectionName( - UserLocalizedLabel userLocalizedLabel = default) - { - return new DisplayCollectionName - { - UserLocalizedLabel = userLocalizedLabel, - }; - } - - /// - /// Creates a new instance of . - /// - public static UserLocalizedLabel UserLocalizedLabel( - string label = default) - { - return new UserLocalizedLabel - { - Label = label, - }; - } - - /// - /// Creates a new instance of . - /// - public static EntityItemList EntityItemList( - List value = default, - string nextLink = default) - { - return new EntityItemList + return new DataSetsList { Value = value, - NextLink = nextLink, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static EntityItem EntityItem( - JsonElement? dynamicProperties = default) + public static DataSet DataSet( + string name = default, + string displayName = default, + List query = default) { - return new EntityItem + return new DataSet { - DynamicProperties = dynamicProperties, + Name = name, + DisplayName = displayName, + Query = query, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static ActionsDynamicValuesList ActionsDynamicValuesList( - List value = default) + public static PassThroughNativeQuery PassThroughNativeQuery( + string language = default) { - return new ActionsDynamicValuesList + return new PassThroughNativeQuery { - Value = value, + Language = language, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static SearchOutput SearchOutput( - List listOfRows = default, - long? totalRowCount = default, - JsonElement? facetResults = default) + public static TableMetadata TableMetadata( + string name = default, + string title = default, + string xMsPermission = default, + TableCapabilitiesMetadata xMsCapabilities = default, + ObjectEntity schema = default, + ObjectEntity referencedEntities = default, + string webUrl = default) { - return new SearchOutput + return new TableMetadata { - ListOfRows = listOfRows, - TotalRowCount = totalRowCount, - FacetResults = facetResults, + Name = name, + Title = title, + XMsPermission = xMsPermission, + XMsCapabilities = xMsCapabilities, + Schema = schema, + ReferencedEntities = referencedEntities, + WebUrl = webUrl, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerResponse GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerResponse( - ObjectEntity schema = default) + public static TableCapabilitiesMetadata TableCapabilitiesMetadata( + TableSortRestrictionsMetadata sortRestrictions = default, + TableFilterRestrictionsMetadata filterRestrictions = default, + TableSelectRestrictionsMetadata selectRestrictions = default, + bool? isOnlyServerPagable = default, + List filterFunctionSupport = default, + List serverPagingOptions = default) { - return new GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerResponse - { - Schema = schema, + return new TableCapabilitiesMetadata + { + SortRestrictions = sortRestrictions, + FilterRestrictions = filterRestrictions, + SelectRestrictions = selectRestrictions, + IsOnlyServerPagable = isOnlyServerPagable, + FilterFunctionSupport = filterFunctionSupport, + ServerPagingOptions = serverPagingOptions, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static GetMetadataForUnboundActionInputResponse GetMetadataForUnboundActionInputResponse( - ObjectEntity schema = default) + public static TableSortRestrictionsMetadata TableSortRestrictionsMetadata( + bool? sortable = default, + List unsortableProperties = default, + List ascendingOnlyProperties = default) { - return new GetMetadataForUnboundActionInputResponse + return new TableSortRestrictionsMetadata { - Schema = schema, + Sortable = sortable, + UnsortableProperties = unsortableProperties, + AscendingOnlyProperties = ascendingOnlyProperties, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static GetMetadataForUnboundActionResponseResponse GetMetadataForUnboundActionResponseResponse( - ObjectEntity schema = default) + public static TableFilterRestrictionsMetadata TableFilterRestrictionsMetadata( + bool? filterable = default, + List nonFilterableProperties = default, + List requiredProperties = default) { - return new GetMetadataForUnboundActionResponseResponse + return new TableFilterRestrictionsMetadata { - Schema = schema, + Filterable = filterable, + NonFilterableProperties = nonFilterableProperties, + RequiredProperties = requiredProperties, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static GetMetadataForBoundActionInputResponse GetMetadataForBoundActionInputResponse( - ObjectEntity schema = default) + public static TableSelectRestrictionsMetadata TableSelectRestrictionsMetadata( + bool? selectable = default) { - return new GetMetadataForBoundActionInputResponse + return new TableSelectRestrictionsMetadata { - Schema = schema, + Selectable = selectable, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static GetMetadataForBoundActionResponseResponse GetMetadataForBoundActionResponseResponse( - ObjectEntity schema = default) + public static TablesList TablesList( + List
value = default) { - return new GetMetadataForBoundActionResponseResponse + return new TablesList { - Schema = schema, + Value = value, }; } /// - /// Creates a new instance of . + /// Creates a new instance of . /// - public static CallbackRegistration CallbackRegistration( - int? tableVersion = default, - string notificationUrl = default, + public static Table Table( string name = default, - string tableName = default, - int? changeType = default, - string actionName = default, - int? scope = default, - string selectColumns = default, - string filterRows = default, - string delayUntil = default, - int? runAs = default) + string displayName = default, + JsonElement? dynamicProperties = default) { - return new CallbackRegistration + return new Table { - TableVersion = tableVersion, - NotificationUrl = notificationUrl, Name = name, - TableName = tableName, - ChangeType = changeType, - ActionName = actionName, - Scope = scope, - SelectColumns = selectColumns, - FilterRows = filterRows, - DelayUntil = delayUntil, - RunAs = runAs, + DisplayName = displayName, + DynamicProperties = dynamicProperties, }; } + } - /// - /// Creates a new instance of . - /// - public static AssociateEntityRequest AssociateEntityRequest( - string relateWith = default) - { - return new AssociateEntityRequest - { - RelateWith = relateWith, - }; - } + #endregion Model Factory - /// - /// Creates a new instance of . - /// - public static SearchRequestBody SearchRequestBody( - string searchTerm = default, - string searchType = default, - string searchMode = default, - int? rowCount = default, - string rowFilter = default, - List tableFilter = default, - List sortBy = default, - List facetQuery = default, - int? skipRows = default, - bool? returnRowCount = default) - { - return new SearchRequestBody - { - SearchTerm = searchTerm, - SearchType = searchType, - SearchMode = searchMode, - RowCount = rowCount, - RowFilter = rowFilter, - TableFilter = tableFilter, - SortBy = sortBy, - FacetQuery = facetQuery, - SkipRows = skipRows, - ReturnRowCount = returnRowCount, - }; - } + #region Trigger Payloads + + /// + /// Typed trigger payload for the OnDeletedItems trigger (Commondataservice "When a row is deleted (Admin Only) (Deprecated)", operationId: GetOnDeletedItems_V2). + /// Deserialize Connector Namespace callbacks directly: JsonSerializer.Deserialize<CommondataserviceOnDeletedItemsTriggerPayload>(body). + /// + public class CommondataserviceOnDeletedItemsTriggerPayload : TriggerCallbackPayload + { + } + + /// + /// Typed trigger payload for the OnNewItems trigger (Commondataservice "When a row is added (Admin Only) (Deprecated)", operationId: GetOnNewItems_V2). + /// Deserialize Connector Namespace callbacks directly: JsonSerializer.Deserialize<CommondataserviceOnNewItemsTriggerPayload>(body). + /// + public class CommondataserviceOnNewItemsTriggerPayload : TriggerCallbackPayload + { + } + + /// + /// Typed trigger payload for the OnUpdatedItems trigger (Commondataservice "When a row is modified (Admin Only) (Deprecated)", operationId: GetOnUpdatedItems_V2). + /// Deserialize Connector Namespace callbacks directly: JsonSerializer.Deserialize<CommondataserviceOnUpdatedItemsTriggerPayload>(body). + /// + public class CommondataserviceOnUpdatedItemsTriggerPayload : TriggerCallbackPayload + { + } + /// + /// Static registry of trigger operations for the Commondataservice connector that have typed payloads. + /// Maps operation names to their typed subtypes. + /// Triggers that return binary content (e.g., file downloads) are not included here + /// because they have no JSON-deserializable payload type. See + /// for the complete list of trigger operation name constants. + /// + public static class CommondataserviceTriggers + { /// - /// Creates a new instance of . + /// Trigger operations with typed payloads for the Commondataservice connector. + /// This is a subset of all triggers — see for the full list. /// - public static WhenAnActionIsPerformedSubscriptionRequest WhenAnActionIsPerformedSubscriptionRequest( - int? tableVersion = default, - string notificationUrl = default, - string name = default, - int? scope = default, - string tableName = default, - string actionName = default) - { - return new WhenAnActionIsPerformedSubscriptionRequest + public static IReadOnlyDictionary Operations { get; } = new ReadOnlyDictionary( + new Dictionary(StringComparer.OrdinalIgnoreCase) { - TableVersion = tableVersion, - NotificationUrl = notificationUrl, - Name = name, - Scope = scope, - TableName = tableName, - ActionName = actionName, - }; - } + ["GetOnDeletedItems_V2"] = typeof(CommondataserviceOnDeletedItemsTriggerPayload), + ["GetOnNewItems_V2"] = typeof(CommondataserviceOnNewItemsTriggerPayload), + ["GetOnUpdatedItems_V2"] = typeof(CommondataserviceOnUpdatedItemsTriggerPayload), + }); } - #endregion Model Factory + #endregion Trigger Payloads } @@ -858,14 +700,22 @@ namespace Azure.Connectors.Sdk.Commondataservice public static class CommondataserviceTriggerOperations { /// - /// When a row is added, modified or deleted. + /// When a row is deleted (Admin Only) (Deprecated). + /// Payload type: . + /// + public const string OnDeletedItems = "GetOnDeletedItems_V2"; + + /// + /// When a row is added (Admin Only) (Deprecated). + /// Payload type: . /// - public const string OnSubscribeWebhookTrigger = "SubscribeWebhookTrigger"; + public const string OnNewItems = "GetOnNewItems_V2"; /// - /// When an action is performed. + /// When a row is modified (Admin Only) (Deprecated). + /// Payload type: . /// - public const string OnBusinessEventsTrigger = "BusinessEventsTrigger"; + public const string OnUpdatedItems = "GetOnUpdatedItems_V2"; } @@ -880,72 +730,83 @@ public static class CommondataserviceTriggerOperations public static class CommondataserviceTriggerParameters { /// - /// Input parameters for the OnSubscribeWebhookTrigger trigger operation (operationId: SubscribeWebhookTrigger). + /// Input parameters for the OnDeletedItems trigger operation (operationId: GetOnDeletedItems_V2). /// - public static class OnSubscribeWebhookTrigger + public static class OnDeletedItems { /// - /// The header parameter required to make Microsoft Dataverse bypass the cache and hit DB - /// Required. - /// Default: Strong. + /// An ODATA filter query to restrict the entries returned (e.g. stringColumn eq 'string' OR numberColumn lt 123). /// - public const string Consistency = "Consistency"; + public const string Filter = "$filter"; /// - /// Using this header parameter to save catalog in flow definition and Microsoft Dataverse will ignore as they don't use this info - /// Required. - /// Default: all. + /// An ODATA orderBy query for specifying the order of entries. /// - public const string Catalog = "catalog"; + public const string Orderby = "$orderby"; /// - /// Using this header parameter to save category in flow definition and Microsoft Dataverse will ignore as they don't use this info - /// Required. - /// Default: all. + /// Total number of entries to retrieve (default = all). /// - public const string Category = "category"; + public const string Top = "$top"; /// - /// Select an Environment - /// Required. - /// Dynamic values from: GetOrganizations. + /// The number of entries to skip (default = 0). /// - public const string Organization = "organization"; + public const string Skip = "$skip"; } /// - /// Input parameters for the OnBusinessEventsTrigger trigger operation (operationId: BusinessEventsTrigger). + /// Input parameters for the OnNewItems trigger operation (operationId: GetOnNewItems_V2). /// - public static class OnBusinessEventsTrigger + public static class OnNewItems { /// - /// The header parameter required to make CDS bypass the cache and hit DB. - /// Required. - /// Default: Strong. + /// An ODATA filter query to restrict the entries returned (e.g. stringColumn eq 'string' OR numberColumn lt 123). /// - public const string Consistency = "Consistency"; + public const string Filter = "$filter"; /// - /// Select an Environment - /// Required. - /// Dynamic values from: GetOrganizations. + /// An ODATA orderBy query for specifying the order of entries. /// - public const string Organization = "organization"; + public const string Orderby = "$orderby"; /// - /// Choose an option to filter tables and actions. - /// Required. - /// Dynamic values from: GetCatalogs. + /// Total number of entries to retrieve (default = all). /// - public const string Catalog = "catalog"; + public const string Top = "$top"; /// - /// Choose an option to filter tables and actions. - /// Required. - /// Dynamic values from: GetEntityListEnum. + /// The number of entries to skip (default = 0). /// - public const string Category = "category"; + public const string Skip = "$skip"; + + } + + /// + /// Input parameters for the OnUpdatedItems trigger operation (operationId: GetOnUpdatedItems_V2). + /// + public static class OnUpdatedItems + { + /// + /// An ODATA filter query to restrict the entries returned (e.g. stringColumn eq 'string' OR numberColumn lt 123). + /// + public const string Filter = "$filter"; + + /// + /// An ODATA orderBy query for specifying the order of entries. + /// + public const string Orderby = "$orderby"; + + /// + /// Total number of entries to retrieve (default = all). + /// + public const string Top = "$top"; + + /// + /// The number of entries to skip (default = 0). + /// + public const string Skip = "$skip"; } @@ -1019,30 +880,24 @@ protected CommondataserviceClient() : base() { } [EditorBrowsable(EditorBrowsableState.Never)] public override string ToString() => base.ToString(); + // NOTE(daviburg): Double-encodes a value for URL path segments. + // The commondataservice connector uses x-ms-url-encoding: double on path + // parameters because dataset values are full URLs with slashes and colons. + private static string DoubleEscape(string value) => Uri.EscapeDataString(Uri.EscapeDataString(value)); + /// - /// Get table metadata + /// GetDataSetsMetadata /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Table name - /// Select Query - /// Expand Query /// Cancellation token. - /// The Get table metadata response. - public virtual async Task GetMetadataForGetEntityAsync(string tableName, string selectQuery = default, string expandQuery = default, CancellationToken cancellationToken = default) + /// The GetDataSetsMetadata response. + public virtual async Task GetDataSetsMetadataAsync(CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForGetEntityAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetDataSetsMetadataAsync"); try { - if (tableName is null) - throw new ArgumentNullException(nameof(tableName)); - var queryParams = new List(); - if (selectQuery != default) - queryParams.Add($"selectedEntityAttributes={Uri.EscapeDataString(selectQuery.ToString())}"); - if (expandQuery != default) - queryParams.Add($"expandEntityAttributes={Uri.EscapeDataString(expandQuery.ToString())}"); - var path = $"/api/flow/$metadata.json/entities/{Uri.EscapeDataString(tableName.ToString())}" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); + var path = $"/$metadata.json/datasets"; return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1054,22 +909,21 @@ public virtual async Task GetMetadataForGetEntityAsync(string ta } /// - /// Get table metadata + /// Follows nextLink to retrieve next page of data /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Table name + /// This operation gets the next page of data. + /// nextLink value /// Cancellation token. - /// The Get table metadata response. - public virtual async Task GetMetadataForGetEntityCUDTriggerAsync(string tableName, CancellationToken cancellationToken = default) + public virtual async Task GetNextPageAsync(string nextLinkValue, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForGetEntityCUDTriggerAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetNextPageAsync"); try { - if (tableName is null) - throw new ArgumentNullException(nameof(tableName)); - var path = $"/api/flow/$metadata.json/entities/{Uri.EscapeDataString(tableName.ToString())}/cudtrigger"; - return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + if (nextLinkValue is null) + throw new ArgumentNullException(nameof(nextLinkValue)); + var path = $"/nextLink/{Uri.EscapeDataString(nextLinkValue.ToString())}"; + await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1081,22 +935,32 @@ public virtual async Task GetMetadataForGetEntityCUDTriggerAsync } /// - /// Get table metadata - Post + /// Associates one row to another on the provided relationship /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Table name + /// Associates one row to another on the provided relationship + /// Dataset + /// Table + /// Id + /// Relationship + /// The request body. /// Cancellation token. - /// The Get table metadata - Post response. - public virtual async Task GetMetadataForPostEntityAsync(string tableName, CancellationToken cancellationToken = default) + /// The Associates one row to another on the provided relationship response. + public virtual async Task AssociateRecordsPatchItemAsync([DynamicValues("GetDataSets")] string dataset, [DynamicValues("GetTables")] string table, string id, string relationship, AssociateRecordsPatchItemInput input, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForPostEntityAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.AssociateRecordsPatchItemAsync"); try { - if (tableName is null) - throw new ArgumentNullException(nameof(tableName)); - var path = $"/api/flow/$metadata.json/entities/{Uri.EscapeDataString(tableName.ToString())}/postitem"; + if (dataset is null) + throw new ArgumentNullException(nameof(dataset)); + if (table is null) + throw new ArgumentNullException(nameof(table)); + if (id is null) + throw new ArgumentNullException(nameof(id)); + if (relationship is null) + throw new ArgumentNullException(nameof(relationship)); + var path = $"/v2/datasets/{DoubleEscape(dataset.ToString())}/tables/{DoubleEscape(table.ToString())}/items/{DoubleEscape(id.ToString())}/Relationship/{DoubleEscape(relationship.ToString())}"; return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .CallConnectorAsync(HttpMethod.Patch, path, input, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1108,22 +972,32 @@ public virtual async Task GetMetadataForPostEntityAsync(string t } /// - /// Get table metadata - Patch + /// Creates Note (annotation) for specified table row /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Table name + /// Creates Note (annotation) for specified table row. + /// Dataset + /// Table + /// Id + /// The request body. + /// File Name /// Cancellation token. - /// The Get table metadata - Patch response. - public virtual async Task GetMetadataForPatchEntityAsync(string tableName, CancellationToken cancellationToken = default) + /// The Creates Note (annotation) for specified table row response. + public virtual async Task CreateAttachmentAsync([DynamicValues("GetDataSets")] string dataset, [DynamicValues("GetTables")] string table, int id, byte[] input, string fileName, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForPatchEntityAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.CreateAttachmentAsync"); try { - if (tableName is null) - throw new ArgumentNullException(nameof(tableName)); - var path = $"/api/flow/$metadata.json/entities/{Uri.EscapeDataString(tableName.ToString())}/patchitem"; + if (dataset is null) + throw new ArgumentNullException(nameof(dataset)); + if (table is null) + throw new ArgumentNullException(nameof(table)); + var queryParams = new List(); + if (fileName is null) + throw new ArgumentNullException(nameof(fileName)); + queryParams.Add($"displayName={Uri.EscapeDataString(fileName.ToString())}"); + var path = $"/v2/datasets/{DoubleEscape(dataset.ToString())}/tables/{DoubleEscape(table.ToString())}/items/{DoubleEscape(id.ToString())}/attachments" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1135,19 +1009,30 @@ public virtual async Task GetMetadataForPatchEntityAsync(string } /// - /// Get attribute filters + /// Deletes specified attachment on the Note (annotation) /// - /// Discovery method used to populate dynamic parameter values at design time. + /// Deletes specified attachment on the Note (annotation). + /// Dataset + /// Table + /// Id + /// AttachmentId /// Cancellation token. - /// The Get attribute filters response. - public virtual async Task GetAttributeFiltersCodelessAsync(CancellationToken cancellationToken = default) + public virtual async Task DeleteAttachmentAsync([DynamicValues("GetDataSets")] string dataset, [DynamicValues("GetTables")] string table, string id, string attachmentId, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetAttributeFiltersCodelessAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.DeleteAttachmentAsync"); try { - var path = $"/api/flow/$metadata.json/entities/GetAttributeFilters"; - return await this - .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) + if (dataset is null) + throw new ArgumentNullException(nameof(dataset)); + if (table is null) + throw new ArgumentNullException(nameof(table)); + if (id is null) + throw new ArgumentNullException(nameof(id)); + if (attachmentId is null) + throw new ArgumentNullException(nameof(attachmentId)); + var path = $"/v2/datasets/{DoubleEscape(dataset.ToString())}/tables/{DoubleEscape(table.ToString())}/items/{DoubleEscape(id.ToString())}/attachments/{DoubleEscape(attachmentId.ToString())}"; + await this + .CallConnectorAsync(HttpMethod.Delete, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1159,19 +1044,27 @@ public virtual async Task GetAttributeFiltersCodelessAsync(Cancell } /// - /// GetEntityRelationships + /// Delete a row (legacy) /// - /// Discovery method used to populate dynamic parameter values at design time. + /// This operation deletes a row from a table collection + /// Environment + /// Table Name + /// Item identifier /// Cancellation token. - /// The GetEntityRelationships response. - public virtual async Task GetEntityRelationshipsAsync(CancellationToken cancellationToken = default) + public virtual async Task DeleteItemAsync([DynamicValues("GetDataSets_V2")] string environment, [DynamicValues("GetTables")] string tableName, string itemIdentifier, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetEntityRelationshipsAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.DeleteItemAsync"); try { - var path = $"/api/flow/$metadata.json/GetEntityRelationships"; - return await this - .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) + if (environment is null) + throw new ArgumentNullException(nameof(environment)); + if (tableName is null) + throw new ArgumentNullException(nameof(tableName)); + if (itemIdentifier is null) + throw new ArgumentNullException(nameof(itemIdentifier)); + var path = $"/v2/datasets/{DoubleEscape(environment.ToString())}/tables/{DoubleEscape(tableName.ToString())}/items/{DoubleEscape(itemIdentifier.ToString())}"; + await this + .CallConnectorAsync(HttpMethod.Delete, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1183,19 +1076,34 @@ public virtual async Task GetEntityRelatio } /// - /// GetOrganizations + /// Disassociates a row from a multi-valued relationship /// - /// Discovery method used to populate dynamic parameter values at design time. + /// Disassociates a row from a multi-valued relationship + /// Dataset + /// Table + /// Id + /// Relationship + /// RelatedId /// Cancellation token. - /// The GetOrganizations response. - public virtual async Task GetOrganizationsAsync(CancellationToken cancellationToken = default) + /// The Disassociates a row from a multi-valued relationship response. + public virtual async Task DisassociateRecordsPostItemAsync([DynamicValues("GetDataSets")] string dataset, [DynamicValues("GetTables")] string table, string id, string relationship, string relatedId, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetOrganizationsAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.DisassociateRecordsPostItemAsync"); try { - var path = $"/getorgs"; + if (dataset is null) + throw new ArgumentNullException(nameof(dataset)); + if (table is null) + throw new ArgumentNullException(nameof(table)); + if (id is null) + throw new ArgumentNullException(nameof(id)); + if (relationship is null) + throw new ArgumentNullException(nameof(relationship)); + if (relatedId is null) + throw new ArgumentNullException(nameof(relatedId)); + var path = $"/v2/datasets/{DoubleEscape(dataset.ToString())}/tables/{DoubleEscape(table.ToString())}/items/{DoubleEscape(id.ToString())}/Relationship/{DoubleEscape(relationship.ToString())}/RelatedId/{DoubleEscape(relatedId.ToString())}"; return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .CallConnectorAsync(HttpMethod.Delete, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1207,19 +1115,31 @@ public virtual async Task GetOrganizationsAsync( } /// - /// GetEntityListEnum + /// Disassociates a row from a single-valued relationship /// - /// Discovery method used to populate dynamic parameter values at design time. + /// Disassociates a row from a single-valued relationship + /// Dataset + /// Table + /// Id + /// Relationship /// Cancellation token. - /// The GetEntityListEnum response. - public virtual async Task GetEntityListEnumAsync(CancellationToken cancellationToken = default) + /// The Disassociates a row from a single-valued relationship response. + public virtual async Task DisassociateSingleValueRecordDeleteItemAsync([DynamicValues("GetDataSets")] string dataset, [DynamicValues("GetTables")] string table, string id, string relationship, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetEntityListEnumAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.DisassociateSingleValueRecordDeleteItemAsync"); try { - var path = $"/api/flow/$metadata.json/GetEntityListEnum"; + if (dataset is null) + throw new ArgumentNullException(nameof(dataset)); + if (table is null) + throw new ArgumentNullException(nameof(table)); + if (id is null) + throw new ArgumentNullException(nameof(id)); + if (relationship is null) + throw new ArgumentNullException(nameof(relationship)); + var path = $"/v2/datasets/{DoubleEscape(dataset.ToString())}/tables/{DoubleEscape(table.ToString())}/items/{DoubleEscape(id.ToString())}/Relationship/{DoubleEscape(relationship.ToString())}"; return await this - .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) + .CallConnectorAsync(HttpMethod.Delete, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1231,66 +1151,31 @@ public virtual async Task GetEntityListEnumAsync(Canc } /// - /// List rows + /// Retrieves file content for specified Note (annotation) /// - /// This action allows you to list the rows in a Microsoft Dataverse table that match the selected options. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. - /// Table name - /// Select columns - /// Filter rows - /// Sort By - /// Expand Query - /// Fetch Xml Query - /// Row count - /// Skip token - /// Partition ID - /// Cancellation token. - /// An async enumerable of items across all pages. - public virtual AsyncPageable ListRecordsAsync([DynamicValues("GetEntityListEnum")] string tableName, string selectColumns = default, string filterRows = default, string sortBy = default, string expandQuery = default, string fetchXmlQuery = default, int? rowCount = default, string skipToken = default, string partitionId = default, CancellationToken cancellationToken = default) - { - if (tableName is null) - throw new ArgumentNullException(nameof(tableName)); - var queryParams = new List(); - if (selectColumns != default) - queryParams.Add($"$select={Uri.EscapeDataString(selectColumns.ToString())}"); - if (filterRows != default) - queryParams.Add($"$filter={Uri.EscapeDataString(filterRows.ToString())}"); - if (sortBy != default) - queryParams.Add($"$orderby={Uri.EscapeDataString(sortBy.ToString())}"); - if (expandQuery != default) - queryParams.Add($"$expand={Uri.EscapeDataString(expandQuery.ToString())}"); - if (fetchXmlQuery != default) - queryParams.Add($"fetchXml={Uri.EscapeDataString(fetchXmlQuery.ToString())}"); - if (rowCount.HasValue) - queryParams.Add($"$top={Uri.EscapeDataString(rowCount.Value.ToString())}"); - if (skipToken != default) - queryParams.Add($"$skiptoken={Uri.EscapeDataString(skipToken.ToString())}"); - if (partitionId != default) - queryParams.Add($"partitionId={Uri.EscapeDataString(partitionId.ToString())}"); - var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); - return this.CreatePageable( - ct => this.CallConnectorAsync(HttpMethod.Get, path, cancellationToken: ct), - (nextLink, ct) => this.CallConnectorAsync(HttpMethod.Get, nextLink, cancellationToken: ct), - cancellationToken); - } - - /// - /// Add a new row - /// - /// This action allows you to add a new row in the selected Microsoft Dataverse table. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. - /// Table name - /// The request body. + /// Retrieves file content for specified Note (annotation). + /// Dataset + /// Table + /// Id + /// AttachmentId /// Cancellation token. - /// The Add a new row response. - public virtual async Task CreateRecordAsync([DynamicValues("GetEntityListEnum")] string tableName, CreateRecordInput input, CancellationToken cancellationToken = default) + /// The Retrieves file content for specified Note (annotation) response. + public virtual async Task GetAttachmentContentAsync([DynamicValues("GetDataSets")] string dataset, [DynamicValues("GetTables")] string table, string id, string attachmentId, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.CreateRecordAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetAttachmentContentAsync"); try { - if (tableName is null) - throw new ArgumentNullException(nameof(tableName)); - var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}"; + if (dataset is null) + throw new ArgumentNullException(nameof(dataset)); + if (table is null) + throw new ArgumentNullException(nameof(table)); + if (id is null) + throw new ArgumentNullException(nameof(id)); + if (attachmentId is null) + throw new ArgumentNullException(nameof(attachmentId)); + var path = $"/v2/datasets/{DoubleEscape(dataset.ToString())}/tables/{DoubleEscape(table.ToString())}/items/{DoubleEscape(id.ToString())}/attachments/{DoubleEscape(attachmentId.ToString())}/$value"; return await this - .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1302,35 +1187,37 @@ public virtual async Task CreateRecordAsync([DynamicValues } /// - /// Get a row by ID + /// Retrieves all collection valued relationship items as an expand would /// - /// This action allows you to get the row that matches an ID in a Microsoft Dataverse table. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. - /// Table name - /// Row ID - /// Select columns - /// Expand Query - /// Partition Id + /// Retrieves all collection valued relationship items as an expand would + /// Dataset + /// Table + /// Id + /// Collection + /// Relationship + /// Target /// Cancellation token. - /// The Get a row by ID response. - public virtual async Task GetItemCodelessAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, string selectColumns = default, string expandQuery = default, string partitionId = default, CancellationToken cancellationToken = default) + /// The Retrieves all collection valued relationship items as an expand would response. + public virtual async Task GetCollectionRelationshipsAsync([DynamicValues("GetDataSets")] string dataset, [DynamicValues("GetTables")] string table, string id, string collection, string relationship, string target, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetItemCodelessAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetCollectionRelationshipsAsync"); try { - if (tableName is null) - throw new ArgumentNullException(nameof(tableName)); - if (rowId is null) - throw new ArgumentNullException(nameof(rowId)); - var queryParams = new List(); - if (selectColumns != default) - queryParams.Add($"$select={Uri.EscapeDataString(selectColumns.ToString())}"); - if (expandQuery != default) - queryParams.Add($"$expand={Uri.EscapeDataString(expandQuery.ToString())}"); - if (partitionId != default) - queryParams.Add($"partitionId={Uri.EscapeDataString(partitionId.ToString())}"); - var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); + if (dataset is null) + throw new ArgumentNullException(nameof(dataset)); + if (table is null) + throw new ArgumentNullException(nameof(table)); + if (id is null) + throw new ArgumentNullException(nameof(id)); + if (collection is null) + throw new ArgumentNullException(nameof(collection)); + if (relationship is null) + throw new ArgumentNullException(nameof(relationship)); + if (target is null) + throw new ArgumentNullException(nameof(target)); + var path = $"/v2/datasets/{DoubleEscape(dataset.ToString())}/tables/{DoubleEscape(table.ToString())}/items/{DoubleEscape(id.ToString())}/Collection/{DoubleEscape(collection.ToString())}/{DoubleEscape(relationship.ToString())}/TargetTable/{DoubleEscape(target.ToString())}"; return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1342,28 +1229,18 @@ public virtual async Task GetItemCodelessAsync([Dynamic } /// - /// Delete a row + /// GetDataSets_V2 /// - /// This action allows you to delete a row from a Microsoft Dataverse table. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. - /// Table name - /// Row ID - /// Partition Id /// Cancellation token. - public virtual async Task DeleteRecordAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, string partitionId = default, CancellationToken cancellationToken = default) + /// The GetDataSets_V2 response. + public virtual async Task GetDataSetsAsync(CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.DeleteRecordAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetDataSetsAsync"); try { - if (tableName is null) - throw new ArgumentNullException(nameof(tableName)); - if (rowId is null) - throw new ArgumentNullException(nameof(rowId)); - var queryParams = new List(); - if (partitionId != default) - queryParams.Add($"partitionId={Uri.EscapeDataString(partitionId.ToString())}"); - var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); - await this - .CallConnectorAsync(HttpMethod.Delete, path, cancellationToken: cancellationToken) + var path = $"/v2/datasets"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1375,26 +1252,28 @@ await this } /// - /// Update a row + /// Get row (legacy) /// - /// This action allows you to modify any selected row in a Microsoft Dataverse table, or adds a new row if it doesn't exist. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. - /// Table name - /// Row ID - /// The request body. + /// This operation retrieves the specified row for a table + /// Environment + /// Table Name + /// Item identifier /// Cancellation token. - /// The Update a row response. - public virtual async Task UpdateRecordAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, UpdateRecordInput input, CancellationToken cancellationToken = default) + /// The Get row (legacy) response. + public virtual async Task GetItemAsync([DynamicValues("GetDataSets_V2")] string environment, [DynamicValues("GetTables")] string tableName, string itemIdentifier, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.UpdateRecordAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetItemAsync"); try { + if (environment is null) + throw new ArgumentNullException(nameof(environment)); if (tableName is null) throw new ArgumentNullException(nameof(tableName)); - if (rowId is null) - throw new ArgumentNullException(nameof(rowId)); - var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})"; + if (itemIdentifier is null) + throw new ArgumentNullException(nameof(itemIdentifier)); + var path = $"/v2/datasets/{DoubleEscape(environment.ToString())}/tables/{DoubleEscape(tableName.ToString())}/items/{DoubleEscape(itemIdentifier.ToString())}"; return await this - .CallConnectorAsync(HttpMethod.Patch, path, input, cancellationToken) + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1406,33 +1285,28 @@ public virtual async Task UpdateRecordAsync([DynamicValues } /// - /// Upload a file or an image + /// Retrieves all Notes (annotations) for the provided table row Id /// - /// This action allows you to upload a file or an image content to a Microsoft Dataverse table with a compatible column type. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. - /// Table name - /// Row ID - /// Column name - /// The request body. - /// Content name + /// Retrieves all Notes (annotations) for the provided table row Id. + /// Dataset + /// Table + /// Id /// Cancellation token. - public virtual async Task UpdateEntityFileImageFieldContentAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, [DynamicValues("GetAttributeFiltersCodeless")] string columnName, byte[] input, string contentName, CancellationToken cancellationToken = default) + /// The Retrieves all Notes (annotations) for the provided table row Id response. + public virtual async Task GetItemAttachmentsAsync([DynamicValues("GetDataSets")] string dataset, [DynamicValues("GetTables")] string table, string id, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.UpdateEntityFileImageFieldContentAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetItemAttachmentsAsync"); try { - if (tableName is null) - throw new ArgumentNullException(nameof(tableName)); - if (rowId is null) - throw new ArgumentNullException(nameof(rowId)); - if (columnName is null) - throw new ArgumentNullException(nameof(columnName)); - var queryParams = new List(); - if (contentName is null) - throw new ArgumentNullException(nameof(contentName)); - queryParams.Add($"x-ms-file-name={Uri.EscapeDataString(contentName.ToString())}"); - var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})/{Uri.EscapeDataString(columnName.ToString())}" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); - await this - .CallConnectorAsync(HttpMethod.Put, path, input, cancellationToken) + if (dataset is null) + throw new ArgumentNullException(nameof(dataset)); + if (table is null) + throw new ArgumentNullException(nameof(table)); + if (id is null) + throw new ArgumentNullException(nameof(id)); + var path = $"/v2/datasets/{DoubleEscape(dataset.ToString())}/tables/{DoubleEscape(table.ToString())}/items/{DoubleEscape(id.ToString())}/attachments"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1444,108 +1318,41 @@ await this } /// - /// Download a file or an image + /// List rows (legacy) /// - /// This action allows you to download a file or an image content from a Microsoft Dataverse table. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. - /// Table name - /// Row ID - /// Column name - /// Image size + /// This operation gets rows for a table + /// Environment + /// Table Name + /// Aggregation transformation + /// Filter Query + /// Order By + /// Top Count + /// Expand Query /// Cancellation token. - /// The Download a file or an image response. - public virtual async Task GetEntityFileImageFieldContentAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, [DynamicValues("GetAttributeFiltersCodeless")] string columnName, string imageSize = default, CancellationToken cancellationToken = default) + /// The List rows (legacy) response. + public virtual async Task GetItemsAsync([DynamicValues("GetDataSets_V2")] string environment, [DynamicValues("GetTables")] string tableName, string aggregationTransformation = default, string filterQuery = default, string orderBy = default, int? topCount = default, string expandQuery = default, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetEntityFileImageFieldContentAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetItemsAsync"); try { + if (environment is null) + throw new ArgumentNullException(nameof(environment)); if (tableName is null) throw new ArgumentNullException(nameof(tableName)); - if (rowId is null) - throw new ArgumentNullException(nameof(rowId)); - if (columnName is null) - throw new ArgumentNullException(nameof(columnName)); var queryParams = new List(); - if (imageSize != default) - queryParams.Add($"size={Uri.EscapeDataString(imageSize.ToString())}"); - var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})/{Uri.EscapeDataString(columnName.ToString())}/$value" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); - return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) - .ConfigureAwait(continueOnCapturedContext: false); - - } - catch (Exception ex) when (!ex.IsFatal()) - { - activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); - throw; - } - } - - /// - /// GetUnboundActions - /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Cancellation token. - /// The GetUnboundActions response. - public virtual async Task GetUnboundActionsAsync(CancellationToken cancellationToken = default) - { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetUnboundActionsAsync"); - try - { - var path = $"/api/flow/$metadata.json/actionListEnum/unboundactions"; - return await this - .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) - .ConfigureAwait(continueOnCapturedContext: false); - - } - catch (Exception ex) when (!ex.IsFatal()) - { - activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); - throw; - } - } - - /// - /// GetBoundActions - /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Cancellation token. - /// The GetBoundActions response. - public virtual async Task GetBoundActionsAsync(CancellationToken cancellationToken = default) - { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetBoundActionsAsync"); - try - { - var path = $"/api/flow/$metadata.json/actionListEnum/boundactions"; - return await this - .CallConnectorAsync(HttpMethod.Post, path, cancellationToken: cancellationToken) - .ConfigureAwait(continueOnCapturedContext: false); - - } - catch (Exception ex) when (!ex.IsFatal()) - { - activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); - throw; - } - } - - /// - /// Perform an unbound action - /// - /// This action allows you to perform Microsoft Dataverse actions available in the environment that are not associated with any table. - /// Action Name - /// The request body. - /// Cancellation token. - /// The Perform an unbound action response. - public virtual async Task PerformUnboundActionAsync([DynamicValues("GetUnboundActions")] string actionName, PerformUnboundActionInput input, CancellationToken cancellationToken = default) - { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.PerformUnboundActionAsync"); - try - { - if (actionName is null) - throw new ArgumentNullException(nameof(actionName)); - var path = $"/api/data/v9.2/{Uri.EscapeDataString(actionName.ToString())}"; + if (aggregationTransformation != default) + queryParams.Add($"$apply={Uri.EscapeDataString(aggregationTransformation.ToString())}"); + if (filterQuery != default) + queryParams.Add($"$filter={Uri.EscapeDataString(filterQuery.ToString())}"); + if (orderBy != default) + queryParams.Add($"$orderby={Uri.EscapeDataString(orderBy.ToString())}"); + if (topCount.HasValue) + queryParams.Add($"$top={Uri.EscapeDataString(topCount.Value.ToString())}"); + if (expandQuery != default) + queryParams.Add($"$expand={Uri.EscapeDataString(expandQuery.ToString())}"); + var path = $"/v2/datasets/{DoubleEscape(environment.ToString())}/tables/{DoubleEscape(tableName.ToString())}/items" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); return await this - .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1557,29 +1364,25 @@ public virtual async Task PerformUnboundActionAsyn } /// - /// Perform a bound action + /// Get table metadata - Patch /// - /// This action allows you to perform Microsoft Dataverse actions associated with a selected table. - /// Table name - /// Action Name - /// Row ID - /// The request body. + /// Gets table metadata for patch operation + /// Environment + /// Table Name /// Cancellation token. - /// The Perform a bound action response. - public virtual async Task PerformBoundActionAsync([DynamicValues("GetEntityListEnum")] string tableName, [DynamicValues("GetBoundActions")] string actionName, string rowId, PerformBoundActionInput input, CancellationToken cancellationToken = default) + /// The Get table metadata - Patch response. + public virtual async Task GetMetadataForPatchItemAsync([DynamicValues("GetDataSets")] string environment, [DynamicValues("GetTables")] string tableName, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.PerformBoundActionAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForPatchItemAsync"); try { + if (environment is null) + throw new ArgumentNullException(nameof(environment)); if (tableName is null) throw new ArgumentNullException(nameof(tableName)); - if (actionName is null) - throw new ArgumentNullException(nameof(actionName)); - if (rowId is null) - throw new ArgumentNullException(nameof(rowId)); - var path = $"/api/data/v9.2/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})/{Uri.EscapeDataString(actionName.ToString())}"; + var path = $"/v2/$metadata.json/datasets/{DoubleEscape(environment.ToString())}/tables/{DoubleEscape(tableName.ToString())}/patchitem"; return await this - .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1591,28 +1394,25 @@ public virtual async Task PerformBoundActionAsync([D } /// - /// Relate rows + /// Get table metadata - Post /// - /// This action allows you to link a row in one Microsoft Dataverse table to another if the tables have a one-to-many or many-to-many relationship. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. - /// Table name - /// Row ID - /// Relationship - /// The request body. + /// Gets table metadata for post operation + /// Environment + /// Table Name /// Cancellation token. - public virtual async Task AssociateEntitiesAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, [DynamicValues("GetEntityRelationships")] string relationship, AssociateEntityRequest input, CancellationToken cancellationToken = default) + /// The Get table metadata - Post response. + public virtual async Task GetMetadataForPostItemAsync([DynamicValues("GetDataSets")] string environment, [DynamicValues("GetTables")] string tableName, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.AssociateEntitiesAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForPostItemAsync"); try { + if (environment is null) + throw new ArgumentNullException(nameof(environment)); if (tableName is null) throw new ArgumentNullException(nameof(tableName)); - if (rowId is null) - throw new ArgumentNullException(nameof(rowId)); - if (relationship is null) - throw new ArgumentNullException(nameof(relationship)); - var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})/{Uri.EscapeDataString(relationship.ToString())}/$ref"; - await this - .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) + var path = $"/v2/$metadata.json/datasets/{DoubleEscape(environment.ToString())}/tables/{DoubleEscape(tableName.ToString())}/postitem"; + return await this + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1624,57 +1424,28 @@ await this } /// - /// Unrelate rows + /// Retrieves the metadata for multi select column metadata /// - /// This action allows you to remove the link between a row in one Microsoft Dataverse table to another if the tables have a one-to-many or many-to-many relationship. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. + /// Retrieves the metadata for multi select column metadata. + /// org name /// Table name - /// Row ID - /// Relationship - /// Unrelate with + /// id /// Cancellation token. - public virtual async Task DisassociateEntitiesAsync([DynamicValues("GetEntityListEnum")] string tableName, string rowId, [DynamicValues("GetEntityRelationships")] string relationship, string unrelateWith, CancellationToken cancellationToken = default) + /// The Retrieves the metadata for multi select column metadata response. + public virtual async Task GetMultiSelectMetadataAsync([DynamicValues("GetDataSets")] string orgName, [DynamicValues("GetTables")] string tableName, string id, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.DisassociateEntitiesAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMultiSelectMetadataAsync"); try { + if (orgName is null) + throw new ArgumentNullException(nameof(orgName)); if (tableName is null) throw new ArgumentNullException(nameof(tableName)); - if (rowId is null) - throw new ArgumentNullException(nameof(rowId)); - if (relationship is null) - throw new ArgumentNullException(nameof(relationship)); - var queryParams = new List(); - if (unrelateWith is null) - throw new ArgumentNullException(nameof(unrelateWith)); - queryParams.Add($"$id={Uri.EscapeDataString(unrelateWith.ToString())}"); - var path = $"/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}({Uri.EscapeDataString(rowId.ToString())})/{Uri.EscapeDataString(relationship.ToString())}/$ref" + (queryParams.Count > 0 ? "?" + string.Join("&", queryParams) : ""); - await this - .CallConnectorAsync(HttpMethod.Delete, path, cancellationToken: cancellationToken) - .ConfigureAwait(continueOnCapturedContext: false); - - } - catch (Exception ex) when (!ex.IsFatal()) - { - activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); - throw; - } - } - - /// - /// Search rows - /// - /// This action allows you to search a Microsoft Dataverse environment using Relevance Search, and returns the rows that match the search term most closely. This connector was formerly known as Common Data Service (legacy) and replaces the Dynamics 365 connector. - /// The request body. - /// Cancellation token. - /// The Search rows response. - public virtual async Task GetRelevantRowsAsync(SearchRequestBody input, CancellationToken cancellationToken = default) - { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetRelevantRowsAsync"); - try - { - var path = $"/api/search/v1.0/query"; + if (id is null) + throw new ArgumentNullException(nameof(id)); + var path = $"/v2/datasets/{DoubleEscape(orgName.ToString())}/tables/{DoubleEscape(tableName.ToString())}/items/{DoubleEscape(id.ToString())}/MultiSelect"; return await this - .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1686,17 +1457,29 @@ public virtual async Task GetRelevantRowsAsync(SearchRequestBody i } /// - /// Get catalogs + /// Retrieves the metadata for choice column metadata /// - /// Discovery method used to populate dynamic parameter values at design time. + /// Retrieves the metadata for choice column metadata. + /// Dataset + /// Table + /// id + /// type of choice /// Cancellation token. - /// The Get catalogs response. - public virtual async Task GetCatalogsAsync(CancellationToken cancellationToken = default) + /// The Retrieves the metadata for choice column metadata response. + public virtual async Task GetOptionSetMetadataAsync([DynamicValues("GetDataSets")] string dataset, [DynamicValues("GetTables")] string table, string id, string typeOfChoice, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetCatalogsAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetOptionSetMetadataAsync"); try { - var path = $"/api/data/v9.2/catalogs"; + if (dataset is null) + throw new ArgumentNullException(nameof(dataset)); + if (table is null) + throw new ArgumentNullException(nameof(table)); + if (id is null) + throw new ArgumentNullException(nameof(id)); + if (typeOfChoice is null) + throw new ArgumentNullException(nameof(typeOfChoice)); + var path = $"/v2/datasets/{DoubleEscape(dataset.ToString())}/tables/{DoubleEscape(table.ToString())}/items/{DoubleEscape(id.ToString())}/OptionSet/{DoubleEscape(typeOfChoice.ToString())}"; return await this .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); @@ -1710,25 +1493,25 @@ public virtual async Task GetCatalogsAsync(CancellationToken cance } /// - /// Get action metadata + /// Get table metadata /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Table name - /// Action name + /// Gets table metadata + /// Environment + /// Table Name /// Cancellation token. - /// The Get action metadata response. - public virtual async Task GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerAsync(string tableName, string actionName, CancellationToken cancellationToken = default) + /// The Get table metadata response. + public virtual async Task GetTableAsync([DynamicValues("GetDataSets")] string environment, [DynamicValues("GetTables")] string tableName, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForActionInputAndResponseForWhenAnActionIsPerformedTriggerAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetTableAsync"); try { + if (environment is null) + throw new ArgumentNullException(nameof(environment)); if (tableName is null) throw new ArgumentNullException(nameof(tableName)); - if (actionName is null) - throw new ArgumentNullException(nameof(actionName)); - var path = $"/api/flow/$metadata.json/whenAnActionIsPerformedEntity/{Uri.EscapeDataString(tableName.ToString())}/whenAnActionIsPerformedAction/{Uri.EscapeDataString(actionName.ToString())}"; + var path = $"/v2/$metadata.json/datasets/{DoubleEscape(environment.ToString())}/tables/{DoubleEscape(tableName.ToString())}"; return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1740,22 +1523,21 @@ public virtual async Task - /// Get metadata for unbound action input + /// GetTables_V2 /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Action Name + /// dataset /// Cancellation token. - /// The Get metadata for unbound action input response. - public virtual async Task GetMetadataForUnboundActionInputAsync(string actionName, CancellationToken cancellationToken = default) + /// The GetTables_V2 response. + public virtual async Task GetTablesAsync([DynamicValues("GetDataSets")] string dataset, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForUnboundActionInputAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetTablesAsync"); try { - if (actionName is null) - throw new ArgumentNullException(nameof(actionName)); - var path = $"/api/flow/$metadata.json/flow/api/data/v9.1/{Uri.EscapeDataString(actionName.ToString())}/inputs"; + if (dataset is null) + throw new ArgumentNullException(nameof(dataset)); + var path = $"/v2/datasets/{DoubleEscape(dataset.ToString())}/tables"; return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1767,52 +1549,29 @@ public virtual async Task GetMetadataF } /// - /// Get metadata for unbound action response + /// Update a row (legacy) /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Action Name - /// Cancellation token. - /// The Get metadata for unbound action response response. - public virtual async Task GetMetadataForUnboundActionResponseAsync(string actionName, CancellationToken cancellationToken = default) - { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForUnboundActionResponseAsync"); - try - { - if (actionName is null) - throw new ArgumentNullException(nameof(actionName)); - var path = $"/api/flow/$metadata.json/flow/api/data/v9.1/{Uri.EscapeDataString(actionName.ToString())}/response"; - return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) - .ConfigureAwait(continueOnCapturedContext: false); - - } - catch (Exception ex) when (!ex.IsFatal()) - { - activity?.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); - throw; - } - } - - /// - /// Get metadata for bound action input - /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Table name - /// Action Name + /// This operation updates an existing row for a table + /// Environment + /// Table Name + /// Row identifier + /// The request body. /// Cancellation token. - /// The Get metadata for bound action input response. - public virtual async Task GetMetadataForBoundActionInputAsync(string tableName, string actionName, CancellationToken cancellationToken = default) + /// The Update a row (legacy) response. + public virtual async Task PatchItemAsync([DynamicValues("GetDataSets_V2")] string environment, [DynamicValues("GetTables")] string tableName, string rowIdentifier, PatchItemInput input, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForBoundActionInputAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.PatchItemAsync"); try { + if (environment is null) + throw new ArgumentNullException(nameof(environment)); if (tableName is null) throw new ArgumentNullException(nameof(tableName)); - if (actionName is null) - throw new ArgumentNullException(nameof(actionName)); - var path = $"/api/flow/$metadata.json/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}/{Uri.EscapeDataString(actionName.ToString())}/inputs"; + if (rowIdentifier is null) + throw new ArgumentNullException(nameof(rowIdentifier)); + var path = $"/v2/datasets/{DoubleEscape(environment.ToString())}/tables/{DoubleEscape(tableName.ToString())}/items/{DoubleEscape(rowIdentifier.ToString())}"; return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .CallConnectorAsync(HttpMethod.Patch, path, input, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } @@ -1824,25 +1583,26 @@ public virtual async Task GetMetadataFor } /// - /// Get metadata for bound action response + /// Add a new row (legacy) /// - /// Discovery method used to populate dynamic parameter values at design time. - /// Table name - /// Action Name + /// This operation adds a new row of a table + /// Environment + /// Table Name + /// The request body. /// Cancellation token. - /// The Get metadata for bound action response response. - public virtual async Task GetMetadataForBoundActionResponseAsync(string tableName, string actionName, CancellationToken cancellationToken = default) + /// The Add a new row (legacy) response. + public virtual async Task PostItemAsync([DynamicValues("GetDataSets_V2")] string environment, [DynamicValues("GetTables")] string tableName, PostItemInput input, CancellationToken cancellationToken = default) { - using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.GetMetadataForBoundActionResponseAsync"); + using var activity = CommondataserviceClient.ConnectorActivitySource.StartActivity("CommondataserviceClient.PostItemAsync"); try { + if (environment is null) + throw new ArgumentNullException(nameof(environment)); if (tableName is null) throw new ArgumentNullException(nameof(tableName)); - if (actionName is null) - throw new ArgumentNullException(nameof(actionName)); - var path = $"/api/flow/$metadata.json/api/data/v9.1/{Uri.EscapeDataString(tableName.ToString())}/{Uri.EscapeDataString(actionName.ToString())}/response"; + var path = $"/v2/datasets/{DoubleEscape(environment.ToString())}/tables/{DoubleEscape(tableName.ToString())}/items"; return await this - .CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken) + .CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); } diff --git a/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs b/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs index 2db64d9..0488728 100644 --- a/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs +++ b/tests/Azure.Connectors.Sdk.Tests/CommondataserviceClientTests.cs @@ -83,24 +83,22 @@ public void Dispose_CalledTwice_ShouldNotThrow() } [TestMethod] - public async Task GetOrganizationsAsync_WithMockedResponse_ReturnsExpectedResult() + public async Task GetDataSetsAsync_WithMockedResponse_ReturnsExpectedResult() { // Arrange - var expectedResponse = new OrganizationsDynamicValuesList + var expectedResponse = new DataSetsList { Value = [ - new OrganizationsDynamicValuesListItem + new DataSet { - Id = "org-1", - FriendlyName = "Contoso", - Url = "https://contoso.crm.dynamics.com" + Name = "https://contoso.crm.dynamics.com", + DisplayName = "Contoso (contoso)" }, - new OrganizationsDynamicValuesListItem + new DataSet { - Id = "org-2", - FriendlyName = "Fabrikam", - Url = "https://fabrikam.crm.dynamics.com" + Name = "https://fabrikam.crm.dynamics.com", + DisplayName = "Fabrikam (fabrikam)" } ] }; @@ -113,19 +111,19 @@ public async Task GetOrganizationsAsync_WithMockedResponse_ReturnsExpectedResult // Act var result = await client - .GetOrganizationsAsync(cancellationToken: CancellationToken.None) + .GetDataSetsAsync(cancellationToken: CancellationToken.None) .ConfigureAwait(continueOnCapturedContext: false); // Assert Assert.IsNotNull(result); Assert.IsNotNull(result.Value); - Assert.AreEqual(2, result.Value.Count); - Assert.AreEqual("Contoso", result.Value[0].FriendlyName); - Assert.AreEqual("https://fabrikam.crm.dynamics.com", result.Value[1].Url); + Assert.AreEqual(expected: 2, actual: result.Value.Count); + Assert.AreEqual(expected: "Contoso (contoso)", actual: result.Value[0].DisplayName); + Assert.AreEqual(expected: "https://fabrikam.crm.dynamics.com", actual: result.Value[1].Name); } [TestMethod] - public async Task GetItemCodelessAsync_WithErrorResponse_ThrowsConnectorException() + public async Task GetItemAsync_WithErrorResponse_ThrowsConnectorException() { // Arrange using var client = CreateMockedClient(() => new HttpResponseMessage @@ -138,61 +136,50 @@ public async Task GetItemCodelessAsync_WithErrorResponse_ThrowsConnectorExceptio var exception = await Assert .ThrowsExactlyAsync(async () => await client - .GetItemCodelessAsync( + .GetItemAsync( + environment: "https://contoso.crm.dynamics.com", tableName: "accounts", - rowId: "00000000-0000-0000-0000-000000000000", + itemIdentifier: "00000000-0000-0000-0000-000000000000", cancellationToken: CancellationToken.None) .ConfigureAwait(continueOnCapturedContext: false)) .ConfigureAwait(continueOnCapturedContext: false); - Assert.AreEqual(400, exception.Status); + Assert.AreEqual(expected: 400, actual: exception.Status); Assert.IsTrue(exception.ResponseBody.Contains("Invalid request", StringComparison.Ordinal)); } [TestMethod] - public void OrganizationsDynamicValuesListItem_JsonSerialization_RoundTrips() + public void DataSet_JsonSerialization_RoundTrips() { // Arrange - var organization = new OrganizationsDynamicValuesListItem + var dataSet = new DataSet { - Id = "org-abc", - FriendlyName = "Contoso", - Url = "https://contoso.crm.dynamics.com" + Name = "https://contoso.crm.dynamics.com", + DisplayName = "Contoso (contoso)" }; // Act - var json = JsonSerializer.Serialize(organization); - var deserialized = JsonSerializer.Deserialize(json); + var json = JsonSerializer.Serialize(dataSet); + var deserialized = JsonSerializer.Deserialize(json); // Assert Assert.IsNotNull(deserialized); - Assert.AreEqual(organization.Id, deserialized.Id); - Assert.AreEqual(organization.FriendlyName, deserialized.FriendlyName); - Assert.AreEqual(organization.Url, deserialized.Url); + Assert.AreEqual(expected: dataSet.Name, actual: deserialized.Name); + Assert.AreEqual(expected: dataSet.DisplayName, actual: deserialized.DisplayName); } [TestMethod] - public void SearchRequestBody_JsonSerialization_RoundTrips() + public void PostItemInput_JsonSerialization_RoundTrips() { // Arrange - var request = new SearchRequestBody - { - SearchTerm = "Contoso", - SearchType = "simple", - RowCount = 25, - ReturnRowCount = true - }; + var input = new PostItemInput(); // Act - var json = JsonSerializer.Serialize(request); - var deserialized = JsonSerializer.Deserialize(json); + var json = JsonSerializer.Serialize(input); + var deserialized = JsonSerializer.Deserialize(json); // Assert Assert.IsNotNull(deserialized); - Assert.AreEqual(request.SearchTerm, deserialized.SearchTerm); - Assert.AreEqual(request.SearchType, deserialized.SearchType); - Assert.AreEqual(request.RowCount, deserialized.RowCount); - Assert.AreEqual(request.ReturnRowCount, deserialized.ReturnRowCount); } } }