I'm trying to get a HashSet<string>
into Azure Table Storage. I want to model a project which has members:
public class DbProject : ITableEntity {
public string Name { get; set; } = default!;
public string Owner { get; set; } = default!;
public HashSet<string> Members { get; set; } = default!;
public DateTime CreatedOn { get; set; } = default!;
public string PartitionKey { get; set; } = default!;
public string RowKey { get;set; } = default!;
public DateTimeOffset? Timestamp { get;set; } = default!;
public ETag ETag { get;set; } = default!;
}
The 'insert logic' looks something like:
var dbProject = new DbProject {
Name = projectName,
Owner = owner,
CreatedOn = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc),
Members = new HashSet<string> { owner },
PartitionKey = $"{owner}__{projectName}",
RowKey = $"{owner}__{projectName}"
};
try {
this.tableClient.AddEntity<DbProject>(dbProject);
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
The error I'm getting is:
ErrorCode: InvalidInput
Content:
{"odata.error":{"code":"InvalidInput","message":{"lang":"en-US","value":"An error occurred while processing this request.\nRequestId:6019edb7-c002-001c-1be9-bd1379000000\nTime:2021-10-10T15:15:24.5069601Z"}}}
If I remove the HashSet it works like a charm, so I guess there is something wrong with using complex types when creating a record in Azure Table Storage.
I would like to run simple queries like:
public bool IsMember(string owner, string projectName) {
var query = TableClient.CreateQueryFilter<DbProject>(u => u.Name == projectName && u.Members.Contains(owner));
return tableClient.Query<DbProject>(query).Any();
}
CodePudding user response:
The reason you're running into this issue is because Azure Table Storage does not support complex data types (HashSet is one of them).
For a list of supported data types, please see this link: https://docs.microsoft.com/en-us/rest/api/storageservices/understanding-the-table-service-data-model#property-types.
What you will have to do is somehow serialize the complex data type into one of the supported data types (string
data type is the best fit) and save it. When you read the data, you will have to deserialize it back. Please note that in doing so you will lose the capability on querying on this particular attribute.
CodePudding user response:
Since you are asking for alternative solutions in your comment: The most obvious choice would be to use CosmosDB instead of Table Storage. Table storage is extremely limited in many scenarios. If you don't need to use table storage for a specific reason, using CosmosDB with SQL API is the recommended way for new projects.
As mentioned in Jeremy's comment, you can use HashSet just fine there: How to search on Cosmos DB in a complex JSON Object
If you want to keep Table Storage and don't need to do queries on your HashSet: You can serialize your HashSet to a string like Gaurav mentioned. Maybe something like this:
[IgnoreProperty]
public HashSet<string> Members { get; set; }
public override void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext)
{
Members = JsonConvert.DeserializeObject<HashSet<string>>(properties[nameof(Members)].StringValue);
}
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{
var properties = new Dictionary<string, EntityProperty>();
properties.Add(nameof(Members), new EntityProperty(JsonConvert.SerializeObject(Members)));
return properties;
}
You could also look at this: Adding Complex Properties of a TableEntity to Azure Table Storage