【问题标题】:Code First & Identity with Azure Table Storage代码优先和身份与 Azure 表存储
【发布时间】:2013-10-17 20:45:36
【问题描述】:

我正在开发一个小型 Web 应用程序,并且我刚刚达到了需要开始制定数据库决策的开发阶段。我最初的计划是在 Azure 上使用 MSSQL 使用 EF Code First,因为它只是简化了使用数据库的过程。然而,在调查我在 Azure 上的数据库托管功能时,我发现了 Azure 表存储,它为我打开了 NoSQL 的世界。

虽然 Internet 上充斥着关于 NoSQL 特性的讨论,但我设法收集到的最大原因之一是 NoSQL 将整个对象作为一个对象存储在数据库中,而不会将数据分解为有利于性能的各种表.虽然这听起来很吸引人,但 EF Code First 通过自动将对象组合在一起并将对象分离到 SQL 数据库中有效地消除了这个问题,而开发人员无需担心查询。

然而,我的主要问题是我找不到任何文档来使用 EF Code First 和 ASP.NET Identity 等 NoSQL 数据库。由于我的应用目前使用 Identity,因此我希望避免切换到其他应用。

问:是否可以在 Azure 表中使用 Code First 和/或 Identity?


编辑:关于我的应用程序的一点点 作为一个极端的简化,我的应用程序允许我的用户通过混合和匹配预配置的数据类型来创建自定义配置文件。例如,用户可以将任意数量的引用对象添加到他们的个人资料中,然后定义引用的值(即“做你自己;其他人都已经被占用了。”)。或者他们可以使用 Movie 对象来定义他们最喜欢的电影的集合(即“Title: Inception, Year: 2010”)。平均而言,用户可以在其页面上轻松拥有 50 个或更多此类属性;他们可以拥有的属性数量没有限制。

使用此示例,我可以轻松了解如何使用 Code First 来实现它(Profile 有一个 Quote 对象列表和一个 Movie 对象列表)。我还不确定这将如何映射到 Azure Tables 等 NoSQL 数据库。因此,根据我的应用程序的需求,我不确定从 Code First 切换到 NoSQL 是否是一个合理的决定,因为我会失去一些特性和功能。

【问题讨论】:

  • EF 是一个对象 relational 映射器。但为什么不同时使用呢?
  • 我很喜欢,但由于我找不到任何有关如何将 EF 与表一起使用的文档,我目前正在考虑将 EF/Identity 与 SQL 一起用于我的身份验证和表存储用于我的数据,但我目前不确定这种增加的复杂性是否值得。

标签: entity-framework azure ef-code-first azure-table-storage asp.net-identity


【解决方案1】:

因此,我们将有一个完全针对此场景的示例,使用 AzureTable 存储作为 UserStore 的无 sql 实现。基本上,您使用 Azure 存储 API 实现 IUserStore。这是实现登录/密码方法的基本实现,但不是全部:

public class AzureRole : TableEntity, IRole {
    public string Id { get; set; }
    public string Name { get; set; }
}

public class AzureLogin : TableEntity {
    public AzureLogin() {
        PartitionKey = Constants.IdentityPartitionKey;
        RowKey = Guid.NewGuid().ToString();
    }

    public AzureLogin(string ownerId, UserLoginInfo info) : this() {
        UserId = ownerId;
        LoginProvider = info.LoginProvider;
        ProviderKey = info.ProviderKey;
    }

    public string UserId { get; set; }
    public string ProviderKey { get; set; }
    public string LoginProvider { get; set; }
}

public class AzureUser : TableEntity, IUser {
    public AzureUser() {
        PartitionKey = Constants.IdentityPartitionKey;
        RowKey = Guid.NewGuid().ToString();
        Id = RowKey;
        Roles = new List<string>();
        Claims = new List<Claim>();
        Logins = new List<AzureLogin>();
    }

    public AzureUser(string userName) : this() {
        UserName = userName;
    }

    public string Id { get; set; }
    public string UserName { get; set; }
    public string PasswordHash { get; set; }
    public string SecurityStamp { get; set; }
    public IList<string> Roles { get; set; }
    public IList<AzureLogin> Logins { get; set; }
    public IList<Claim> Claims { get; set; }
}

public static class Constants {
    public const string IdentityPartitionKey = "ASP.NET Identity";
}

public class AzureStore : IUserStore<AzureUser>, IUserClaimStore<AzureUser>, IUserLoginStore<AzureUser>, IUserRoleStore<AzureUser>, IUserPasswordStore<AzureUser> {
    public AzureStore() {
        // Retrieve the storage account from the connection string.
        CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));

        // CreateAsync the table client.
        CloudTableClient tableClient = storageAccount.CreateCloudTableClient();

        // CreateAsync the table if it doesn't exist.
        CloudTable table = tableClient.GetTableReference("Identity");
        table.CreateIfNotExists();
        Table = table;

        BatchOperation = new TableBatchOperation();
    }

    public TableBatchOperation BatchOperation { get; set; }
    public CloudTable Table { get; set; }

    public void Dispose() {
    }

    public Task<IList<Claim>> GetClaimsAsync(AzureUser user) {
        return Task.FromResult(user.Claims);
    }

    public Task AddClaimAsync(AzureUser user, System.Security.Claims.Claim claim) {
        return Task.FromResult(0);
    }

    public Task RemoveClaimAsync(AzureUser user, System.Security.Claims.Claim claim) {
        return Task.FromResult(0);
    }

    Task IUserStore<AzureUser>.CreateAsync(AzureUser user) {
        TableOperation op = TableOperation.Insert(user);
        var result = Table.Execute(op);
        return Task.FromResult(0);
    }

    Task IUserStore<AzureUser>.UpdateAsync(AzureUser user) {
        TableOperation op = TableOperation.Replace(user);
        var result = Table.Execute(op);
        return Task.FromResult(0);
    }

    public Task<AzureUser> FindByIdAsync(string userId) {
        TableOperation op = TableOperation.Retrieve<AzureUser>(Constants.IdentityPartitionKey, userId);
        var result = Table.Execute(op);
        return Task.FromResult<AzureUser>(result.Result as AzureUser);
    }

    public Task<AzureUser> FindByNameAsync(string userName) {
        TableQuery<AzureUser> query = new TableQuery<AzureUser>().Where(TableQuery.GenerateFilterCondition("UserName", QueryComparisons.Equal, userName));
        return Task.FromResult(Table.ExecuteQuery(query).FirstOrDefault());
    }

    public Task AddLoginAsync(AzureUser user, UserLoginInfo login) {
        TableOperation op = TableOperation.Insert(new AzureLogin(user.Id, login));
        var result = Table.Execute(op);
        return Task.FromResult(0);
    }

    public Task RemoveLoginAsync(AzureUser user, UserLoginInfo login) {
        var al = Find(login);
        if (al != null) {
            TableOperation op = TableOperation.Delete(al);
            var result = Table.Execute(op);
        }
        return Task.FromResult(0);
    }

    public Task<IList<UserLoginInfo>> GetLoginsAsync(AzureUser user) {
        TableQuery<AzureLogin> query = new TableQuery<AzureLogin>()
            .Where(TableQuery.GenerateFilterCondition("UserId", QueryComparisons.Equal, user.Id))
            .Select(new string[] { "LoginProvider", "ProviderKey" });
        var results = Table.ExecuteQuery(query);
        IList<UserLoginInfo> logins = new List<UserLoginInfo>();
        foreach (var al in results) {
            logins.Add(new UserLoginInfo(al.LoginProvider, al.ProviderKey));
        }
        return Task.FromResult(logins);
    }

    private AzureLogin Find(UserLoginInfo login) {
        TableQuery<AzureLogin> query = new TableQuery<AzureLogin>()
            .Where(TableQuery.CombineFilters(
                TableQuery.GenerateFilterCondition("LoginProvider", QueryComparisons.Equal, login.LoginProvider),
                TableOperators.And,
                TableQuery.GenerateFilterCondition("ProviderKey", QueryComparisons.Equal, login.ProviderKey)))
            .Select(new string[] { "UserId" });
        return Table.ExecuteQuery(query).FirstOrDefault();
    }

    public Task<AzureUser> FindAsync(UserLoginInfo login) {
        var al = Find(login);
        if (al != null) {
            return FindByIdAsync(al.UserId);
        }
        return Task.FromResult<AzureUser>(null);
    }

    public Task AddToRoleAsync(AzureUser user, string role) {
        return Task.FromResult(0);
    }

    public Task RemoveFromRoleAsync(AzureUser user, string role) {
        return Task.FromResult(0);
    }

    public Task<IList<string>> GetRolesAsync(AzureUser user) {
        return Task.FromResult(user.Roles);
    }

    public Task<bool> IsInRoleAsync(AzureUser user, string role) {
        return Task.FromResult(false);
    }


    public Task DeleteAsync(AzureUser user) {
        throw new NotImplementedException();
    }

    public Task<string> GetPasswordHashAsync(AzureUser user) {
        return Task.FromResult(user.PasswordHash);
    }

    public Task<bool> HasPasswordAsync(AzureUser user) {
        return Task.FromResult(user.PasswordHash != null);
    }

    public Task SetPasswordHashAsync(AzureUser user, string passwordHash) {
        user.PasswordHash = passwordHash;
        return Task.FromResult(0);
    }
}

【讨论】:

  • 谢谢!微软的官方实现会很棒。这会是 11 月 13 日 VS2013 版本的一部分吗?
  • 嘿,快两年了,我没有找到任何使用 Azure Tables 的角色存储和用户存储的官方实现
  • Azure 表存储不允许集合...你是在序列化它们还是什么?
  • 您有使用 azure 存储模拟器的工作示例吗?我似乎无法让它在本地工作。
  • 这是否适用于 API?我正在尝试做这样的事情,但显示 Gran 的发球未注册。
【解决方案2】:

实际上,您不能将 EF Code First 与 Azure 表存储一起使用。话虽如此,使用表存储通常使用与代码类似的方法来完成 - 即您创建类,然后它们动态创建表。

请注意,表存储没有关系或类似的东西。表存储比其他 NoSQL 解决方案更简单,因为您不能将复杂对象存储在单个表“行”中。

您可能会创建一个仅使用表和/或 blob 存储的 .net 身份提供程序,但我找不到任何示例 - 我确定曾经有一个 codeplex 项目,但我现在找不到。

Gert Arnold 的意思是同时使用 SQL Azure 和表存储(EF 仅与 sql azure 部分一起使用)。通过这种方式,您可以将每一个都用于它们最擅长的领域 - 用于存储大量简单结构化数据的表存储,用于更复杂的数据部分(即需要关系)的 sql azure

【讨论】:

    【解决方案3】:

    供日后参考。有一个使用 Identity with Azure Table storage 的 github 项目。 James Randall's Accidental Fish。我不确定这些角色是否已经实现。

    【讨论】:

      【解决方案4】:

      借助最新的实体框架核心,您现在可以使用 EF 连接到 Azure 存储表:EntityFramework.AzureTableStorage 7.0.0-beta1

      如果您想配置您的 Dbcontext,请查看我的 post

      使用它,您可以实现您的UserManager 类。

      【讨论】:

      • EF 表存储提供程序当前处于暂停状态:stackoverflow.com/a/35188991/310446
      • Azure 表存储提供程序将不会由 EF Core 团队实现:由于我们将精力集中在 Cosmos DB(最初作为文档存储)而不是 ATS 上,因此关闭它。我们不打算让 EF 团队实施和维护 ATS 提供程序,但我们会全力支持社区为此所做的努力。来自Azure Table Storage Provider GitHub 问题。
      • @0xced 好点。这也是一个老帖子了。你应该写一个关于那个的答案
      猜你喜欢
      • 2012-05-08
      • 2017-08-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-17
      • 1970-01-01
      • 1970-01-01
      • 2018-01-28
      相关资源
      最近更新 更多