18

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

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

然而,我的主要问题是我找不到任何文档来使用 EF Code First 和 ASP.NET Identity 等 NoSQL 数据库。由于我的应用程序当前使用身份,我想避免切换到其他东西。

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


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

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

4

4 回答 4

19

因此,我们将有一个完全针对此场景的示例,使用 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);
    }
}
于 2013-10-19T00:15:46.667 回答
5

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

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

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

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

于 2013-10-18T08:16:32.423 回答
1

供将来参考。有一个使用 Identity with Azure Table storage 的 github 项目。 詹姆斯兰德尔的意外鱼。我不确定这些角色是否已经实施。

于 2014-08-18T06:45:09.820 回答
0

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

如果您想配置您的 Dbcontext,请参阅我的帖子。

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

于 2016-02-02T01:30:11.800 回答