Code First&与Azure表存储的身份

时间:2013-10-17 20:45:36

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

我正在开发一个小型网络应用程序,我只是在开发中需要开始做出数据库决策。我最初的计划是在Azure上使用MSSQL获取EF Code First,因为它只是简化了使用数据库的过程。但是,在调查Azure上的数据库托管功能时,我发现了Azure Table Storage,它为我打开了NoSQL世界。

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

然而,我的主要问题是我找不到任何文档来使用像EF Code First和ASP.NET Identity与NoSQL数据库这样的东西。由于我的应用程序目前使用Identity,我希望避免切换到其他内容。

问:是否可以在Azure表中使用代码优先和/或身份?


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

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

4 个答案:

答案 0 :(得分:18)

因此,我们将使用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);
    }
}

答案 1 :(得分:5)

实际上,您不能将EF Code First与Azure表存储一起使用。说,使用表存储通常首先使用类似的代码方法完成 - 即您创建类并且他们动态创建表。

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

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

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

答案 2 :(得分:1)

以备日后参考。有一个使用Identity与Azure表存储的github项目。 James Randall's Accidental Fish。我不确定角色是否已经实施。

答案 3 :(得分:0)

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

如果您要配置Dbcontext,请参阅我的post

使用它,您可以实施UserManager课程。