使用 .NET 管理 Azure Data Lake Storage 中的目录和文件

本文将向您展示如何使用 .NET 在具有层级命名空间的存储账户(Azure Data Lake Storage Gen2)中创建和管理目录和文件。

要了解如何获取、设置和更新目录和文件的访问控制列表 (ACL),请参阅使用 .NET 管理 Azure Data Lake Storage 中的 ACL

包 (NuGet) | 示例 | API 参考 | 提供反馈

先决条件

设置项目

若要开始,请安装 Azure.Storage.Files.DataLake NuGet 包。

有关如何安装 NuGet 包的详细信息,请参阅使用 NuGet 包管理器在 Visual Studio 中安装和管理包

然后,把这些 using 语句添加到代码文件顶部。

using Azure;
using Azure.Storage.Files.DataLake;
using Azure.Storage.Files.DataLake.Models;
using Azure.Storage;
using System.IO;

注意

Data Lake Storage 上的多协议访问使应用程序能够同时使用 Blob API 和 Data Lake Storage Gen2 API 来处理启用了分层命名空间 (HNS) 的存储帐户中的数据。 使用 Data Lake Storage Gen2 特有的功能(如目录操作和 ACL)时,请使用 Data Lake Storage Gen2 API,如本文所示。

选择要在给定场景中使用的 API 时,请考虑应用程序的工作负载和需求,以及已知问题HNS 对工作负载和应用程序造成的影响

授权访问并连接到数据资源

若要使用本文中的代码示例,需创建一个表示存储帐户的授权 DataLakeServiceClient 实例。 可以使用Microsoft Entra ID、帐户访问密钥或共享访问签名(SAS)来授权DataLakeServiceClient对象。

使用Azure身份客户端库(针对.NET)通过Microsoft Entra ID认证您的应用程序。

创建 DataLakeServiceClient 实例并传入 DefaultAzureCredential 类的新实例。

public static DataLakeServiceClient GetDataLakeServiceClient(string accountName)
{
    string dfsUri = $"https://{accountName}.dfs.core.chinacloudapi.cn";

    DataLakeServiceClient dataLakeServiceClient = new DataLakeServiceClient(
        new Uri(dfsUri),
        new DefaultAzureCredential());

    return dataLakeServiceClient;
}

要详细了解如何使用 DefaultAzureCredential 授权访问数据,请参阅 如何使用 Azure 服务对 .NET 应用程序进行身份验证

创建容器

容器充当文件的文件系统。 可以使用以下方法创建容器:

以下代码示例会创建一个容器,并返回 DataLakeFileSystemClient 对象供以后使用:

public async Task<DataLakeFileSystemClient> CreateFileSystem(
    DataLakeServiceClient serviceClient,
    string fileSystemName)
{
    return await serviceClient.CreateFileSystemAsync(fileSystemName);
}

操作完成后,容器作为文件系统在存储账户中可用。

创建目录

可以使用以下方法在容器中创建目录引用:

以下代码示例是先向容器添加一个目录,然后添加子目录,并返回一个 DataLakeDirectoryClient 对象供后续使用:

public async Task<DataLakeDirectoryClient> CreateDirectory(
    DataLakeFileSystemClient fileSystemClient,
    string directoryName,
    string subdirectoryName)
{
    DataLakeDirectoryClient directoryClient =
        await fileSystemClient.CreateDirectoryAsync(directoryName);

    return await directoryClient.CreateSubDirectoryAsync(subdirectoryName);
}

重命名或移动目录

可以使用以下方法重命名或移动目录:

使用你之前创建的 DataLakeDirectoryClient,将目录的路径作为参数传递。 以下代码示例说明了如何重命名子目录:

public async Task<DataLakeDirectoryClient> RenameDirectory(
    DataLakeFileSystemClient fileSystemClient,
    string directoryPath,
    string subdirectoryName,
    string subdirectoryNameNew)
{
    DataLakeDirectoryClient directoryClient =
        fileSystemClient.GetDirectoryClient(string.Join('/', directoryPath, subdirectoryName));

    return await directoryClient.RenameAsync(string.Join('/', directoryPath, subdirectoryNameNew));
}

下面的代码示例演示如何将子目录从一个目录移动到另一个目录:

public async Task<DataLakeDirectoryClient> MoveDirectory(
    DataLakeFileSystemClient fileSystemClient,
    string directoryPathFrom,
    string directoryPathTo,
    string subdirectoryName)
{
    DataLakeDirectoryClient directoryClient =
         fileSystemClient.GetDirectoryClient(string.Join('/', directoryPathFrom, subdirectoryName));

    return await directoryClient.RenameAsync(string.Join('/', directoryPathTo, subdirectoryName));
}

将文件上传到目录

可以使用以下方法将内容上传到新的或现有的文件:

以下代码示例使用之前创建的 DataLakeDirectoryClient 通过 UploadAsync 方法将本地文件上传到目录:

public async Task UploadFile(
    DataLakeDirectoryClient directoryClient,
    string fileName,
    string localPath)
{
    DataLakeFileClient fileClient = 
        directoryClient.GetFileClient(fileName);

    FileStream fileStream = File.OpenRead(localPath);

    DataLakeFileUploadOptions uploadOptions = new()
    {
        // If change notifications are enabled, set Close to true
        // This value indicates the final close of the file stream
        // And emits a change notification event upon successful flush

        // Close = true
    };

    await fileClient.UploadAsync(content: fileStream, options: uploadOptions);
}

上传完成后,文件会以指定名称出现在目标目录中。

将数据追加到文件

若要将数据追加到文件中,请使用以下方法:

下面的代码示例演示如何使用以下步骤将数据追加到文件末尾:

public async Task AppendDataToFile(
    DataLakeDirectoryClient directoryClient,
    string fileName,
    Stream stream)
{
    DataLakeFileClient fileClient = 
        directoryClient.GetFileClient(fileName);

    long fileSize = fileClient.GetProperties().Value.ContentLength;

    await fileClient.AppendAsync(stream, offset: fileSize);

    DataLakeFileFlushOptions flushOptions = new()
    {
        // If change notifications are enabled, set Close to true
        // This value indicates the final close of the file stream
        // And emits a change notification event upon successful flush

        // Close = true
    };

    await fileClient.FlushAsync(position: fileSize + stream.Length, options: flushOptions);
}

从目录下载

下面的代码示例演示如何使用以下步骤将文件从目录下载到本地文件:

此示例使用 BinaryReaderFileStream 将字节保存到文件。

public async Task DownloadFile(
    DataLakeDirectoryClient directoryClient,
    string fileName,
    string localPath)
{
    DataLakeFileClient fileClient =
        directoryClient.GetFileClient(fileName);

    Response<DataLakeFileReadStreamingResult> downloadResponse = await fileClient.ReadStreamingAsync();

    BinaryReader reader = new BinaryReader(downloadResponse.Value.Content);

    FileStream fileStream = File.OpenWrite(localPath);

    int bufferSize = 4096;

    byte[] buffer = new byte[bufferSize];

    int count;

    while ((count = reader.Read(buffer, 0, buffer.Length)) != 0)
    {
        fileStream.Write(buffer, 0, count);
    }

    await fileStream.FlushAsync();

    fileStream.Close();
}

列出目录内容

可以通过使用以下方法并枚举结果来列出目录内容:

枚举结果中的各个路径时,可能会在获取值的过程中向该服务发出多个请求。

以下代码示例打印了位于目录中的每个文件名称:

public async Task ListFilesInDirectory(
    DataLakeFileSystemClient fileSystemClient,
    string directoryName)
{
    IAsyncEnumerator<PathItem> enumerator =
        fileSystemClient.GetPathsAsync(directoryName).GetAsyncEnumerator();

    await enumerator.MoveNextAsync();

    PathItem item = enumerator.Current;

    while (item != null)
    {
        Console.WriteLine(item.Name);

        if (!await enumerator.MoveNextAsync())
        {
            break;
        }

        item = enumerator.Current;
    }

}

删除目录

可以使用以下方法删除目录:

以下代码示例说明了如何删除目录:

public async Task DeleteDirectory(
    DataLakeFileSystemClient fileSystemClient,
    string directoryName)
{
    DataLakeDirectoryClient directoryClient =
        fileSystemClient.GetDirectoryClient(directoryName);

    await directoryClient.DeleteAsync();
}

还原软删除的目录

可以使用 Azure 存储客户端库来还原软删除的目录。 使用以下方法列出 DataLakeFileSystemClient 实例的已删除路径:

使用以下方法还原软删除的目录:

下面的代码示例演示如何列出已删除的路径和还原软删除的目录:

public async Task RestoreDirectory(
    DataLakeFileSystemClient fileSystemClient,
    string directoryName)
{
    DataLakeDirectoryClient directoryClient =
        fileSystemClient.GetDirectoryClient(directoryName);

    // List deleted paths
    List<PathDeletedItem> deletedItems = new List<PathDeletedItem>();
    await foreach (PathDeletedItem deletedItem in fileSystemClient.GetDeletedPathsAsync(directoryName))
    {
        deletedItems.Add(deletedItem);
    }

    // Restore deleted directory
    Response<DataLakePathClient> restoreResponse = await fileSystemClient.UndeletePathAsync(
        deletedItems[0].Path,
        deletedItems[0].DeletionId);
}

操作完成后,恢复后的目录及其路径会返回到原始位置。

如果你重命名了包含已软删除项的目录,则这些项会与目录断开连接。 如果你想恢复这些项目,你需要将目录名恢复为原始名称,或者创建一个使用原始目录名的独立目录。 否则,在尝试还原这些软删除项时,会出现错误。

为目录创建用户委派 SAS

若要使用本节中的代码示例,请添加以下 using 指令:

using Azure.Storage.Sas;

以下代码示例演示如何在对存储帐户启用分层命名空间时为目录生成用户委派 SAS:

async static Task<Uri> GetUserDelegationSasDirectory(DataLakeDirectoryClient directoryClient)
{
    try
    {
        // Get service endpoint from the directory URI.
        DataLakeUriBuilder dataLakeServiceUri = new DataLakeUriBuilder(directoryClient.Uri)
        {
            FileSystemName = null,
            DirectoryOrFilePath = null
        };

        // Get service client.
        DataLakeServiceClient dataLakeServiceClient =
            new DataLakeServiceClient(dataLakeServiceUri.ToUri(),
                                      new DefaultAzureCredential());

        // Get a user delegation key that's valid for seven days.
        // You can use the key to generate any number of shared access signatures 
        // over the lifetime of the key.
        Azure.Storage.Files.DataLake.Models.UserDelegationKey userDelegationKey =
            await dataLakeServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow,
                                                                  DateTimeOffset.UtcNow.AddDays(7));

        // Create a SAS token that's valid for seven days.
        DataLakeSasBuilder sasBuilder = new DataLakeSasBuilder()
        {
            // Specify the file system name and path, and indicate that
            // the client object points to a directory.
            FileSystemName = directoryClient.FileSystemName,
            Resource = "d",
            IsDirectory = true,
            Path = directoryClient.Path,
            ExpiresOn = DateTimeOffset.UtcNow.AddDays(7)
        };

        // Specify racwl permissions for the SAS.
        sasBuilder.SetPermissions(
            DataLakeSasPermissions.Read |
            DataLakeSasPermissions.Add |
            DataLakeSasPermissions.Create |
            DataLakeSasPermissions.Write |
            DataLakeSasPermissions.List
            );

        // Construct the full URI, including the SAS token.
        DataLakeUriBuilder fullUri = new DataLakeUriBuilder(directoryClient.Uri)
        {
            Sas = sasBuilder.ToSasQueryParameters(userDelegationKey,
                                                  dataLakeServiceClient.AccountName)
        };

        Console.WriteLine("Directory user delegation SAS URI: {0}", fullUri);
        Console.WriteLine();
        return fullUri.ToUri();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
        throw;
    }
}

下面的示例通过模拟的客户端应用程序测试在上一个示例中创建的用户委托 SAS。 如果SAS有效,客户端应用程序可以列出该目录的文件路径。 如果 SAS 无效(例如 SAS 过期),则存储服务将返回错误代码 403(“已禁止”)。

private static async Task ListFilesPathsWithDirectorySasAsync(Uri sasUri)
{
    // Try performing an operation using the directory SAS provided.

    // Create a directory client object for listing operations.
    DataLakeDirectoryClient dataLakeDirectoryClient = new DataLakeDirectoryClient(sasUri);

    // List file paths in the directory.
    try
    {
        // Call the listing operation and return pages of the specified size.
        var resultSegment = dataLakeDirectoryClient.GetPathsAsync(false, false).AsPages();

        // Enumerate the file paths returned with each page.
        await foreach (Page<PathItem> pathPage in resultSegment)
        {
            foreach (PathItem pathItem in pathPage.Values)
            {
                Console.WriteLine("File name: {0}", pathItem.Name);
            }
            Console.WriteLine();
        }

        Console.WriteLine();
        Console.WriteLine("Directory listing operation succeeded for SAS {0}", sasUri);
    }
    catch (RequestFailedException e)
    {
        // Check for a 403 (Forbidden) error. If the SAS is invalid, 
        // Azure Storage returns this error.
        if (e.Status == 403)
        {
            Console.WriteLine("Directory listing operation failed for SAS {0}", sasUri);
            Console.WriteLine("Additional error information: " + e.Message);
            Console.WriteLine();
        }
        else
        {
            Console.WriteLine(e.Message);
            Console.ReadLine();
            throw;
        }
    }
}

若要了解有关创建用户委派 SAS 的详细信息,请参阅使用 .NET 创建用户委派 SAS

为目录创建服务 SAS

在启用了分层命名空间的存储帐户中,可以为目录创建服务 SAS。 若要创建服务 SAS,请确保安装版本为 12.5.0 或更高版本的 Azure.Storage.Files.DataLake 包。

下面的示例演示如何为目录创建服务 SAS:

private static Uri GetServiceSasUriForDirectory(DataLakeDirectoryClient directoryClient,
                                          string storedPolicyName = null)
{
    if (directoryClient.CanGenerateSasUri)
    {
        // Create a SAS token that's valid for one hour.
        DataLakeSasBuilder sasBuilder = new DataLakeSasBuilder()
        {
            // Specify the file system name, the path, and indicate that
            // the client object points to a directory.
            FileSystemName = directoryClient.FileSystemName,
            Resource = "d",
            IsDirectory = true,
            Path = directoryClient.Path,
        };

        // If no stored access policy is specified, create the policy
        // by specifying expiry and permissions.
        if (storedPolicyName == null)
        {
            sasBuilder.ExpiresOn = DateTimeOffset.UtcNow.AddHours(1);
            sasBuilder.SetPermissions(DataLakeSasPermissions.Read |
                DataLakeSasPermissions.Write |
                DataLakeSasPermissions.List);
        }
        else
        {
            sasBuilder.Identifier = storedPolicyName;
        }

        // Get the SAS URI for the specified directory.
        Uri sasUri = directoryClient.GenerateSasUri(sasBuilder);
        Console.WriteLine("SAS URI for ADLS directory is: {0}", sasUri);
        Console.WriteLine();

        return sasUri;
    }
    else
    {
        Console.WriteLine(@"DataLakeDirectoryClient must be authorized with Shared Key 
                          credentials to create a service SAS.");
        return null;
    }
}

若要了解有关创建服务 SAS 的详细信息,请参阅使用 .NET 创建服务 SAS

另请参阅