在Azure Storage Client 2.0中将一个Azure Blob复制到另一个Blob

42

在旧的1.7存储客户端中,有一个CloudBlob.CopyFromBlob(otherBlob)方法,但在2.0版本中似乎不存在。复制blob的推荐最佳实践是什么?我看到了ICloudBlob.BeginStartCopyFromBlob方法。如果那是合适的方法,我该如何使用它?

8个回答

55

Gaurav Mantri在Azure Storage 2.0上写了一系列文章。我从他的博客文章 - 存储客户端库 2.0 - 迁移 Blob 存储代码中提取了此代码片段,用于 Blob 复制。

CloudStorageAccount storageAccount = new CloudStorageAccount(new StorageCredentials(accountName, accountKey), true);
CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer sourceContainer = cloudBlobClient.GetContainerReference(containerName);
CloudBlobContainer targetContainer = cloudBlobClient.GetContainerReference(targetContainerName);
string blobName = "<Blob Name e.g. myblob.txt>";
CloudBlockBlob sourceBlob = sourceContainer.GetBlockBlobReference(blobName);
CloudBlockBlob targetBlob = targetContainer.GetBlockBlobReference(blobName);
targetBlob.StartCopyFromBlob(sourceBlob);

我正在尝试使用此方法在同一存储帐户内复制 blob,但是我收到了 409 冲突错误。消息是“blob 类型无效”。我尝试将其视为页面 blob 和块 blob(我很确定它是页面 blob)。 - Matthew Steeples
问题解决了,原来源是一个页面块,但是目标已经在之前失败的尝试中创建为块块。删除目标并再次尝试即可解决问题。 - Matthew Steeples
6
这是服务器端复制吗? - yonisha

35

使用 Storage 6.3(比原问题中的库要新得多)和异步方法,使用 StartCopyAsync (MSDN)

  CloudStorageAccount storageAccount = CloudStorageAccount.Parse("Your Connection");

  CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
  CloudBlobContainer container = blobClient.GetContainerReference("YourContainer");

  CloudBlockBlob source = container.GetBlockBlobReference("Your Blob");
  CloudBlockBlob target = container.GetBlockBlobReference("Your New Blob");
        
  await target.StartCopyAsync(source);

19

最新版本(7.x)的SDK中有关的内容FYI,此方法已不再适用,因为函数BeginStartCopyBlob已不存在。

您可以尝试以下方式:

// this tunnels the data via your program,
// so it reuploads the blob instead of copying it on service side
using (var stream = await sourceBlob.OpenReadAsync())
{
  await destinationBlob.UploadFromStreamAsync(stream);
}

正如@(Alexey Shcherbak)所提到的那样,这是一个更好的操作方式:

await targetCloudBlob.StartCopyAsync(sourceCloudBlob.Uri);

while (targetCloudBlob.CopyState.Status == CopyStatus.Pending)
{
    await Task.Delay(500);
    // Need to fetch or "CopyState" will never update
    await targetCloudBlob.FetchAttributesAsync(); 
}

if (targetCloudBlob.CopyState.Status != CopyStatus.Success)
{
    throw new Exception("Copy failed: " + targetCloudBlob.CopyState.Status);
}

2
这将重新上传 blob。正如 Aaron Sherman 所提到的 - 使用较新的库应该使用 StartCopyAsync。 - Alexey Shcherbak
如果您可以内联添加Vangaorth的FetchAttributes()修改可能会很有用 - 在经过相当多的调试后我才看到。 - Justin Wignall
自Windows Azure Storage 8.0开始,您需要不断获取属性(targetCloudBlob.FetchAttributes())。上面的片段将因为超时而失败(无限循环)。 - bladekp
需要等待targetCloudBlob.StartCopyAsync(sourceCloudBlob.Uri)完成。 - Zhaoxing Lu

18

从Azure Storage 8开始,要在存储账户之间移动Blob,我使用类似下面的代码,希望能帮到某些人:

//copy blobs - from
CloudStorageAccount sourceStorageAccount = new CloudStorageAccount(new StorageCredentials(storageFromName, storageFromKey), true);
CloudBlobClient sourceCloudBlobClient = sourceStorageAccount.CreateCloudBlobClient();
CloudBlobContainer sourceContainer = sourceCloudBlobClient.GetContainerReference(containerFromName);

//copy blobs - to
CloudStorageAccount targetStorageAccount = new CloudStorageAccount(new StorageCredentials(storageToName, storageToKey), true);
CloudBlobClient targetCloudBlobClient = targetStorageAccount.CreateCloudBlobClient();
CloudBlobContainer targetContainer = targetCloudBlobClient.GetContainerReference(containerToName);

//create target container if didn't exists
try{
    await targetContainer.CreateIfNotExistsAsync();
}
catch(Exception e){
    log.Error(e.Message);
}

CloudBlockBlob sourceBlob = sourceContainer.GetBlockBlobReference(blobName);
CloudBlockBlob targetBlob = targetContainer.GetBlockBlobReference(blobName);

try{
    //initialize copying
    await targetBlob.StartCopyAsync(sourceBlob.Uri);
}
catch(Exception ex){
    log.Error(ex.Message);
    //return error, in my case HTTP
    return req.CreateResponse(HttpStatusCode.BadRequest, "Error, source BLOB probably has private access only: " +ex.Message);
} 

//fetch current attributes
targetBlob.FetchAttributes();

//waiting for completion
while (targetBlob.CopyState.Status == CopyStatus.Pending){
    log.Info("Status: " + targetBlob.CopyState.Status);
    Thread.Sleep(500);
    targetBlob.FetchAttributes();
}

//check status
if (targetBlob.CopyState.Status != CopyStatus.Success){
    //return error, in my case HTTP
    return req.CreateResponse(HttpStatusCode.BadRequest, "Copy failed with status: " + targetBlob.CopyState.Status);
}

//finally remove source in case Copy Status was Success
sourceBlob.Delete();

//and return success (in my case HTTP)
return req.CreateResponse(HttpStatusCode.OK, "Done.");

11

Naveen已经解释了正确使用StartCopyFromBlob(同步方法)的语法。您提到的方法(BeginStartCopyFromBlob)是可与Task结合使用的异步替代方法,例如:

    var blobClient = account.CreateCloudBlobClient();

    // Upload picture.
    var picturesContainer = blobClient.GetContainerReference("pictures");
    picturesContainer.CreateIfNotExists();
    var myPictureBlob = picturesContainer.GetBlockBlobReference("me.png");
    using (var fs = new FileStream(@"C:\Users\Public\Pictures\Sample Pictures\Chrysanthemum.jpg", FileMode.Open))
        myPictureBlob.UploadFromStream(fs);

    // Backup picture.
    var backupContainer = blobClient.GetContainerReference("backup");
    backupContainer.CreateIfNotExists();
    var backupBlob = picturesContainer.GetBlockBlobReference("me.png");

    var task = Task.Factory.FromAsync<string>(backupBlob.BeginStartCopyFromBlob(myPictureBlob, null, null), backupBlob.EndStartCopyFromBlob);
    task.ContinueWith((t) =>
    {
        if (!t.IsFaulted)
        {
            while (true)
            {
                Console.WriteLine("Copy state for {0}: {1}", backupBlob.Uri, backupBlob.CopyState.Status);
                Thread.Sleep(500);
            }
        }
        else
        {
            Console.WriteLine("Error: " + t.Exception);
        }
    });

3
StartCopyFromBlob也是一个异步操作,因为复制Blob操作是异步的 - J punto Marcos

11

看起来自从之前的文章发布以来,该API可能已有所清理。

// _client is a BlobServiceClient injected via DI in the constructor.

BlobContainerClient sourceContainerClient = _client.GetBlobContainerClient(sourceContainerName);
BlobClient sourceClient = sourceContainerClient.GetBlobClient(blobName);

BlobContainerClient destContainerClient = _client.GetBlobContainerClient(destContainerName);
BlobClient destClient = destContainerClient.GetBlobClient(blobName);

// assume that if the following doesn't throw an exception, then it is successful.
CopyFromUriOperation operation = await destClient.StartCopyFromUriAsync(sourceClient.Uri);
await operation.WaitForCompletionAsync();

operation.WaitForCompletionAsync的文档说明:

定期调用服务器,直到长时间运行的操作完成。该方法将周期性地调用UpdateStatusAsync直到HasCompleted为true,然后返回操作的最终结果。

查看此方法的源代码似乎调用了BlobBaseClient.GetProperties(或其异步版本),如果出现错误将抛出RequestFailureException异常。


谢谢。这对于确保文件实时复制非常有效。 这很棒 WaitForCompletionAsync(); - Lucky Lindy

11

对于我来说,WindowsAzure.Storage 8.0.1版本和James Hancock的解决方案在服务器端完成了复制,但客户端复制状态一直停留在Pending(无限循环)。 解决方案是在Thread.sleep(500)后在targetCloudBlob上调用FetchAttributes()

// Aaron Sherman's code 

targetCloudBlob.StartCopy(sourceCloudBlob.Uri);

while (targetCloudBlob.CopyState.Status == CopyStatus.Pending)
{
    Thread.Sleep(500);
    targetCloudBlob.FetchAttributes();
}

// James Hancock's remaining code

微软官方文档(异步示例)


2

这是我的简短简单回答。

public void Copy(CloudBlockBlob srcBlob, CloudBlobContainer destContainer)
{
    CloudBlockBlob destBlob;

    if (srcBlob == null)
    {
        throw new Exception("Source blob cannot be null.");
    }

    if (!destContainer.Exists())
    {
        throw new Exception("Destination container does not exist.");
    }

    //Copy source blob to destination container
    string name = srcBlob.Uri.Segments.Last();
    destBlob = destContainer.GetBlockBlobReference(name);
    destBlob.StartCopyAsync(srcBlob);                
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接