Identidade ASP.NET com EF Database First MVC5

88

É possível usar a nova identidade Asp.net com Database First e EDMX? Ou apenas com o código primeiro?

Aqui está o que eu fiz:

1) Fiz um novo projeto MVC5 e fiz com que a nova identidade criasse as novas tabelas de usuário e funções em meu banco de dados.

2) Em seguida, abri meu arquivo EDMX do Database First e arrastei para a nova tabela Identity Users, pois tenho outras tabelas relacionadas a ela.

3) Ao salvar o EDMX, o gerador POCO do Database First criará automaticamente uma classe de usuário. No entanto, UserManager e RoleManager esperam que uma classe User herde do novo namespace Identity (Microsoft.AspNet.Identity.IUser), portanto, usar a classe POCO User não funcionará.

Eu acho que uma solução possível é editar minhas classes de geração de POCO para que minha classe de usuário herde de IUser?

Ou a identidade do ASP.NET é compatível apenas com o Code First Design?

+++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++

Atualização: Seguindo a sugestão de Anders Abel abaixo, foi o que fiz. Funciona, mas gostaria de saber se existe uma solução mais elegante.

1) Eu estendi minha classe de usuário de entidade criando uma classe parcial dentro do mesmo namespace que minhas entidades geradas automaticamente.

namespace MVC5.DBFirst.Entity
{
    public partial class AspNetUser : IdentityUser
    {
    }
}

2) Mudei meu DataContext para herdar de IdentityDBContext em vez de DBContext. Observe que toda vez que você atualiza seu EDMX e regenera as classes DBContext e Entity, você terá que definir isso novamente.

 public partial class MVC5Test_DBEntities : IdentityDbContext<AspNetUser>  //DbContext

3) Em sua classe de entidade User gerada automaticamente, você deve adicionar a palavra-chave override aos 4 campos a seguir ou comentar esses campos, uma vez que são herdados de IdentityUser (Etapa 1). Observe que toda vez que você atualiza seu EDMX e regenera as classes DBContext e Entity, você terá que definir isso novamente.

    override public string Id { get; set; }
    override public string UserName { get; set; }
    override public string PasswordHash { get; set; }
    override public string SecurityStamp { get; set; }
Patrick Tran
fonte
1
você tem código de amostra de sua implementação? como quando tento replicar o acima, recebo um erro quando tento fazer o login ou registrar um usuário. "O tipo de entidade AspNetUser não faz parte do modelo para o contexto atual", onde AspNetUser é minha entidade de usuário
Tim
Você adicionou a tabela AspNetUser ao seu EDMX? Além disso, certifique-se de que seu AccountController está usando MVC5Test_DBEntities (ou qualquer que seja o nome do seu contexto de banco de dados) em vez de ApplicationContext.
Patrick Tran
8
A identidade ASP.NET é uma pilha fumegante de ____. Suporte horrível para banco de dados primeiro, sem documentação, restrições referenciais pobres (ausente ON CASCADE DELETE no servidor SQL) e usa strings para IDs (problema de desempenho e fragmentação de índice). E esta é a sua 297ª tentativa de estrutura de identidade ...
DeepSpace101
1
@ DeepSpace101 Identity suporta DB-first da mesma forma que Code-first. Os modelos são configurados para fazer o código primeiro, portanto, se você começar a partir de um modelo, terá que alterar algumas coisas. A exclusão em cascata funciona bem, você pode alterar as strings para ints facilmente. Veja minha resposta abaixo.
Sapato
1
@Shoe Devo dizer que acho que você pode estar errado. Ainda estou para encontrar um exemplo / tutorial abrangente e funcional sobre como implementar isso no banco de dados primeiro (sem documentação). A API tenta fazer referência à tabela de junção "IdentityUserRoles" por meio da propriedade IdentityUser.Roles, que quebra o relacionamento no EF db-first, uma vez que as tabelas de junção não são expostas como entidades (restrições referenciais pobres). Eu discordo sobre as strings para IDs, uma vez que podem ser personalizadas especificando os parâmetros de tipo nas classes herdadas. Para resumir, me parece que eles não tinham o DB em mente.
Desequilibrado em

Respostas:

16

Deve ser possível usar o sistema de identidade com POCO e Database First, mas você terá que fazer alguns ajustes:

  1. Atualize o arquivo .tt para geração de POCO para fazer as classes de entidade partial. Isso possibilitará que você forneça implementações adicionais em um arquivo separado.
  2. Faça uma implementação parcial da Userclasse em outro arquivo

 

partial User : IUser
{
}

Isso fará com que a Userclasse implemente a interface certa, sem mexer nos arquivos reais gerados (editar os arquivos gerados é sempre uma má ideia).

Anders Abel
fonte
Obrigado. Vou ter que dar uma chance e relatar se der certo.
Patrick Tran
Eu tentei o que você mencionou. Veja meu post para informações atualizadas ... mas a solução não era muito elegante: /
Patrick Tran
Tenho tido o mesmo problema. Parece que a documentação sobre o DB-first é muito esparsa. Esta é uma boa sugestão, mas acho que você está certo, não funciona
Phil
4
Existe alguma solução que ainda não seja um hack?
user20358
Estou usando a Onion Architecture e todos os POCOs estão no Core. Lá não é recomendado usar IUser. Então, alguma outra solução?
Usman Khalid
13

Meus passos são muito semelhantes, mas eu queria compartilhar.

1) Crie um novo projeto MVC5

2) Crie um novo Model.edmx. Mesmo que seja um novo banco de dados e não tenha tabelas.

3) Edite web.config e substitua esta cadeia de conexões gerada:

<add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\aspnet-SSFInventory-20140521115734.mdf;Initial Catalog=aspnet-SSFInventory-20140521115734;Integrated Security=True" providerName="System.Data.SqlClient" />

com esta cadeia de conexões:

<add name="DefaultConnection" connectionString="Data Source=.\SQLExpress;database=SSFInventory;integrated security=true;" providerName="System.Data.SqlClient" />

Em seguida, crie e execute o aplicativo. Cadastre um usuário e as tabelas serão criadas.

JoshYates1980
fonte
1
isso resolveu um problema, não consegui ver o usuário do aplicativo no banco de dados, mas depois de substituir a conexão padrão, funcionou.
Hassen Ch.
10

EDIT: Identidade ASP.NET com EF Database First para MVC5 CodePlex Project Template.


Eu queria usar um banco de dados existente e criar relacionamentos com ApplicationUser. Foi assim que fiz usando o SQL Server, mas a mesma ideia provavelmente funcionaria com qualquer banco de dados.

  1. Crie um projeto MVC
  2. Abra o banco de dados listado em DefaultConnection em Web.config. Ele será chamado (aspnet- [timestamp] ou algo parecido.)
  3. Faça o script das tabelas do banco de dados.
  4. Insira as tabelas com script no banco de dados existente no SQL Server Management Studio.
  5. Personalize e adicione relacionamentos a ApplicationUser (se necessário).
  6. Criar novo projeto da Web> MVC> Projeto primeiro banco de dados> Importar banco de dados com EF ... Excluindo as classes de identidade inseridas.
  7. Em IdentityModels.cs, altere o ApplicationDbContext :base("DefaltConnection")para usar o DbContext do seu projeto.

Editar: Diagrama de Classe de Identidade Asp.Net insira a descrição da imagem aqui

fedor
fonte
6
O problema não é DBContext, mas que UserManager e RoleManager esperam uma classe que herda de Microsoft.AspNet.Identity.EntityFramework.IdentityUser
Patrick Tran
public class IdentityDbContext <TUser>: DbContext onde TUser: Microsoft.AspNet.Identity.EntityFramework.IdentityUser. Ao usar o banco de dados primeiro, as classes de entidade geradas não herdam de nenhuma classe base.
Patrick Tran
Em seguida, basta excluí-los do banco de dados ao gerar as classes com a estrutura de entidade.
fedor
Se você excluir as tabelas de identidade do EDMX, perderá as propriedades de navegação em outras classes que têm chaves estrangeiras para sua ID de usuário
Patrick Tran
34
Não estou relutante em passar para o código primeiro ... Só que, em alguns cenários e empresas, o administrador do banco de dados cria as tabelas de base, não o codificador.
Patrick Tran
8

IdentityUseré inútil aqui porque é o objeto de código usado pelo UserStorepara autenticação. Depois de definir meu próprio Userobjeto, implementei uma classe parcial que implementa o IUserque é usado pela UserManagerclasse. Eu queria meuId s fosse em intvez de string, então apenas retornei toString () do UserID. Da mesma forma que eu queria nem Usernameser com inicial minúscula.

public partial class User : IUser
{

    public string Id
    {
        get { return this.UserID.ToString(); }
    }

    public string UserName
    {
        get
        {
            return this.Username;
        }
        set
        {
            this.Username = value;
        }
    }
}

Você de forma alguma precisa IUser . É apenas uma interface usada pelo UserManager. Portanto, se você quiser definir um "IUser" diferente, terá que reescrever essa classe para usar sua própria implementação.

public class UserManager<TUser> : IDisposable where TUser: IUser

Agora você escreve o seu próprio, UserStoreque lida com todo o armazenamento de usuários, declarações, funções, etc. Implementar as interfaces de tudo que o código-primeiroUserStore faz e mude where TUser : IdentityUserpara where TUser : Useronde "Usuário" é seu objeto de entidade

public class MyUserStore<TUser> : IUserLoginStore<TUser>, IUserClaimStore<TUser>, IUserRoleStore<TUser>, IUserPasswordStore<TUser>, IUserSecurityStampStore<TUser>, IUserStore<TUser>, IDisposable where TUser : User
{
    private readonly MyAppEntities _context;
    public MyUserStore(MyAppEntities dbContext)
    { 
        _context = dbContext; 
    }

    //Interface definitions
}

Aqui estão alguns exemplos de algumas das implementações de interface

async Task IUserStore<TUser>.CreateAsync(TUser user)
{
    user.CreatedDate = DateTime.Now;
    _context.Users.Add(user);
    await _context.SaveChangesAsync();
}

async Task IUserStore<TUser>.DeleteAsync(TUser user)
{
    _context.Users.Remove(user);
    await _context.SaveChangesAsync();
}

Usando o modelo MVC 5, mudei o AccountController para ficar assim.

public AccountController()
        : this(new UserManager<User>(new MyUserStore<User>(new MyAppEntities())))
{
}

Agora, o login deve funcionar com suas próprias tabelas.

Sapato
fonte
1
Recentemente, tive a chance de implementar isso e, por algum motivo (acredito que por causa da atualização 3.0 da identidade), não consegui implementar o login herdando IdentityUser e substituindo as propriedades; mas escrever um UserStore personalizado e herdar IUser funcionou bem; Apenas dando uma atualização, talvez alguém ache isso útil.
Naz Ekin de
Você pode fornecer um link para todas as implementações de interface ou para o projeto completo?
DespeiL
há um motivo específico pelo qual você usou uma classe parcial aqui?
user8964654
Se você estiver usando um edmx para gerar seus modelos, você deve usar uma classe parcial. Se você estiver codificando primeiro, pode omitir isso
Shoe
3

Boa pergunta.

Eu sou mais um banco de dados em primeiro lugar. O primeiro paradigma do código parece um tanto solto para mim, e as "migrações" parecem muito sujeitas a erros.

Eu queria personalizar o esquema de identidade aspnet e não me incomodar com migrações. Estou bem versado com projetos de banco de dados do Visual Studio (sqlpackage, data-dude) e como ele faz um bom trabalho na atualização de esquemas.

Minha solução simplista é:

1) Crie um projeto de banco de dados que espelhe o esquema de identidade aspnet 2) use a saída deste projeto (.dacpac) como um recurso de projeto 3) implante o .dacpac quando necessário

Para MVC5, modificar a ApplicationDbContextclasse parece fazer isso funcionar ...

1) Implementar IDatabaseInitializer

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDatabaseInitializer<ApplicationDbContext> { ... }

2) No construtor, sinalize que esta classe implementará a inicialização do banco de dados:

Database.SetInitializer<ApplicationDbContext>(this);

3) Implementar InitializeDatabase:

Aqui, escolhi usar o DacFX e implantar meu .dacpac

    void IDatabaseInitializer<ApplicationDbContext>.InitializeDatabase(ApplicationDbContext context)
    {
        using (var ms = new MemoryStream(Resources.Binaries.MainSchema))
        {
            using (var package = DacPackage.Load(ms, DacSchemaModelStorageType.Memory))
            {
                DacServices services = new DacServices(Database.Connection.ConnectionString);
                var options = new DacDeployOptions
                {
                    VerifyDeployment = true,
                    BackupDatabaseBeforeChanges = true,
                    BlockOnPossibleDataLoss = false,
                    CreateNewDatabase = false,
                    DropIndexesNotInSource = true,
                    IgnoreComments = true,

                };
                services.Deploy(package, Database.Connection.Database, true, options);
            }
        }
    }
Aaron Hudon
fonte
2

Passei várias horas trabalhando nisso e finalmente encontrei uma solução que compartilhei no meu blog aqui . Basicamente, você precisa fazer tudo o que foi dito na resposta de stink , mas com uma coisa adicional: garantir que o Identity Framework tenha uma string de conexão SQL-Client específica no topo da string de conexão do Entity Framework usada para suas entidades de aplicativo.

Em resumo, seu aplicativo usará uma string de conexão para o Identity Framework e outra para as entidades de seu aplicativo. Cada string de conexão é de um tipo diferente. Leia minha postagem do blog para um tutorial completo.

Daniel Eagle
fonte
Eu tentei tudo que você mencionou no seu blog duas vezes, não deu certo para mim.
Badhon Jain
@Badhon Lamento muito que as instruções na minha postagem do blog não tenham funcionado para você. Inúmeras pessoas expressaram seus agradecimentos por terem obtido sucesso após meu artigo. Lembre-se sempre de que se a Microsoft atualizar algo, isso pode afetar o resultado. Escrevi o artigo para ASP.NET MVC 5 com Identity Framework 2.0. Qualquer coisa além disso pode apresentar problemas, mas até agora recebi comentários muito recentes indicando sucesso. Eu adoraria ouvir mais sobre seu problema.
Daniel Eagle
Vou tentar seguir mais uma vez, o problema que enfrentei é que não pude usar meu banco de dados personalizado, ele usou o banco de dados construído automaticamente. De qualquer forma, não quis dizer algo errado com o seu artigo. Obrigado por compartilhar o seu conhecimento.
Badhon Jain
1
@Badhon Não se preocupe meu amigo, nunca achei que você estivesse dizendo que havia algo errado com o artigo. Todos nós temos situações únicas com vários casos extremos, então às vezes o que funciona para os outros pode não funcionar para nós. Espero que você tenha resolvido seu problema.
Daniel Eagle
1

Temos um projeto Entity Model DLL onde mantemos nossa classe de modelo. Também mantemos um projeto de banco de dados com todos os scripts de banco de dados. Minha abordagem foi a seguinte

1) Crie seu próprio projeto que tenha o EDMX usando o banco de dados primeiro

2) Faça o script das tabelas em seu banco de dados, usei VS2013 conectado ao localDB (Data Connections) e copiei o script para o projeto de banco de dados, adicione quaisquer colunas personalizadas, por exemplo: Data de nascimento [DATE] não nula

3) Implantar o banco de dados

4) Atualizar o projeto Modelo (EDMX) Adicionar ao projeto Modelo

5) Adicione quaisquer colunas personalizadas à classe do aplicativo

public class ApplicationUser : IdentityUser
{
    public DateTime BirthDate { get; set; }
}

No projeto MVC, AccountController adicionou o seguinte:

O provedor de identidade deseja que uma string de conexão SQL funcione, para manter apenas 1 string de conexão para o banco de dados, extraia a string do provedor da string de conexão EF

public AccountController()
{
   var connection = ConfigurationManager.ConnectionStrings["Entities"];
   var entityConnectionString = new EntityConnectionStringBuilder(connection.ConnectionString);
        UserManager =
            new UserManager<ApplicationUser>(
                new UserStore<ApplicationUser>(
                    new ApplicationDbContext(entityConnectionString.ProviderConnectionString)));
}
Haroon
fonte
1

Descobri que @ JoshYates1980 tem a resposta mais simples.

Depois de uma série de tentativas e erros, fiz o que Josh sugeriu e substituí o connectionString por minha string de conexão DB gerada. o que eu estava confuso originalmente era a seguinte postagem:

Como adicionar autenticação de identidade ASP.NET MVC5 ao banco de dados existente

Onde a resposta aceita de @Win indica a mudança do ApplicationDbContext()nome da conexão. Isso é um pouco vago se você estiver usando a abordagem de Entidade e Banco de Dados / Modelo em que a string de conexão do banco de dados é gerada e adicionada ao Web.configarquivo.

O ApplicationDbContext()nome da conexão é mapeado para a conexão padrão no Web.configarquivo. Portanto, o método de Josh funciona melhor, mas para torná-lo ApplicationDbContext()mais legível, sugiro alterar o nome do seu banco de dados como @Win postado originalmente, certificando-se de alterar o connectionStringpara "DefaultConnection" noWeb.config e comentar e / ou remover a Entidade banco de dados gerado inclui.

Exemplos de código:

TheSchnitz
fonte