< Summary

Class:Azure.Data.Tables.TableClient
Assembly:Azure.Data.Tables
File(s):C:\Git\azure-sdk-for-net\sdk\tables\Azure.Data.Tables\src\TableClient.cs
Covered lines:340
Uncovered lines:56
Coverable lines:396
Total lines:905
Line coverage:85.8% (340 of 396)
Covered branches:40
Total branches:60
Branch coverage:66.6% (40 of 60)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
.ctor(...)-100%100%
.ctor(...)-100%100%
.ctor(...)-100%100%
.ctor(...)-100%100%
.ctor()-100%100%
GetSasBuilder(...)-100%100%
GetSasBuilder(...)-100%100%
Create(...)-62.5%100%
CreateAsync()-62.5%100%
Delete(...)-57.14%100%
DeleteAsync()-57.14%100%
CreateEntityAsync()-80%50%
CreateEntity(...)-80%50%
GetEntity(...)-81.25%100%
GetEntityAsync()-81.25%100%
UpsertEntityAsync()-95.83%62.5%
UpsertEntity(...)-95.83%62.5%
UpdateEntityAsync()-96.3%62.5%
UpdateEntity(...)-96.3%62.5%
QueryAsync(...)-57.14%100%
Query(...)-57.14%100%
QueryAsync(...)-90.63%50%
Query(...)-86.96%50%
DeleteEntityAsync()-100%100%
DeleteEntity(...)-100%100%
GetAccessPolicyAsync()-62.5%100%
GetAccessPolicy(...)-62.5%100%
SetAccessPolicyAsync()-57.14%100%
SetAccessPolicy(...)-57.14%100%
CreateQueryFilter(...)-100%100%
Bind(...)-100%100%
CreateContinuationTokenFromHeaders(...)-100%100%
ParseContinuationToken(...)-75%66.67%

File(s)

C:\Git\azure-sdk-for-net\sdk\tables\Azure.Data.Tables\src\TableClient.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Linq.Expressions;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using Azure.Core;
 10using Azure.Core.Pipeline;
 11using Azure.Data.Tables.Models;
 12using Azure.Data.Tables.Queryable;
 13using Azure.Data.Tables.Sas;
 14
 15namespace Azure.Data.Tables
 16{
 17    /// <summary>
 18    /// The <see cref="TableClient"/> allows you to interact with Azure Storage
 19    /// Tables.
 20    /// </summary>
 21    public class TableClient
 22    {
 23        private readonly string _table;
 24        private readonly OdataMetadataFormat _format;
 25        private readonly ClientDiagnostics _diagnostics;
 26        private readonly TableRestClient _tableOperations;
 27        private readonly string _version;
 28        private readonly bool _isPremiumEndpoint;
 29
 30        /// <summary>
 31        /// Initializes a new instance of the <see cref="TableClient"/>.
 32        /// </summary>
 33        /// <param name="tableName">The name of the table with which this client instance will interact.</param>
 34        /// <param name="endpoint">
 35        /// A <see cref="Uri"/> referencing the table service account.
 36        /// This is likely to be similar to "https://{account_name}.table.core.windows.net/" or "https://{account_name}.
 37        /// </param>
 38        /// <param name="options">
 39        /// Optional client options that define the transport pipeline policies for authentication, retries, etc., that 
 40        /// </param>
 41        /// <exception cref="ArgumentException"><paramref name="endpoint"/> is not https.</exception>
 42        public TableClient(string tableName, Uri endpoint, TableClientOptions options = null)
 843            : this(tableName, endpoint, default(TableSharedKeyPipelinePolicy), options)
 44        {
 845            Argument.AssertNotNull(tableName, nameof(tableName));
 46
 847            if (endpoint.Scheme != "https")
 48            {
 449                throw new ArgumentException("Cannot use TokenCredential without HTTPS.", nameof(endpoint));
 50            }
 451        }
 52
 53        /// <summary>
 54        /// Initializes a new instance of the <see cref="TableClient"/>.
 55        /// </summary>
 56        /// <param name="tableName">The name of the table with which this client instance will interact.</param>
 57        /// <param name="endpoint">
 58        /// A <see cref="Uri"/> referencing the table service account.
 59        /// This is likely to be similar to "https://{account_name}.table.core.windows.net/" or "https://{account_name}.
 60        /// </param>
 61        /// <param name="credential">The shared key credential used to sign requests.</param>
 62        /// <exception cref="ArgumentNullException"><paramref name="tableName"/> or <paramref name="credential"/> is nul
 63        public TableClient(string tableName, Uri endpoint, TableSharedKeyCredential credential)
 2064            : this(tableName, endpoint, new TableSharedKeyPipelinePolicy(credential), null)
 65        {
 1266            Argument.AssertNotNull(tableName, nameof(tableName));
 1267            Argument.AssertNotNull(credential, nameof(credential));
 868        }
 69
 70        /// <summary>
 71        /// Initializes a new instance of the <see cref="TableClient"/>.
 72        /// </summary>
 73        /// <param name="tableName">The name of the table with which this client instance will interact.</param>
 74        /// <param name="endpoint">
 75        /// A <see cref="Uri"/> referencing the table service account.
 76        /// This is likely to be similar to "https://{account_name}.table.core.windows.net/" or "https://{account_name}.
 77        /// </param>
 78        /// <param name="credential">The shared key credential used to sign requests.</param>
 79        /// <param name="options">
 80        /// Optional client options that define the transport pipeline policies for authentication, retries, etc., that 
 81        /// </param>
 82        /// <exception cref="ArgumentNullException"><paramref name="tableName"/> or <paramref name="credential"/> is nul
 83        public TableClient(string tableName, Uri endpoint, TableSharedKeyCredential credential, TableClientOptions optio
 484            : this(tableName, endpoint, new TableSharedKeyPipelinePolicy(credential), options)
 85        {
 486            Argument.AssertNotNull(tableName, nameof(tableName));
 487            Argument.AssertNotNull(credential, nameof(credential));
 488        }
 89
 3290        internal TableClient(string tableName, Uri endpoint, TableSharedKeyPipelinePolicy policy, TableClientOptions opt
 91        {
 3292            Argument.AssertNotNull(tableName, nameof(tableName));
 2893            Argument.AssertNotNull(endpoint, nameof(endpoint));
 94
 2495            options ??= new TableClientOptions();
 2496            HttpPipeline pipeline = HttpPipelineBuilder.Build(options, policy);
 97
 2498            _diagnostics = new ClientDiagnostics(options);
 2499            _tableOperations = new TableRestClient(_diagnostics, pipeline, endpoint.ToString());
 24100            _version = options.VersionString;
 24101            _table = tableName;
 24102            _format = OdataMetadataFormat.ApplicationJsonOdataFullmetadata;
 24103            _isPremiumEndpoint = TableServiceClient.IsPremiumEndpoint(endpoint);
 104            ;
 24105        }
 106
 408107        internal TableClient(string table, TableRestClient tableOperations, string version, ClientDiagnostics diagnostic
 108        {
 408109            _tableOperations = tableOperations;
 408110            _version = version;
 408111            _table = table;
 408112            _format = OdataMetadataFormat.ApplicationJsonOdataFullmetadata;
 408113            _diagnostics = diagnostics;
 408114            _isPremiumEndpoint = isPremiumEndpoint;
 408115        }
 116
 117        /// <summary>
 118        /// Initializes a new instance of the <see cref="TableClient"/>
 119        /// class for mocking.
 120        /// </summary>
 408121        protected TableClient()
 408122        { }
 123
 124
 125        /// <summary>
 126        /// Gets a <see cref="TableSasBuilder"/> instance scoped to the current table.
 127        /// </summary>
 128        /// <param name="permissions"><see cref="TableSasPermissions"/> containing the allowed permissions.</param>
 129        /// <param name="expiresOn">The time at which the shared access signature becomes invalid.</param>
 130        /// <returns>An instance of <see cref="TableSasBuilder"/>.</returns>
 131        public virtual TableSasBuilder GetSasBuilder(TableSasPermissions permissions, DateTimeOffset expiresOn)
 132        {
 12133            return new TableSasBuilder(_table, permissions, expiresOn) { Version = _version };
 134        }
 135
 136        /// <summary>
 137        /// Gets a <see cref="TableSasBuilder"/> instance scoped to the current table.
 138        /// </summary>
 139        /// <param name="rawPermissions">The permissions associated with the shared access signature. This string should
 140        /// <param name="expiresOn">The time at which the shared access signature becomes invalid.</param>
 141        /// <returns>An instance of <see cref="TableSasBuilder"/>.</returns>
 142        public virtual TableSasBuilder GetSasBuilder(string rawPermissions, DateTimeOffset expiresOn)
 143        {
 4144            return new TableSasBuilder(_table, rawPermissions, expiresOn) { Version = _version };
 145        }
 146
 147        /// <summary>
 148        /// Creates the current table.
 149        /// </summary>
 150        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 151        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 152        /// <returns>A <see cref="TableItem"/> containing properties of the table</returns>
 153        public virtual Response<TableItem> Create(CancellationToken cancellationToken = default)
 154        {
 4155            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(Create)}");
 4156            scope.Start();
 157            try
 158            {
 4159                var response = _tableOperations.Create(new TableProperties() { TableName = _table }, null, queryOptions:
 4160                return Response.FromValue(response.Value as TableItem, response.GetRawResponse());
 161            }
 0162            catch (Exception ex)
 163            {
 0164                scope.Failed(ex);
 0165                throw;
 166            }
 4167        }
 168
 169        /// <summary>
 170        /// Creates the current table.
 171        /// </summary>
 172        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 173        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 174        /// <returns>A <see cref="TableItem"/> containing properties of the table</returns>
 175        public virtual async Task<Response<TableItem>> CreateAsync(CancellationToken cancellationToken = default)
 176        {
 4177            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(Create)}");
 4178            scope.Start();
 179            try
 180            {
 4181                var response = await _tableOperations.CreateAsync(new TableProperties() { TableName = _table }, null, qu
 4182                return Response.FromValue(response.Value as TableItem, response.GetRawResponse());
 183            }
 0184            catch (Exception ex)
 185            {
 0186                scope.Failed(ex);
 0187                throw;
 188            }
 4189        }
 190
 191        /// <summary>
 192        /// Deletes the current table.
 193        /// </summary>
 194        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 195        /// <returns></returns>
 196        public virtual Response Delete(CancellationToken cancellationToken = default)
 197        {
 4198            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(Delete)}");
 4199            scope.Start();
 200            try
 201            {
 4202                return _tableOperations.Delete(table: _table, null, cancellationToken: cancellationToken);
 203            }
 0204            catch (Exception ex)
 205            {
 0206                scope.Failed(ex);
 0207                throw;
 208            }
 4209        }
 210
 211        /// <summary>
 212        /// Deletes the current table.
 213        /// </summary>
 214        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 215        /// <returns></returns>
 216        public virtual async Task<Response> DeleteAsync(CancellationToken cancellationToken = default)
 217        {
 4218            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(Delete)}");
 4219            scope.Start();
 220            try
 221            {
 4222                return await _tableOperations.DeleteAsync(table: _table, null, cancellationToken: cancellationToken).Con
 223            }
 0224            catch (Exception ex)
 225            {
 0226                scope.Failed(ex);
 0227                throw;
 228            }
 4229        }
 230
 231        /// <summary>
 232        /// Creates a Table Entity into the Table.
 233        /// </summary>
 234        /// <param name="entity">The entity to create.</param>
 235        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 236        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 237        /// <returns>The created Table entity.</returns>
 238        /// <exception cref="RequestFailedException">Exception thrown if entity already exists.</exception>
 239        public virtual async Task<Response<T>> CreateEntityAsync<T>(T entity, CancellationToken cancellationToken = defa
 240        {
 854241            Argument.AssertNotNull(entity, nameof(entity));
 852242            Argument.AssertNotNull(entity?.PartitionKey, nameof(entity.PartitionKey));
 852243            Argument.AssertNotNull(entity?.RowKey, nameof(entity.RowKey));
 852244            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(CreateEntity)}");
 852245            scope.Start();
 246            try
 247            {
 852248                var response = await _tableOperations.InsertEntityAsync(_table,
 852249                    tableEntityProperties: entity.ToOdataAnnotatedDictionary(),
 852250                    queryOptions: new QueryOptions() { Format = _format },
 852251                    cancellationToken: cancellationToken).ConfigureAwait(false);
 252
 852253                var result = ((Dictionary<string, object>)response.Value).ToTableEntity<T>();
 852254                return Response.FromValue(result, response.GetRawResponse());
 255            }
 0256            catch (Exception ex)
 257            {
 0258                scope.Failed(ex);
 0259                throw;
 260            }
 852261        }
 262
 263        /// <summary>
 264        /// Creates a Table Entity into the Table.
 265        /// </summary>
 266        /// <param name="entity">The entity to create.</param>
 267        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 268        /// <returns>The created Table entity.</returns>
 269        /// <exception cref="RequestFailedException">Exception thrown if entity already exists.</exception>
 270        public virtual Response<T> CreateEntity<T>(T entity, CancellationToken cancellationToken = default) where T : cl
 271        {
 854272            Argument.AssertNotNull(entity, nameof(entity));
 852273            Argument.AssertNotNull(entity?.PartitionKey, nameof(entity.PartitionKey));
 852274            Argument.AssertNotNull(entity?.RowKey, nameof(entity.RowKey));
 852275            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(CreateEntity)}");
 852276            scope.Start();
 277            try
 278            {
 852279                var response = _tableOperations.InsertEntity(_table,
 852280                    tableEntityProperties: entity.ToOdataAnnotatedDictionary(),
 852281                    queryOptions: new QueryOptions() { Format = _format },
 852282                    cancellationToken: cancellationToken);
 283
 852284                var result = ((Dictionary<string, object>)response.Value).ToTableEntity<T>();
 852285                return Response.FromValue(result, response.GetRawResponse());
 286            }
 0287            catch (Exception ex)
 288            {
 0289                scope.Failed(ex);
 0290                throw;
 291            }
 852292        }
 293
 294        /// <summary>
 295        /// Gets the specified table entity.
 296        /// </summary>
 297        /// <param name="partitionKey">The partitionKey that identifies the table entity.</param>
 298        /// <param name="rowKey">The rowKey that identifies the table entity.</param>
 299        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 300        /// <returns>The <see cref="Response"/> indicating the result of the operation.</returns>
 301        /// <exception cref="RequestFailedException">Exception thrown if the entity doesn't exist.</exception>
 302        /// <exception cref="ArgumentNullException"><paramref name="partitionKey"/> or <paramref name="rowKey"/> is null
 303        public virtual Response<T> GetEntity<T>(string partitionKey, string rowKey, CancellationToken cancellationToken 
 304        {
 4305            Argument.AssertNotNull("message", nameof(partitionKey));
 4306            Argument.AssertNotNull("message", nameof(rowKey));
 307
 4308            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(GetEntity)}");
 4309            scope.Start();
 310            try
 311            {
 4312                var response = _tableOperations.QueryEntitiesWithPartitionAndRowKey(
 4313                    _table,
 4314                    partitionKey,
 4315                    rowKey,
 4316                    queryOptions: new QueryOptions() { Format = _format },
 4317                    cancellationToken: cancellationToken);
 318
 4319                var result = ((Dictionary<string, object>)response.Value).ToTableEntity<T>();
 4320                return Response.FromValue(result, response.GetRawResponse());
 321            }
 0322            catch (Exception ex)
 323            {
 0324                scope.Failed(ex);
 0325                throw;
 326            }
 4327        }
 328
 329        /// <summary>
 330        /// Gets the specified table entity.
 331        /// </summary>
 332        /// <param name="partitionKey">The partitionKey that identifies the table entity.</param>
 333        /// <param name="rowKey">The rowKey that identifies the table entity.</param>
 334        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 335        /// <returns>The <see cref="Response"/> indicating the result of the operation.</returns>
 336        /// <exception cref="RequestFailedException">Exception thrown if the entity doesn't exist.</exception>
 337        /// <exception cref="ArgumentNullException"><paramref name="partitionKey"/> or <paramref name="rowKey"/> is null
 338        public virtual async Task<Response<T>> GetEntityAsync<T>(string partitionKey, string rowKey, CancellationToken c
 339        {
 4340            Argument.AssertNotNull("message", nameof(partitionKey));
 4341            Argument.AssertNotNull("message", nameof(rowKey));
 342
 4343            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(GetEntity)}");
 4344            scope.Start();
 345            try
 346            {
 4347                var response = await _tableOperations.QueryEntitiesWithPartitionAndRowKeyAsync(
 4348                    _table,
 4349                    partitionKey,
 4350                    rowKey,
 4351                    queryOptions: new QueryOptions() { Format = _format },
 4352                    cancellationToken: cancellationToken).ConfigureAwait(false);
 353
 4354                var result = ((Dictionary<string, object>)response.Value).ToTableEntity<T>();
 4355                return Response.FromValue(result, response.GetRawResponse());
 356            }
 0357            catch (Exception ex)
 358            {
 0359                scope.Failed(ex);
 0360                throw;
 361            }
 4362        }
 363
 364        /// <summary>
 365        /// Replaces the specified table entity, if it exists. Creates the entity if it does not exist.
 366        /// </summary>
 367        /// <param name="entity">The entity to upsert.</param>
 368        /// <param name="mode">An enum that determines which upsert operation to perform.</param>
 369        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 370        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 371        /// <returns>The <see cref="Response"/> indicating the result of the operation.</returns>
 372        public virtual async Task<Response> UpsertEntityAsync<T>(T entity, TableUpdateMode mode = TableUpdateMode.Merge,
 373        {
 72374            Argument.AssertNotNull(entity, nameof(entity));
 70375            Argument.AssertNotNull(entity?.PartitionKey, nameof(entity.PartitionKey));
 68376            Argument.AssertNotNull(entity?.RowKey, nameof(entity.RowKey));
 66377            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(UpsertEntity)}");
 66378            scope.Start();
 379            try
 380            {
 66381                if (mode == TableUpdateMode.Replace)
 382                {
 62383                    return await _tableOperations.UpdateEntityAsync(_table,
 62384                        entity.PartitionKey,
 62385                        entity.RowKey,
 62386                        tableEntityProperties: entity.ToOdataAnnotatedDictionary(),
 62387                        queryOptions: new QueryOptions() { Format = _format },
 62388                        cancellationToken: cancellationToken).ConfigureAwait(false);
 389                }
 4390                else if (mode == TableUpdateMode.Merge)
 391                {
 4392                    return await _tableOperations.MergeEntityAsync(_table,
 4393                        entity.PartitionKey,
 4394                        entity.RowKey,
 4395                        tableEntityProperties: entity.ToOdataAnnotatedDictionary(),
 4396                        queryOptions: new QueryOptions() { Format = _format },
 4397                        cancellationToken: cancellationToken).ConfigureAwait(false);
 398                }
 399                else
 400                {
 0401                    throw new ArgumentException($"Unexpected value for {nameof(mode)}: {mode}");
 402                }
 403            }
 4404            catch (Exception ex)
 405            {
 4406                scope.Failed(ex);
 4407                throw;
 408            }
 62409        }
 410
 411        /// <summary>
 412        /// Replaces the specified table entity, if it exists. Creates the entity if it does not exist.
 413        /// </summary>
 414        /// <param name="entity">The entity to upsert.</param>
 415        /// <param name="mode">An enum that determines which upsert operation to perform.</param>
 416        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 417        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 418        /// <returns>The <see cref="Response"/> indicating the result of the operation.</returns>
 419        public virtual Response UpsertEntity<T>(T entity, TableUpdateMode mode = TableUpdateMode.Merge, CancellationToke
 420        {
 72421            Argument.AssertNotNull(entity, nameof(entity));
 70422            Argument.AssertNotNull(entity?.PartitionKey, nameof(entity.PartitionKey));
 68423            Argument.AssertNotNull(entity?.RowKey, nameof(entity.RowKey));
 66424            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(UpsertEntity)}");
 66425            scope.Start();
 426            try
 427            {
 66428                if (mode == TableUpdateMode.Replace)
 429                {
 62430                    return _tableOperations.UpdateEntity(_table,
 62431                        entity.PartitionKey,
 62432                        entity.RowKey,
 62433                        tableEntityProperties: entity.ToOdataAnnotatedDictionary(),
 62434                        queryOptions: new QueryOptions() { Format = _format },
 62435                        cancellationToken: cancellationToken);
 436                }
 4437                else if (mode == TableUpdateMode.Merge)
 438                {
 4439                    return _tableOperations.MergeEntity(_table,
 4440                        entity.PartitionKey,
 4441                        entity.RowKey,
 4442                        tableEntityProperties: entity.ToOdataAnnotatedDictionary(),
 4443                        queryOptions: new QueryOptions() { Format = _format },
 4444                        cancellationToken: cancellationToken);
 445                }
 446                else
 447                {
 0448                    throw new ArgumentException($"Unexpected value for {nameof(mode)}: {mode}");
 449                }
 450            }
 4451            catch (Exception ex)
 452            {
 4453                scope.Failed(ex);
 4454                throw;
 455            }
 62456        }
 457
 458        /// <summary>
 459        /// Replaces the specified table entity, if it exists.
 460        /// </summary>
 461        /// <param name="entity">The entity to update.</param>
 462        /// <param name="ifMatch">The If-Match value to be used for optimistic concurrency.</param>
 463        /// <param name="mode">An enum that determines which upsert operation to perform.</param>
 464        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 465        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 466        /// <returns>The <see cref="Response"/> indicating the result of the operation.</returns>
 467        public virtual async Task<Response> UpdateEntityAsync<T>(T entity, string ifMatch, TableUpdateMode mode = TableU
 468        {
 44469            Argument.AssertNotNull(entity, nameof(entity));
 42470            Argument.AssertNotNull(entity?.PartitionKey, nameof(entity.PartitionKey));
 40471            Argument.AssertNotNull(entity?.RowKey, nameof(entity.RowKey));
 38472            Argument.AssertNotNullOrWhiteSpace(ifMatch, nameof(ifMatch));
 36473            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(UpdateEntity)}");
 36474            scope.Start();
 475            try
 476            {
 36477                if (mode == TableUpdateMode.Replace)
 478                {
 24479                    return await _tableOperations.UpdateEntityAsync(_table,
 24480                        entity.PartitionKey,
 24481                        entity.RowKey,
 24482                        tableEntityProperties: entity.ToOdataAnnotatedDictionary(),
 24483                        ifMatch: ifMatch,
 24484                        queryOptions: new QueryOptions() { Format = _format },
 24485                        cancellationToken: cancellationToken).ConfigureAwait(false);
 486                }
 12487                else if (mode == TableUpdateMode.Merge)
 488                {
 12489                    return await _tableOperations.MergeEntityAsync(_table,
 12490                        entity.PartitionKey,
 12491                        entity.RowKey,
 12492                        tableEntityProperties: entity.ToOdataAnnotatedDictionary(),
 12493                        ifMatch: ifMatch,
 12494                        queryOptions: new QueryOptions() { Format = _format },
 12495                        cancellationToken: cancellationToken).ConfigureAwait(false);
 496                }
 497                else
 498                {
 0499                    throw new ArgumentException($"Unexpected value for {nameof(mode)}: {mode}");
 500                }
 501            }
 12502            catch (Exception ex)
 503            {
 12504                scope.Failed(ex);
 12505                throw;
 506            }
 24507        }
 508
 509        /// <summary>
 510        /// Replaces the specified table entity, if it exists.
 511        /// </summary>
 512        /// <param name="entity">The entity to update.</param>
 513        /// <param name="ifMatch">The If-Match value to be used for optimistic concurrency.</param>
 514        /// <param name="mode">An enum that determines which upsert operation to perform.</param>
 515        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 516        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 517        /// <returns>The <see cref="Response"/> indicating the result of the operation.</returns>
 518        public virtual Response UpdateEntity<T>(T entity, string ifMatch, TableUpdateMode mode = TableUpdateMode.Merge, 
 519        {
 44520            Argument.AssertNotNull(entity, nameof(entity));
 42521            Argument.AssertNotNull(entity?.PartitionKey, nameof(entity.PartitionKey));
 40522            Argument.AssertNotNull(entity?.RowKey, nameof(entity.RowKey));
 38523            Argument.AssertNotNullOrWhiteSpace(ifMatch, nameof(ifMatch));
 36524            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(UpdateEntity)}");
 36525            scope.Start();
 526            try
 527            {
 36528                if (mode == TableUpdateMode.Replace)
 529                {
 24530                    return _tableOperations.UpdateEntity(_table,
 24531                        entity.PartitionKey,
 24532                        entity.RowKey,
 24533                        tableEntityProperties: entity.ToOdataAnnotatedDictionary(),
 24534                        ifMatch: ifMatch,
 24535                        queryOptions: new QueryOptions() { Format = _format },
 24536                        cancellationToken: cancellationToken);
 537                }
 12538                else if (mode == TableUpdateMode.Merge)
 539                {
 12540                    return _tableOperations.MergeEntity(_table,
 12541                        entity.PartitionKey,
 12542                        entity.RowKey,
 12543                        tableEntityProperties: entity.ToOdataAnnotatedDictionary(),
 12544                        ifMatch: ifMatch,
 12545                        queryOptions: new QueryOptions() { Format = _format },
 12546                        cancellationToken: cancellationToken);
 547                }
 548                else
 549                {
 0550                    throw new ArgumentException($"Unexpected value for {nameof(mode)}: {mode}");
 551                }
 552            }
 12553            catch (Exception ex)
 554            {
 12555                scope.Failed(ex);
 12556                throw;
 557            }
 24558        }
 559
 560        /// <summary>
 561        /// Queries entities in the table.
 562        /// </summary>
 563        /// <param name="filter">Returns only entities that satisfy the specified filter.</param>
 564        /// <param name="maxPerPage">The maximum number of entities that will be returned per page.</param>
 565        /// <param name="select">Selects which set of entity properties to return in the result set.</param>
 566        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 567        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 568        public virtual AsyncPageable<T> QueryAsync<T>(Expression<Func<T, bool>> filter, int? maxPerPage = null, IEnumera
 569        {
 192570            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(Query)}");
 192571            scope.Start();
 572            try
 573            {
 192574                return QueryAsync<T>(Bind(filter), maxPerPage, select, cancellationToken);
 575            }
 0576            catch (Exception ex)
 577            {
 0578                scope.Failed(ex);
 0579                throw;
 580            }
 192581        }
 582
 583        /// <summary>
 584        /// Queries entities in the table.
 585        /// </summary>
 586        /// <param name="filter">Returns only entities that satisfy the specified filter.</param>
 587        /// <param name="maxPerPage">The maximum number of entities that will be returned per page.</param>
 588        /// <param name="select">Selects which set of entity properties to return in the result set.</param>
 589        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 590        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 591        public virtual Pageable<T> Query<T>(Expression<Func<T, bool>> filter, int? maxPerPage = null, IEnumerable<string
 592        {
 192593            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(Query)}");
 192594            scope.Start();
 595            try
 596            {
 192597                return Query<T>(Bind(filter), maxPerPage, select, cancellationToken);
 598            }
 0599            catch (Exception ex)
 600            {
 0601                scope.Failed(ex);
 0602                throw;
 603            }
 192604        }
 605
 606        /// <summary>
 607        /// Queries entities in the table.
 608        /// </summary>
 609        /// <param name="filter">Returns only entities that satisfy the specified filter.</param>
 610        /// <param name="maxPerPage">The maximum number of entities that will be returned per page.</param>
 611        /// <param name="select">Selects which set of entity properties to return in the result set.</param>
 612        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 613        /// <returns></returns>
 614        public virtual AsyncPageable<T> QueryAsync<T>(string filter = null, int? maxPerPage = null, IEnumerable<string> 
 615        {
 334616            string selectArg = select == null ? null : string.Join(",", select);
 617
 334618            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(Query)}");
 334619            scope.Start();
 620            try
 621            {
 334622                return PageableHelpers.CreateAsyncEnumerable(async _ =>
 334623            {
 668624                var response = await _tableOperations.QueryEntitiesAsync(
 668625                    _table,
 668626                    queryOptions: new QueryOptions() { Format = _format, Top = maxPerPage, Filter = filter, Select = sel
 668627                    cancellationToken: cancellationToken).ConfigureAwait(false);
 334628
 668629                return Page.FromValues(response.Value.Value.ToTableEntityList<T>(),
 668630                    CreateContinuationTokenFromHeaders(response.Headers),
 668631                    response.GetRawResponse());
 668632            }, async (continuationToken, _) =>
 334633            {
 366634                var (NextPartitionKey, NextRowKey) = ParseContinuationToken(continuationToken);
 334635
 366636                var response = await _tableOperations.QueryEntitiesAsync(
 366637                    _table,
 366638                    queryOptions: new QueryOptions() { Format = _format, Top = maxPerPage, Filter = filter, Select = sel
 366639                    nextPartitionKey: NextPartitionKey,
 366640                    nextRowKey: NextRowKey,
 366641                    cancellationToken: cancellationToken).ConfigureAwait(false);
 334642
 366643                return Page.FromValues(response.Value.Value.ToTableEntityList<T>(),
 366644                    CreateContinuationTokenFromHeaders(response.Headers),
 366645                    response.GetRawResponse());
 366646            });
 647            }
 0648            catch (Exception ex)
 649            {
 0650                scope.Failed(ex);
 0651                throw;
 652            }
 334653        }
 654
 655
 656        /// <summary>
 657        /// Queries entities in the table.
 658        /// </summary>
 659        /// <param name="filter">Returns only entities that satisfy the specified filter.</param>
 660        /// <param name="maxPerPage">The maximum number of entities that will be returned per page.</param>
 661        /// <param name="select">Selects which set of entity properties to return in the result set.</param>
 662        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 663
 664        public virtual Pageable<T> Query<T>(string filter = null, int? maxPerPage = null, IEnumerable<string> select = n
 665        {
 334666            string selectArg = select == null ? null : string.Join(",", select);
 667
 334668            return PageableHelpers.CreateEnumerable((int? _) =>
 334669            {
 668670                using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(Query)}");
 668671                scope.Start();
 334672                try
 334673                {
 668674                    var response = _tableOperations.QueryEntities(_table,
 668675                        queryOptions: new QueryOptions() { Format = _format, Top = maxPerPage, Filter = filter, Select =
 668676                        cancellationToken: cancellationToken);
 334677
 668678                    return Page.FromValues(
 668679                        response.Value.Value.ToTableEntityList<T>(),
 668680                        CreateContinuationTokenFromHeaders(response.Headers),
 668681                        response.GetRawResponse());
 334682                }
 0683                catch (Exception ex)
 334684                {
 0685                    scope.Failed(ex);
 0686                    throw;
 334687                }
 668688            }, (continuationToken, _) =>
 334689            {
 366690                using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(Query)}");
 366691                scope.Start();
 334692                try
 334693                {
 366694                    var (NextPartitionKey, NextRowKey) = ParseContinuationToken(continuationToken);
 334695
 366696                    var response = _tableOperations.QueryEntities(
 366697                        _table,
 366698                        queryOptions: new QueryOptions() { Format = _format, Top = maxPerPage, Filter = filter, Select =
 366699                        nextPartitionKey: NextPartitionKey,
 366700                        nextRowKey: NextRowKey,
 366701                        cancellationToken: cancellationToken);
 334702
 366703                    return Page.FromValues(response.Value.Value.ToTableEntityList<T>(),
 366704                        CreateContinuationTokenFromHeaders(response.Headers),
 366705                        response.GetRawResponse());
 334706                }
 0707                catch (Exception ex)
 334708                {
 0709                    scope.Failed(ex);
 0710                    throw;
 334711                }
 366712            });
 713        }
 714
 715        /// <summary>
 716        /// Deletes the specified table entity.
 717        /// </summary>
 718        /// <param name="partitionKey">The partitionKey that identifies the table entity.</param>
 719        /// <param name="rowKey">The rowKey that identifies the table entity.</param>
 720        /// <param name="ifMatch">The If-Match value to be used for optimistic concurrency.</param>
 721        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 722        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 723        /// <returns>The <see cref="Response"/> indicating the result of the operation.</returns>
 724        public virtual async Task<Response> DeleteEntityAsync(string partitionKey, string rowKey, string ifMatch = "*", 
 725        {
 24726            Argument.AssertNotNull(partitionKey, nameof(partitionKey));
 24727            Argument.AssertNotNull(rowKey, nameof(rowKey));
 24728            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(DeleteEntity)}");
 24729            scope.Start();
 730            try
 731            {
 24732                return await _tableOperations.DeleteEntityAsync(_table,
 24733                    partitionKey,
 24734                    rowKey,
 24735                    ifMatch: ifMatch,
 24736                    queryOptions: new QueryOptions() { Format = _format },
 24737                    cancellationToken: cancellationToken).ConfigureAwait(false);
 738            }
 8739            catch (Exception ex)
 740            {
 8741                scope.Failed(ex);
 8742                throw;
 743            }
 16744        }
 745
 746        /// <summary>
 747        /// Deletes the specified table entity.
 748        /// </summary>
 749        /// <param name="partitionKey">The partitionKey that identifies the table entity.</param>
 750        /// <param name="rowKey">The rowKey that identifies the table entity.</param>
 751        /// <param name="ifMatch">The If-Match value to be used for optimistic concurrency. The default is to delete unc
 752        /// <param name="cancellationToken">A <see cref="CancellationToken"/> controlling the request lifetime.</param>
 753        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 754        /// <returns>The <see cref="Response"/> indicating the result of the operation.</returns>
 755        public virtual Response DeleteEntity(string partitionKey, string rowKey, string ifMatch = "*", CancellationToken
 756        {
 24757            Argument.AssertNotNull(partitionKey, nameof(partitionKey));
 24758            Argument.AssertNotNull(rowKey, nameof(rowKey));
 24759            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(DeleteEntity)}");
 24760            scope.Start();
 761            try
 762            {
 24763                return _tableOperations.DeleteEntity(_table,
 24764                    partitionKey,
 24765                    rowKey,
 24766                    ifMatch: ifMatch,
 24767                    queryOptions: new QueryOptions() { Format = _format },
 24768                    cancellationToken: cancellationToken);
 769            }
 8770            catch (Exception ex)
 771            {
 8772                scope.Failed(ex);
 8773                throw;
 774            }
 16775        }
 776
 777        /// <summary> Retrieves details about any stored access policies specified on the table that may be used with Sh
 778        /// <param name="cancellationToken"> The cancellation token to use. </param>
 779        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 780        public virtual async Task<Response<IReadOnlyList<SignedIdentifier>>> GetAccessPolicyAsync(CancellationToken canc
 781        {
 2782            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(GetAccessPolicy)}");
 2783            scope.Start();
 784            try
 785            {
 2786                var response = await _tableOperations.GetAccessPolicyAsync(_table, cancellationToken: cancellationToken)
 2787                return Response.FromValue(response.Value, response.GetRawResponse());
 788            }
 0789            catch (Exception ex)
 790            {
 0791                scope.Failed(ex);
 0792                throw;
 793            }
 2794        }
 795
 796        /// <summary> Retrieves details about any stored access policies specified on the table that may be used with Sh
 797        /// <param name="cancellationToken"> The cancellation token to use. </param>
 798        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 799        public virtual Response<IReadOnlyList<SignedIdentifier>> GetAccessPolicy(CancellationToken cancellationToken = d
 800        {
 2801            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(GetAccessPolicy)}");
 2802            scope.Start();
 803            try
 804            {
 2805                var response = _tableOperations.GetAccessPolicy(_table, cancellationToken: cancellationToken);
 2806                return Response.FromValue(response.Value, response.GetRawResponse());
 807            }
 0808            catch (Exception ex)
 809            {
 0810                scope.Failed(ex);
 0811                throw;
 812            }
 2813        }
 814
 815        /// <summary> sets stored access policies for the table that may be used with Shared Access Signatures. </summar
 816        /// <param name="tableAcl"> the access policies for the table. </param>
 817        /// <param name="cancellationToken"> The cancellation token to use. </param>
 818        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 819        public virtual async Task<Response> SetAccessPolicyAsync(IEnumerable<SignedIdentifier> tableAcl = null, Cancella
 820        {
 2821            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(SetAccessPolicy)}");
 2822            scope.Start();
 823            try
 824            {
 2825                return await _tableOperations.SetAccessPolicyAsync(_table, tableAcl: tableAcl, cancellationToken: cancel
 826            }
 0827            catch (Exception ex)
 828            {
 0829                scope.Failed(ex);
 0830                throw;
 831            }
 2832        }
 833
 834        /// <summary> sets stored access policies for the table that may be used with Shared Access Signatures. </summar
 835        /// <param name="tableAcl"> the access policies for the table. </param>
 836        /// <param name="cancellationToken"> The cancellation token to use. </param>
 837        /// <exception cref="RequestFailedException">The server returned an error. See <see cref="Exception.Message"/> f
 838        public virtual Response SetAccessPolicy(IEnumerable<SignedIdentifier> tableAcl, CancellationToken cancellationTo
 839        {
 2840            using DiagnosticScope scope = _diagnostics.CreateScope($"{nameof(TableClient)}.{nameof(SetAccessPolicy)}");
 2841            scope.Start();
 842            try
 843            {
 2844                return _tableOperations.SetAccessPolicy(_table, tableAcl: tableAcl, cancellationToken: cancellationToken
 845            }
 0846            catch (Exception ex)
 847            {
 0848                scope.Failed(ex);
 0849                throw;
 850            }
 2851        }
 852
 853        /// <summary>
 854        /// Creates an Odata filter query string from the provided expression.
 855        /// </summary>
 856        /// <typeparam name="T">The type of the entity being queried. Typically this will be derrived from <see cref="IT
 857        /// <param name="filter">A filter expresssion.</param>
 858        /// <returns>The string representation of the filter expression.</returns>
 76859        public static string CreateQueryFilter<T>(Expression<Func<T, bool>> filter) => Bind(filter);
 860
 861        internal static string Bind(Expression expression)
 862        {
 460863            Argument.AssertNotNull(expression, nameof(expression));
 864
 460865            Dictionary<Expression, Expression> normalizerRewrites = new Dictionary<Expression, Expression>(ReferenceEqua
 866
 867            // Evaluate any local evaluatable expressions ( lambdas etc)
 460868            Expression partialEvaluatedExpression = Evaluator.PartialEval(expression);
 869
 870            // Normalize expression, replace String Comparisons etc.
 460871            Expression normalizedExpression = ExpressionNormalizer.Normalize(partialEvaluatedExpression, normalizerRewri
 872
 873            // Parse the Bound expression into sub components, i.e. take count, filter, select columns, request options,
 460874            ExpressionParser parser = new ExpressionParser();
 460875            parser.Translate(normalizedExpression);
 876
 877            // Return the FilterString.
 460878            return parser.FilterString == "true" ? null : parser.FilterString;
 879        }
 880
 881        private static string CreateContinuationTokenFromHeaders(TableQueryEntitiesHeaders headers)
 882        {
 732883            if (headers.XMsContinuationNextPartitionKey == null && headers.XMsContinuationNextRowKey == null)
 884            {
 668885                return null;
 886            }
 887            else
 888            {
 64889                return $"{headers.XMsContinuationNextPartitionKey} {headers.XMsContinuationNextRowKey}";
 890            }
 891        }
 892
 893        private static (string NextPartitionKey, string NextRowKey) ParseContinuationToken(string continuationToken)
 894        {
 895            // There were no headers passed and the continuation token contains just the space delimiter
 64896            if (continuationToken?.Length <= 1)
 897            {
 0898                return (null, null);
 899            }
 900
 64901            var tokens = continuationToken.Split(' ');
 64902            return (tokens[0], tokens.Length > 1 ? tokens[1] : null);
 903        }
 904    }
 905}