如何编码 Azure 存储表行键和分区键?
Posted
技术标签:
【中文标题】如何编码 Azure 存储表行键和分区键?【英文标题】:How can I encode Azure storage table row keys and partition keys? 【发布时间】:2014-02-04 08:25:47 【问题描述】:我正在使用 Azure 存储表,并且我有数据进入包含斜杠的 RowKey。根据this MSDN page,PartitionKey 和 RowKey 都不允许使用以下字符:
正斜杠 (/) 字符
反斜杠()字符
数字符号 (#) 字符
问号 (?) 字符
从 U+0000 到 U+001F 的控制字符,包括:
水平制表符 (\t) 字符
换行符(\n)
回车 (\r) 字符
从 U+007F 到 U+009F 的控制字符
我见过一些人使用 URL 编码来解决这个问题。不幸的是,这可能会导致一些故障,例如能够插入但无法删除某些实体。我还看到有些人使用 base64 编码,但这也可能包含不允许的字符。
如何有效地编码我的 RowKey 而不会遇到不允许的字符或滚动我自己的编码?
【问题讨论】:
“但无法删除某些实体”为什么会这样? @usr 这是一个错误。不知道为什么,但我已经看到了很多关于它的报告。 另见:Azure Table Storage RowKey restricted Character Patterns? 【参考方案1】:2020 年 8 月 18 日更新了(新?)Azure 搜索中的“+”字符问题。有关背景,请参见下面@mladenb 的 cmets。值得注意的是,引用的文档页面不排除“+”字符。
当 URL 采用 Base64 编码时,Azure 表存储键列中唯一无效的字符是正斜杠 ('/')。若要解决此问题,只需将正斜杠字符替换为 (1) 在 Azure 表存储键列中有效且 (2) 不是 Base64 字符的另一个字符。我发现的最常见的例子(在其他答案中被引用)是用下划线('_')替换正斜杠('/')。
private static String EncodeUrlInKey(String url)
var keyBytes = System.Text.Encoding.UTF8.GetBytes(url);
var base64 = System.Convert.ToBase64String(keyBytes);
return base64.Replace('/','_').Replace('+','-');
解码时,只需撤消替换的字符(首先!),然后 Base64 解码生成的字符串。仅此而已。
private static String DecodeUrlInKey(String encodedKey)
var base64 = encodedKey.Replace('-','+').Replace('_', '/');
byte[] bytes = System.Convert.FromBase64String(base64);
return System.Text.Encoding.UTF8.GetString(bytes);
有人建议其他 Base64 字符也需要编码。根据Azure Table Storage docs,情况并非如此。
【讨论】:
由于这个有很多观点,我只是想补充一点,只是从/到base58转换要简单得多 如果您已经在使用带有 base58 例程的库,则有效点。 OP希望避免“自己动手”,这个答案没有任何假设。如果你确实走这条路,最好记录下 base58 编码正在被使用,因为有不止一个。 我想我可能更愿意将您的 UTF8 keyBytes 变量作为 HEX 字符串转储,而不是在编码时一直使用 base64。这样,我就不必弄乱你提到的正斜杠了。 @ShawnEary - 当然,如果您不介意较长的键。 BitConverter 是一种方法。您可以在此处的答案中找到其他选项:***.com/questions/623104/byte-to-hex-string @MladenB - 感谢您的跟进。示例代码已更新。【参考方案2】:我也遇到了同样的需求。
我对 Base64 编码不满意,因为它将人类可读的字符串变成了无法识别的字符串,并且无论它们是否遵守规则都会扩大字符串的大小(当绝大多数字符不非法时,这是一种损失需要转义的字符)。
这是一个使用“!”的编码器/解码器作为转义字符,与传统上使用反斜杠字符的方式大致相同。
public static class TableKeyEncoding
// https://msdn.microsoft.com/library/azure/dd179338.aspx
//
// The following characters are not allowed in values for the PartitionKey and RowKey properties:
// The forward slash(/) character
// The backslash(\) character
// The number sign(#) character
// The question mark (?) character
// Control characters from U+0000 to U+001F, including:
// The horizontal tab(\t) character
// The linefeed(\n) character
// The carriage return (\r) character
// Control characters from U+007F to U+009F
public static string Encode(string unsafeForUseAsAKey)
StringBuilder safe = new StringBuilder();
foreach (char c in unsafeForUseAsAKey)
switch (c)
case '/':
safe.Append("!f");
break;
case '\\':
safe.Append("!b");
break;
case '#':
safe.Append("!p");
break;
case '?':
safe.Append("!q");
break;
case '\t':
safe.Append("!t");
break;
case '\n':
safe.Append("!n");
break;
case '\r':
safe.Append("!r");
break;
case '!':
safe.Append("!!");
break;
default:
if (c <= 0x1f || (c >= 0x7f && c <= 0x9f))
int charCode = c;
safe.Append("!x" + charCode.ToString("x2"));
else
safe.Append(c);
break;
return safe.ToString();
public static string Decode(string key)
StringBuilder decoded = new StringBuilder();
int i = 0;
while (i < key.Length)
char c = key[i++];
if (c != '!' || i == key.Length)
// There's no escape character ('!'), or the escape should be ignored because it's the end of the array
decoded.Append(c);
else
char escapeCode = key[i++];
switch (escapeCode)
case 'f':
decoded.Append('/');
break;
case 'b':
decoded.Append('\\');
break;
case 'p':
decoded.Append('#');
break;
case 'q':
decoded.Append('?');
break;
case 't':
decoded.Append('\t');
break;
case 'n':
decoded.Append("\n");
break;
case 'r':
decoded.Append("\r");
break;
case '!':
decoded.Append('!');
break;
case 'x':
if (i + 2 <= key.Length)
string charCodeString = key.Substring(i, 2);
int charCode;
if (int.TryParse(charCodeString, NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out charCode))
decoded.Append((char)charCode);
i += 2;
break;
default:
decoded.Append('!');
break;
return decoded.ToString();
由于在编写自己的编码器时应格外小心,因此我也为它编写了一些单元测试。
using Xunit;
namespace xUnit_Tests
public class TableKeyEncodingTests
const char Unicode0X1A = (char) 0x1a;
public void RoundTripTest(string unencoded, string encoded)
Assert.Equal(encoded, TableKeyEncoding.Encode(unencoded));
Assert.Equal(unencoded, TableKeyEncoding.Decode(encoded));
[Fact]
public void RoundTrips()
RoundTripTest("!\n", "!!!n");
RoundTripTest("left" + Unicode0X1A + "right", "left!x1aright");
// The following characters are not allowed in values for the PartitionKey and RowKey properties:
// The forward slash(/) character
// The backslash(\) character
// The number sign(#) character
// The question mark (?) character
// Control characters from U+0000 to U+001F, including:
// The horizontal tab(\t) character
// The linefeed(\n) character
// The carriage return (\r) character
// Control characters from U+007F to U+009F
[Fact]
void EncodesAllForbiddenCharacters()
List<char> forbiddenCharacters = "\\/#?\t\n\r".ToCharArray().ToList();
forbiddenCharacters.AddRange(Enumerable.Range(0x00, 1+(0x1f-0x00)).Select(i => (char)i));
forbiddenCharacters.AddRange(Enumerable.Range(0x7f, 1+(0x9f-0x7f)).Select(i => (char)i));
string allForbiddenCharacters = String.Join("", forbiddenCharacters);
string allForbiddenCharactersEncoded = TableKeyEncoding.Encode(allForbiddenCharacters);
// Make sure decoding is same as encoding
Assert.Equal(allForbiddenCharacters, TableKeyEncoding.Decode(allForbiddenCharactersEncoded));
// Ensure encoding does not contain any forbidden characters
Assert.Equal(0, allForbiddenCharacters.Count( c => allForbiddenCharactersEncoded.Contains(c) ));
【讨论】:
是的,我认为不失去键的可读性非常重要。令更多人感到惊讶的是,他们不这样做或 URLencode,然后修复任何无法使用的东西,而不是 Base64-encode 和修复,这似乎是这里的一般方法。 这里需要注意的是,Microsoft Azure Storage Explorer v0.8.3 无法通过 PartitionKey eq 查询对象,如果您有! PartitionKey 中的字符。在幕后,事情似乎工作正常,但由于某种原因,相等运算符不能正常工作。我将此报告为错误,但如果您使用此解决方案和特定字符,请记住这一点。 我怀疑这是因为自然键只在部分情况下才有意义,而可读性通常/通常不利于代理键。'\t'
、'\n'
、'\r'
的代码应该是不必要的,因为这些字符无论如何都由范围检查处理。好的,与范围的编码相比,它增加了可读性。
Decode
方法中,如果十六进制值不正确,例如当编码字符串包含!xhh
时,我们忽略所有四个字符。当escapeCode
未知时(例如!h
),我们应该有相同的行为,而不是将字符!
附加到解码的字符串。【参考方案3】:
URL 编码/解码功能怎么样。它负责处理'/'
、'?'
和'#'
字符。
string url = "http://www.google.com/search?q=Example";
string key = HttpUtility.UrlEncode(url);
string urlBack = HttpUtility.UrlDecode(key);
【讨论】:
【参考方案4】:查看这些链接 https://www.rfc-editor.org/rfc/rfc4648#page-7 Code for decoding/encoding a modified base64 URL(另见第二个答案:https://***.com/a/1789179/1094268)
我自己也遇到了问题。这些是我现在使用的我自己的函数。我使用了我提到的第二个答案中的技巧,以及更改了与可能仍然出现的 azure 键不兼容的 +
和 /
。
private static String EncodeSafeBase64(String toEncode)
if (toEncode == null)
throw new ArgumentNullException("toEncode");
String base64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(toEncode));
StringBuilder safe = new StringBuilder();
foreach (Char c in base64String)
switch (c)
case '+':
safe.Append('-');
break;
case '/':
safe.Append('_');
break;
default:
safe.Append(c);
break;
return safe.ToString();
private static String DecodeSafeBase64(String toDecode)
if (toDecode == null)
throw new ArgumentNullException("toDecode");
StringBuilder deSafe = new StringBuilder();
foreach (Char c in toDecode)
switch (c)
case '-':
deSafe.Append('+');
break;
case '_':
deSafe.Append('/');
break;
default:
deSafe.Append(c);
break;
return Encoding.UTF8.GetString(Convert.FromBase64String(deSafe.ToString()));
【讨论】:
根据 Azure 文档,“+”字符在 Azure 表键字段中不是无效的。 @JasonWeber 我最初制作这个已经有一段时间了,但我很确定我记得读过那是(或曾经是)一个未记录的异常。【参考方案5】:如果只是斜线,您可以在写入表格时简单地将它们替换为另一个字符,例如“|”并在阅读时重新替换它们。
【讨论】:
【参考方案6】:我所看到的是,虽然很多非字母数字字符在技术上是允许的,但它作为分区和行键并不能很好地工作。
我查看了这里和其他地方已经给出的答案并写了这个: https://github.com/JohanNorberg/AlphaNumeric
两个字母数字编码器。
如果你需要转义一个主要是字母数字的字符串,你可以使用这个:
AlphaNumeric.English.Encode(str);
如果您需要转义大部分不是字母数字的字符串,您可以使用:
AlphaNumeric.Data.EncodeString(str);
编码数据:
var base64 = Convert.ToBase64String(bytes);
var alphaNumericEncodedString = base64
.Replace("0", "01")
.Replace("+", "02")
.Replace("/", "03")
.Replace("=", "04");
但是,如果您想使用例如电子邮件地址作为行键,您只需要转义“@”和“.”。此代码将执行此操作:
char[] validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456789".ToCharArray();
char[] allChars = rawString.ToCharArray();
StringBuilder builder = new StringBuilder(rawString.Length * 2);
for(int i = 0; i < allChars.Length; i++)
int c = allChars[i];
if((c >= 51 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122))
builder.Append(allChars[i]);
else
int index = builder.Length;
int count = 0;
do
builder.Append(validChars[c % 59]);
c /= 59;
count++;
while (c > 0);
if (count == 1) builder.Insert(index, '0');
else if (count == 2) builder.Insert(index, '1');
else if (count == 3) builder.Insert(index, '2');
else throw new Exception("Base59 has invalid count, method must be wrong Count is: " + count);
return builder.ToString();
【讨论】:
以上是关于如何编码 Azure 存储表行键和分区键?的主要内容,如果未能解决你的问题,请参考以下文章