ASP.NET Identity DbContext Verwirrung

196

Eine Standard-MVC 5-App enthält diesen Code in IdentityModels.cs. Dieser Code gilt für alle ASP.NET-Identitätsvorgänge für die Standardvorlagen:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }
}

Wenn ich einen neuen Controller mithilfe von Ansichten mit Entity Framework auf ein Gerüst stelle und im Dialogfeld einen "neuen Datenkontext ..." erstelle, wird dies für mich generiert:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace WebApplication1.Models
{
    public class AllTheOtherStuffDbContext : DbContext
    {
        // You can add custom code to this file. Changes will not be overwritten.
        // 
        // If you want Entity Framework to drop and regenerate your database
        // automatically whenever you change your model schema, please use data migrations.
        // For more information refer to the documentation:
        // http://msdn.microsoft.com/en-us/data/jj591621.aspx

        public AllTheOtherStuffDbContext() : base("name=AllTheOtherStuffDbContext")
        {
        }

        public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }

    }
} 

Wenn ich einen anderen Controller + eine andere Ansicht mit EF erstellen würde, beispielsweise für ein Tiermodell, würde diese neue Zeile direkt unter public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }- wie folgt - automatisch generiert :

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

namespace WebApplication1.Models
{
    public class AllTheOtherStuffDbContext : DbContext
    {
        // You can add custom code to this file. Changes will not be overwritten.
        // 
        // If you want Entity Framework to drop and regenerate your database
        // automatically whenever you change your model schema, please use data migrations.
        // For more information refer to the documentation:
        // http://msdn.microsoft.com/en-us/data/jj591621.aspx

        public AllTheOtherStuffDbContext() : base("name=AllTheOtherStuffDbContext")
        {
        }

        public System.Data.Entity.DbSet<WebApplication1.Models.Movie> Movies { get; set; }
        public System.Data.Entity.DbSet<WebApplication1.Models.Animal> Animals { get; set; }

    }
} 

ApplicationDbContext(für alle ASP.NET Identity-Inhalte) erbt, von IdentityDbContextdenen wiederum erbt DbContext. AllOtherStuffDbContext(für meine eigenen Sachen) erbt von DbContext.

Meine Frage lautet also:

Welches dieser beiden ( ApplicationDbContextund AllOtherStuffDbContext) sollte ich für alle meine anderen Modelle verwenden? Oder sollte ich einfach die standardmäßige automatische Generierung verwenden, ApplicationDbContextda dies kein Problem darstellen sollte, da sie von der Basisklasse stammt DbContext, oder wird es einen gewissen Overhead geben? Sie sollten nur ein DbContextObjekt in Ihrer App für alle Ihre Modelle verwenden (ich habe dies irgendwo gelesen), damit ich nicht einmal in Betracht ziehen sollte, beide ApplicationDbContextund AllOtherStuffDbContextin einer einzigen App zu verwenden? Oder was ist die beste Vorgehensweise in MVC 5 mit ASP.NET Identity?

Der gestiefelte Kater
quelle
1
Apropos; Dies ist sehr schwierig und für meine Augen beim Scannen des Dokuments nicht erforderlich: public System.Data.Entity.DbSet <WebApplication1.Models.Movie> Movies {get; einstellen; } - Der Teil System.Data.Entity und WebApplication1.Models. Kann es nicht aus der Deklaration entfernt werden und stattdessen die Namespaces im Abschnitt using-Anweisungen hinzufügen?
PussInBoots
Der gestiefelte Kater - ja zu deinem Kommentar. Das sollte gut funktionieren.
SB2055
Dies ist ein gutes und funktionierendes Beispiel (MVC 6) und eine gute Implementierung mit dem ASP.NET 5 Identity (> = v3) Framework ohne Entity Framework für MongoDB.Driver (> = v2.1.0) github.com/saan800/SaanSoft. AspNet.Identity3.MongoDB
Stanislav Prusac

Antworten:

178

Ich würde eine einzelne Context-Klasse verwenden, die von IdentityDbContext erbt. Auf diese Weise kann der Kontext alle Beziehungen zwischen Ihren Klassen und dem IdentityUser und den Rollen des IdentityDbContext berücksichtigen. Der IdentityDbContext ist sehr wenig aufwendig, es handelt sich im Grunde genommen um einen regulären DbContext mit zwei DbSets. Eine für die Benutzer und eine für die Rollen.

Olav Nybø
quelle
52
Dies gilt für ein einzelnes MVC5-Projekt, ist jedoch nicht wünschenswert, wenn der abgeleitete DbContext von mehreren Projekten gemeinsam genutzt wird, einige nicht von MVC5, während andere keine Identitätsunterstützung benötigen.
Dave
Wählte dieselbe Datenbank für einfachere Wartbarkeit und bessere relationale Integrität. Weil die Benutzerentität und die Rollenentität leicht mit anderen Anwendungsobjekten verknüpft werden können.
anIBMer
6
@ Dave - Dies erschwert die Partitionierung von Benutzerdaten mithilfe von zwei verschiedenen Kontexten. Partitioniert Ihre MVC-App Daten nach Benutzer, die anderen Apps jedoch nicht? Die gemeinsame Nutzung derselben Datenschicht ist üblich, aber ich glaube nicht, dass einige Projekte die vom Benutzer partitionierten Daten benötigen, andere nicht.
RickAndMSFT
1
Ist jemandem eine Anleitung zum Extrahieren des ApplicationDBContext aus einem MVC-Projekt und zum Einfügen in eine vorhandene EF-Datenschicht bekannt? Das Zusammenführen der beiden, wie oben beschrieben, scheint der richtige Ansatz zu sein, aber ich arbeite an einem zeitlich begrenzten Projekt. Ich möchte es gleich beim ersten Mal richtig machen, würde mich aber über alle Fallstricke freuen, die vor mir liegen ...
Mike Devenney
7
Nachdem ich ungefähr eine Stunde lang gesucht hatte, zeigte mir diese Antwort die richtige Richtung - aber ich war mir nicht sicher, wie ich sie umsetzen sollte (für eine sehr wörtliche Person, die ich bin). Wenn es also jemand anderem hilft, ist es für mich am einfachsten, die Datei IdentityModels.cs zu öffnen und Ihr neues DbSet in die ApplicationDbContext-Klasse einzufügen.
SeanOB
45

Es gibt viel Verwirrung über IdentityDbContext , eine schnelle Suche in Stackoverflow, und Sie werden folgende Fragen finden:
" Warum ist Asp.Net Identity IdentityDbContext eine Black-Box?
Wie kann ich die Tabellennamen ändern, wenn ich Visual Studio 2013 AspNet Identity verwende?
MyDbContext mit IdentityDbContext zusammenführen "

Um all diese Fragen zu beantworten, müssen wir verstehen, dass IdentityDbContext nur eine von DbContext geerbte Klasse ist.
Werfen wir einen Blick auf die IdentityDbContext-Quelle :

/// <summary>
/// Base class for the Entity Framework database context used for identity.
/// </summary>
/// <typeparam name="TUser">The type of user objects.</typeparam>
/// <typeparam name="TRole">The type of role objects.</typeparam>
/// <typeparam name="TKey">The type of the primary key for users and roles.</typeparam>
/// <typeparam name="TUserClaim">The type of the user claim object.</typeparam>
/// <typeparam name="TUserRole">The type of the user role object.</typeparam>
/// <typeparam name="TUserLogin">The type of the user login object.</typeparam>
/// <typeparam name="TRoleClaim">The type of the role claim object.</typeparam>
/// <typeparam name="TUserToken">The type of the user token object.</typeparam>
public abstract class IdentityDbContext<TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TRoleClaim, TUserToken> : DbContext
    where TUser : IdentityUser<TKey, TUserClaim, TUserRole, TUserLogin>
    where TRole : IdentityRole<TKey, TUserRole, TRoleClaim>
    where TKey : IEquatable<TKey>
    where TUserClaim : IdentityUserClaim<TKey>
    where TUserRole : IdentityUserRole<TKey>
    where TUserLogin : IdentityUserLogin<TKey>
    where TRoleClaim : IdentityRoleClaim<TKey>
    where TUserToken : IdentityUserToken<TKey>
{
    /// <summary>
    /// Initializes a new instance of <see cref="IdentityDbContext"/>.
    /// </summary>
    /// <param name="options">The options to be used by a <see cref="DbContext"/>.</param>
    public IdentityDbContext(DbContextOptions options) : base(options)
    { }

    /// <summary>
    /// Initializes a new instance of the <see cref="IdentityDbContext" /> class.
    /// </summary>
    protected IdentityDbContext()
    { }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of Users.
    /// </summary>
    public DbSet<TUser> Users { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User claims.
    /// </summary>
    public DbSet<TUserClaim> UserClaims { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User logins.
    /// </summary>
    public DbSet<TUserLogin> UserLogins { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User roles.
    /// </summary>
    public DbSet<TUserRole> UserRoles { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of User tokens.
    /// </summary>
    public DbSet<TUserToken> UserTokens { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of roles.
    /// </summary>
    public DbSet<TRole> Roles { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="DbSet{TEntity}"/> of role claims.
    /// </summary>
    public DbSet<TRoleClaim> RoleClaims { get; set; }

    /// <summary>
    /// Configures the schema needed for the identity framework.
    /// </summary>
    /// <param name="builder">
    /// The builder being used to construct the model for this context.
    /// </param>
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<TUser>(b =>
        {
            b.HasKey(u => u.Id);
            b.HasIndex(u => u.NormalizedUserName).HasName("UserNameIndex").IsUnique();
            b.HasIndex(u => u.NormalizedEmail).HasName("EmailIndex");
            b.ToTable("AspNetUsers");
            b.Property(u => u.ConcurrencyStamp).IsConcurrencyToken();

            b.Property(u => u.UserName).HasMaxLength(256);
            b.Property(u => u.NormalizedUserName).HasMaxLength(256);
            b.Property(u => u.Email).HasMaxLength(256);
            b.Property(u => u.NormalizedEmail).HasMaxLength(256);
            b.HasMany(u => u.Claims).WithOne().HasForeignKey(uc => uc.UserId).IsRequired();
            b.HasMany(u => u.Logins).WithOne().HasForeignKey(ul => ul.UserId).IsRequired();
            b.HasMany(u => u.Roles).WithOne().HasForeignKey(ur => ur.UserId).IsRequired();
        });

        builder.Entity<TRole>(b =>
        {
            b.HasKey(r => r.Id);
            b.HasIndex(r => r.NormalizedName).HasName("RoleNameIndex");
            b.ToTable("AspNetRoles");
            b.Property(r => r.ConcurrencyStamp).IsConcurrencyToken();

            b.Property(u => u.Name).HasMaxLength(256);
            b.Property(u => u.NormalizedName).HasMaxLength(256);

            b.HasMany(r => r.Users).WithOne().HasForeignKey(ur => ur.RoleId).IsRequired();
            b.HasMany(r => r.Claims).WithOne().HasForeignKey(rc => rc.RoleId).IsRequired();
        });

        builder.Entity<TUserClaim>(b => 
        {
            b.HasKey(uc => uc.Id);
            b.ToTable("AspNetUserClaims");
        });

        builder.Entity<TRoleClaim>(b => 
        {
            b.HasKey(rc => rc.Id);
            b.ToTable("AspNetRoleClaims");
        });

        builder.Entity<TUserRole>(b => 
        {
            b.HasKey(r => new { r.UserId, r.RoleId });
            b.ToTable("AspNetUserRoles");
        });

        builder.Entity<TUserLogin>(b =>
        {
            b.HasKey(l => new { l.LoginProvider, l.ProviderKey });
            b.ToTable("AspNetUserLogins");
        });

        builder.Entity<TUserToken>(b => 
        {
            b.HasKey(l => new { l.UserId, l.LoginProvider, l.Name });
            b.ToTable("AspNetUserTokens");
        });
    }
}


Basierend auf dem Quellcode haben wir zwei Optionen, wenn wir IdentityDbContext mit unserem DbContext zusammenführen möchten:

Erste Option:
Erstellen Sie einen DbContext, der von IdentityDbContext erbt und Zugriff auf die Klassen hat.

   public class ApplicationDbContext 
    : IdentityDbContext
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }

    static ApplicationDbContext()
    {
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }

    // Add additional items here as needed
}


Zusätzliche Hinweise:

1) Wir können auch die Standardtabellennamen der asp.net-Identität mit der folgenden Lösung ändern:

    public class ApplicationDbContext : IdentityDbContext
    {    
        public ApplicationDbContext(): base("DefaultConnection")
        {
        }

        protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<IdentityUser>().ToTable("user");
            modelBuilder.Entity<ApplicationUser>().ToTable("user");

            modelBuilder.Entity<IdentityRole>().ToTable("role");
            modelBuilder.Entity<IdentityUserRole>().ToTable("userrole");
            modelBuilder.Entity<IdentityUserClaim>().ToTable("userclaim");
            modelBuilder.Entity<IdentityUserLogin>().ToTable("userlogin");
        }
    }

2) Außerdem können wir jede Klasse erweitern und Klassen wie 'IdentityUser', 'IdentityRole', ... eine beliebige Eigenschaft hinzufügen.

    public class ApplicationRole : IdentityRole<string, ApplicationUserRole>
{
    public ApplicationRole() 
    {
        this.Id = Guid.NewGuid().ToString();
    }

    public ApplicationRole(string name)
        : this()
    {
        this.Name = name;
    }

    // Add any custom Role properties/code here
}


// Must be expressed in terms of our custom types:
public class ApplicationDbContext 
    : IdentityDbContext<ApplicationUser, ApplicationRole, 
    string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }

    static ApplicationDbContext()
    {
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }

    // Add additional items here as needed
}

Um Zeit zu sparen, können wir die erweiterbare Projektvorlage AspNet Identity 2.0 verwenden , um alle Klassen zu erweitern.

Zweite Option:(Nicht empfohlen)
Wir müssen eigentlich nicht von IdentityDbContext erben, wenn wir den gesamten Code selbst schreiben.
Grundsätzlich können wir also einfach von DbContext erben und unsere angepasste Version von "OnModelCreating (ModelBuilder Builder)" aus dem IdentityDbContext-Quellcode implementieren

Arvand
quelle
2
@ mike-devenney Hier ist deine Antwort zum Zusammenführen der beiden Kontextebenen, hoffe es hilft.
Arvand
1
Danke Arvand, ich habe das verpasst und bin seltsamerweise 1,5 Jahre später darauf gestoßen, als ich mich wieder mit dem Thema befasst habe. :)
Mike Devenney
9

Dies ist ein später Eintrag für Leute, aber unten ist meine Implementierung. Sie werden auch feststellen, dass ich die Möglichkeit, den Standardtyp KEYs zu ändern, deaktiviert habe: Die Details dazu finden Sie in den folgenden Artikeln:

HINWEISE:
Es ist zu beachten, dass Sie diese nicht Guid'sfür Ihre Schlüssel verwenden können. Dies liegt daran, dass sie unter der Haube a sind Structund als solche kein Unboxing haben, das ihre Konvertierung von einem generischen <TKey>Parameter ermöglichen würde.

Die Klassen sehen aus wie:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole, string, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
    #region <Constructors>

    public ApplicationDbContext() : base(Settings.ConnectionString.Database.AdministrativeAccess)
    {
    }

    #endregion

    #region <Properties>

    //public DbSet<Case> Case { get; set; }

    #endregion

    #region <Methods>

    #region

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        //modelBuilder.Configurations.Add(new ResourceConfiguration());
        //modelBuilder.Configurations.Add(new OperationsToRolesConfiguration());
    }

    #endregion

    #region

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }

    #endregion

    #endregion
}

    public class ApplicationUser : IdentityUser<string, CustomUserLogin, CustomUserRole, CustomUserClaim>
    {
        #region <Constructors>

        public ApplicationUser()
        {
            Init();
        }

        #endregion

        #region <Properties>

        [Required]
        [StringLength(250)]
        public string FirstName { get; set; }

        [Required]
        [StringLength(250)]
        public string LastName { get; set; }

        #endregion

        #region <Methods>

        #region private

        private void Init()
        {
            Id = Guid.Empty.ToString();
        }

        #endregion

        #region public

        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

            // Add custom user claims here

            return userIdentity;
        }

        #endregion

        #endregion
    }

    public class CustomUserStore : UserStore<ApplicationUser, CustomRole, string, CustomUserLogin, CustomUserRole, CustomUserClaim>
    {
        #region <Constructors>

        public CustomUserStore(ApplicationDbContext context) : base(context)
        {
        }

        #endregion
    }

    public class CustomUserRole : IdentityUserRole<string>
    {
    }

    public class CustomUserLogin : IdentityUserLogin<string>
    {
    }

    public class CustomUserClaim : IdentityUserClaim<string> 
    { 
    }

    public class CustomRoleStore : RoleStore<CustomRole, string, CustomUserRole>
    {
        #region <Constructors>

        public CustomRoleStore(ApplicationDbContext context) : base(context)
        {
        } 

        #endregion
    }

    public class CustomRole : IdentityRole<string, CustomUserRole>
    {
        #region <Constructors>

        public CustomRole() { }
        public CustomRole(string name) 
        { 
            Name = name; 
        }

        #endregion
    }
Gefangener NULL
quelle
8

Wenn Sie die Abstraktionen des IdentityDbContext durchgehen, werden Sie feststellen, dass er genauso aussieht wie Ihr abgeleiteter DbContext. Der einfachste Weg ist Olavs Antwort. Wenn Sie jedoch mehr Kontrolle darüber haben möchten, was erstellt wird, und etwas weniger Abhängigkeit von den Identitätspaketen , schauen Sie sich meine Frage und Antwort hier an . Es gibt ein Codebeispiel, wenn Sie dem Link folgen, aber zusammenfassend fügen Sie einfach die erforderlichen DbSets zu Ihrer eigenen DbContext-Unterklasse hinzu.

joelmdev
quelle