我正在尝试创建一个 CSV 文件,并将其导入 Azure 存储帐户。
public static void ExportCsvToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<ReportRow> reportEntries)
{
using (var ms = new MemoryStream())
{
using (var file = new StreamWriter(ms))
{
file.WriteLine("Date,StoreId,ItemId,SalesQuantity");
foreach (var row in reportEntries)
{
var line = $"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"";
file.WriteLine(line);
}
var blockBlob = container.GetBlockBlobReference($"{fileName}.csv");
ms.Position = 0;
blockBlob.UploadFromStream(ms);
}
}
}
我在内存中创建文件,然后将其复制并上传到Azure。
我的 "问题 "是,为此我需要首先将整个文件保存在内存中,然后才开始复制(如果文件太大,机器内存不足,这可能是一个问题)。
理想情况下,我可以直接写到 azure,或者一旦我填满了我的内存流缓冲区,我就会把它复制到 azure,然后再写到它上面,而不是在我的内存流缓冲区中分配更多的空间。
有没有一种方法可以直接写到Azure中去?
编辑:高拉夫-曼特丽-AIS的回答,我想出了这个办法(因为我有50000多个条目,这是块的极限)。
根据Gaurav Mantri-AIS的回答,我想到了这个(因为我有超过50000个条目,这是块的限制)。
public static void ExportCSVToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<RawReportRow> reportEntries)
{
var blob = container.GetAppendBlobReference($"{fileName}.csv");
blob.CreateOrReplace();
blob.AppendText($"Date,StoreId,ItemId,SalesQuantity{Environment.NewLine}");
foreach (var row in reportEntries)
{
var line = $"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"{Environment.NewLine}";
blob.AppendText(line);
}
}
这个解决方案的问题是需要的时间太长,从5分钟到一个多小时。我可能做错了什么,因为AppendBlob应该能很好地进行追加,但似乎不是这样。
有什么办法可以提高写入速度吗?
.当然可以这样做。一个解决办法是使用 StringBuilder
并不断向其中添加数据。当所有数据添加完毕后,创建一个字节数组,然后从中创建一个内存流,并上传该内存流。
下面是示例代码(虽然未经测试)。
public static void ExportCsvToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<ReportRow> reportEntries)
{
using (var ms = new MemoryStream())
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Date,StoreId,ItemId,SalesQuantity");
foreach (var row in reportEntries)
{
var line = $"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"";
sb.AppendLine(line);
}
var buffer = Encoding.UTF8.GetBytes(sb.ToString());
ms.Write(buffer, 0, buffer.Length);
var blockBlob = container.GetBlockBlobReference($"{fileName}.csv");
ms.Position = 0;
blockBlob.UploadFromStream(ms);
}
}
UPDATE
假设你使用的是SDK 9.3.3版本,你可以使用 UploadText
方法,并直接将该字符串上传到Azure存储中。类似于
public static void ExportCsvToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<string> reportEntries)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Date,StoreId,ItemId,SalesQuantity");
foreach (var row in reportEntries)
{
var line = $"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"";
sb.AppendLine(line);
}
var blockBlob = container.GetBlockBlobReference($"{fileName}.csv");
blockBlob.UploadText(sb.ToString());
}
UPDATE 2
然而另一种选择是将每一行作为一个单独的块上传,然后最后提交块列表。但是请记住,一个blob中只能有50000个块,如果你的数据中记录超过50000条,这种方法就会失败。为了规避这个限制,你可能想把某些记录合并起来,然后保存为一个块。
下面是示例代码。
public static void ExportCsvToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<string> reportEntries)
{
List<string> blockIds = new List<string>();
CloudBlockBlob blob = container.GetBlockBlobReference(fileName);
int counter = 0;
foreach (var row in reportEntries)
{
var line = $"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"";
var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(counter.ToString("d6")));
blob.PutBlock(blockId, new MemoryStream(Encoding.UTF8.GetBytes(line)), string.Empty);
blockIds.Add(blockId);
counter++;
}
blob.PutBlockList(blockIds);
}
我打算试一试,主要是基于: Gaurav Mantri-AIS的答复. 因为我觉得你们是在做什么。
让我们一起努力... 一方面,你们希望尽快写到Blob,以限制内存使用。另一方面,我们又不想写到 每行 因为这超过了块的限制。所以我们需要有一个 X数量的记录 的内存中,然后再写入到blob中。
我在这里尝试了一些伪代码与 X 值为50。我认为这个值可以(也应该)针对内存使用、性能和块数进行优化。
public static void ExportCsvToStorageAccount(string fileName, CloudBlobContainer container, IEnumerable<string> reportEntries)
{
List<string> blockIds = new List<string>();
CloudBlockBlob blob = container.GetBlockBlobReference(fileName);
int counter = 0;
StringBuilder builder = new StringBuilder();
foreach (var row in reportEntries)
{
builder.Append($"\"{row.Date}\",\"{row.StoreId}\",\"{row.ItemId}\",\"{row.SalesQuantity}\"");
counter++;
if (counter % 50 == 0)
{
var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(counter.ToString("d6")));
blob.PutBlock(blockId, new MemoryStream(Encoding.UTF8.GetBytes(line)), string.Empty);
builder = new StringBuilder();
blockIds.Add(blockId);
}
}
// Check if there's anything still in the String Builder and write it
if (builder.Length != 0)
{
var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(counter.ToString("d6")));
blob.PutBlock(blockId, new MemoryStream(Encoding.UTF8.GetBytes(line)), string.Empty);
}
blob.PutBlockList(blockIds);
}
另一个需要考虑的问题是,存储的 Azure Function 绑定使你能够将一个 blob 绑定到一个新的存储区。Stream
. 这让我有两件事需要思考。
EDIT: 我查了一下资料来源 azure-webjobs-sdk
并发现它使用了 CloudBlobStream
. 尽管它被标记为遗产,但你仍然可以得到一个。CloudBlobStream
呼叫 OpenWriteAsync
关于 CloudBlockBlob
. 我没有时间去测试一个例子,但我。做了 在SO上找到这个例子。在飞行中向Azure Blob上传文件.
public async Task<Stream> GetWriteStreamAsync(string storagePath, string contentType)
{
var blockBlob = blobContainer.GetBlockBlobReference(storagePath);
blockBlob.Properties.ContentType = contentType;
CloudBlobStream bb = await blockBlob.OpenWriteAsync();
return bb;
}