How do I query an Azure storage table with Linq?

2019-06-15 00:37发布

问题:

I'm not sure where exactly, but I've got the wrong idea somewhere with this.

I'm trying to, in a first instance, query an azure storage table using linq. But I can't work out how it's done. From looking at a variety of sources I have the following:

List<BlogViewModel> blogs = new List<BlogViewModel>();

CloudStorageAccount storageAccount = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("BlogConnectionString"));
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable blogTable = tableClient.GetTableReference("BlogEntries");

try
{
   TableServiceContext tableServiceContext = tableClient.GetTableServiceContext();
   TableServiceQuery<BlogEntry> query = (from blog in blogTable.CreateQuery<BlogEntry>()
   select blog).AsTableServiceQuery<BlogEntry>(tableServiceContext);
   foreach (BlogEntry blog in query)
   {
      blogs.Add(new BlogViewModel { Body = blog.Body });
   }
}
catch { }

I probably had it closer before I messed around with it. Either that, or I'm misunderstanding what the TableService is. The following code did work for me, but I'm trying to switch it to using Linq instead.

List<BlogViewModel> blogs = new List<BlogViewModel>();

var storageAccount = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue("BlogConnectionString"));
var tableClient = storageAccount.CreateCloudTableClient();
CloudTable blogTable = tableClient.GetTableReference("BlogEntries");

TableRequestOptions reqOptions = new TableRequestOptions()
{
   MaximumExecutionTime = TimeSpan.FromSeconds(1.5),
   RetryPolicy = new LinearRetry(TimeSpan.FromSeconds(3), 3)
};
List<BlogEntry> lists;

try
{
   var query = new TableQuery<BlogEntry>();
   lists = blogTable.ExecuteQuery(query, reqOptions).ToList();

   foreach (BlogEntry blog in lists)
   {
      blogs.Add(new BlogViewModel { Body = blog.Body });
   }
}
catch { }

I've been unable to find a good solid example anywhere of what I should be doing. But from what I've been reading, it does suggest using Linq is possible. Any help or pointers appreciated. Thanks.


Slight update. The following is the syntax error I currently get on AsTableServiceQuery:

'System.Linq.IQueryable' does not contain a definition for 'AsTableServiceQuery' and no extension method 'AsTableServiceQuery' accepting a first argument of type 'System.Linq.IQueryable' could be found (are you missing a using directive or an assembly reference?)

However, I don't think this reflects the real issue, I think I have just got it put together wrong, just can't find a solid example anywhere that works.

回答1:

TableServiceContext is no longer needed in the new Table Service Layer of Azure Storage Client Library. For more information on this change, please see our blog post Announcing Storage Client Library 2.1 RTM & CTP for Windows Phone.

Please make sure BlogEntry implements ITableEntity and then the following code should work just fine:

List<BlogViewModel> blogs = new List<BlogViewModel>();

CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable blogTable = tableClient.GetTableReference("BlogEntries");

try
{
    IEnumerable<BlogEntry> query = (from blog in blogTable.CreateQuery<BlogEntry>()
                                    select blog);
    foreach (BlogEntry blog in query)
    {
        blogs.Add(new BlogViewModel { Body = blog.Body });
    }
}
catch { }


回答2:

My current table storage repository does this:

public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> expression) 
{
    if (IsTableEmpty())
    {
        return Enumerable.Empty<TEntity>().AsQueryable();
    }
    else
    {
        return _cloudTable.CreateQuery<TEntity>().AsQueryable().Where(expression);
    }
}

My _cloudTable corresponds to your blogTable.



回答3:

Based on previous answer, I've created extensions methods to support First, FirstOrDefault, Single and SingleOrDefault:

/// <summary>
/// Provides additional Linq support for the <see cref="TableQuery{TElement}"/> class. 
/// </summary>
public static class LinqToTableQueryExtensions
{
    /// <summary>
    /// Returns the first element in a sequence.
    /// </summary>
    /// <typeparam name="TSource">The type of the elements of source.</typeparam>
    /// <param name="tableQuery">A TableQuery{TSource} to return the first element of</param>
    public static TSource First<TSource>(this TableQuery<TSource> tableQuery) where TSource : ITableEntity
    {
        return ((IEnumerable<TSource>)tableQuery.Take(1)).First();
    }

    /// <summary>
    /// Returns the first element in a sequence that satisfies a specified condition.
    /// </summary>
    /// <typeparam name="TSource">The type of the elements of source.</typeparam>
    /// <param name="tableQuery">A TableQuery{TSource} to return the first element of</param>
    /// <param name="predicate">A function to test an element for a condition.</param>
    public static TSource First<TSource>(this TableQuery<TSource> tableQuery, Expression<Func<TSource, bool>> predicate) where TSource : ITableEntity
    {
        return tableQuery.Where(predicate).Take(1).First();
    }

    /// <summary>
    /// Returns the first element of the sequence or a default value if no such element is found.
    /// </summary>
    /// <typeparam name="TSource">The type of the elements of source.</typeparam>
    /// <param name="tableQuery">A TableQuery{TSource} to return the first element of</param>
    public static TSource FirstOrDefault<TSource>(this TableQuery<TSource> tableQuery) where TSource : ITableEntity
    {
        return ((IEnumerable<TSource>)tableQuery.Take(1)).FirstOrDefault();
    }

    /// <summary>
    /// Returns the first element of the sequence that satisfies a condition or a default value if no such element is found.
    /// </summary>
    /// <typeparam name="TSource">The type of the elements of source.</typeparam>
    /// <param name="tableQuery">A TableQuery{TSource} to return the first element of</param>
    /// <param name="predicate">A function to test an element for a condition.</param>
    public static TSource FirstOrDefault<TSource>(this TableQuery<TSource> tableQuery, Expression<Func<TSource, bool>> predicate) where TSource : ITableEntity
    {
        return tableQuery.Where(predicate).Take(1).FirstOrDefault();
    }

    /// <summary>
    /// Return the only element of a sequence, and throws an exception if there is no exactly one element in the sequence.
    /// </summary>
    /// <typeparam name="TSource">The type of the elements of source.</typeparam>
    /// <param name="tableQuery">A TableQuery{TSource}> to return the single element of</param>
    /// <param name="predicate">A function to test an element for a condition.</param>
    public static TSource Single<TSource>(this TableQuery<TSource> tableQuery, Expression<Func<TSource, bool>> predicate) where TSource : ITableEntity
    {
        // Get 2 and try to get single ^^
        return tableQuery.Where(predicate).Take(2).Single();
    }

    /// <summary>
    /// Returns the only element of a sequence, or a default value if the sequence is empty; this method throws an exception if there is more than one element in the sequence.
    /// </summary>
    /// <typeparam name="TSource">The type of the elements of source.</typeparam>
    /// <param name="tableQuery">A TableQuery{TSource}> to return the single element of</param>
    /// <param name="predicate">A function to test an element for a condition.</param>
    public static TSource SingleOrDefault<TSource>(this TableQuery<TSource> tableQuery, Expression<Func<TSource, bool>> predicate) where TSource : ITableEntity
    {
        // Get 2 and try to get single ^^
        return tableQuery.Where(predicate).Take(2).SingleOrDefault();
    }
}

So you can use it like that:

public class CustomerEntity : TableEntity { public string Email { get; set; } } 
...
var storageAccount = CloudStorageAccount.Parse( "MyStorageAccountConnectionstring");
var tableClient = storageAccount.CreateCloudTableClient();
var table = tableClient.GetTableReference("myTable");

// Linq Query with Where And First
var person = table.CreateQuery<CustomerEntity>()
    .Where(c => c.Email == "Walter1@contoso.com").First();

// Linq query that used the First() Extension method
person = table.CreateQuery<CustomerEntity>()
    .First(c => c.Email == "Walter1@contoso.com");


回答4:

Here are some handy extension methods to wrap this.. (and a bonus case for ServiceBus Custom Properties keystore)

namespace Microsoft.WindowsAzure.Storage.Table
{
    public static class CloudTableExtensions
    {
        public static TEntity GetTableEntity<TEntity>(this CloudTable cloudTable, BrokeredMessage brokeredMessage, string partitionKeyPropertyName, string rowKeyPropertyName, TableRequestOptions requestOptions = null, OperationContext operationContext = null)
            where TEntity : ITableEntity, new()
        {
            var partitionKey = brokeredMessage.Properties[partitionKeyPropertyName] as string;
            var rowKey = brokeredMessage.Properties[rowKeyPropertyName] as string;
            return GetTableEntity<TEntity>(cloudTable, partitionKey, rowKey, requestOptions, operationContext);
        }

        public static TEntity GetTableEntity<TEntity>(this CloudTable cloudTable, string partitionKey, string rowKey, TableRequestOptions requestOptions = null, OperationContext operationContext = null)
            where TEntity : ITableEntity, new()
        {
            var singleInstanceQuery = (Expression<Func<TEntity, bool>>)(x => x.PartitionKey == partitionKey && x.RowKey == rowKey);
            IEnumerable<TEntity> queryResults = cloudTable.ExecuteQuery(singleInstanceQuery, requestOptions, operationContext);
            return queryResults.SingleOrDefault();
        }

        public static IEnumerable<TEntity> ExecuteQuery<TEntity>(this CloudTable cloudTable, Expression<Func<TEntity, bool>> expression, TableRequestOptions requestOptions = null, OperationContext operationContext = null)
            where TEntity : ITableEntity, new()
        {
            var query = cloudTable.CreateQuery<TEntity>().Where(expression) as TableQuery<TEntity>;
            return cloudTable.ExecuteQuery(query, requestOptions, operationContext);
        }
    }
}