Für EntityType 'IdentityUserLogin' ist kein Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType


105

Ich arbeite mit Entity Framework Code First und MVC 5. Als ich meine Anwendung mit der Authentifizierung einzelner Benutzerkonten erstellte, erhielt ich einen Kontocontroller und alle erforderlichen Klassen und Codes, die erforderlich sind, damit die Indiv-Benutzerkontenauthentifizierung funktioniert .

Zu den bereits vorhandenen Codes gehörte:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext() : base("DXContext", throwIfV1Schema: false)
    {

    }

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

Aber dann habe ich zuerst meinen eigenen Kontext mit Code erstellt, sodass ich jetzt auch Folgendes habe:

public class DXContext : DbContext
{
    public DXContext() : base("DXContext")
    {
        
    }

    public DbSet<ApplicationUser> Users { get; set; }
    public DbSet<IdentityRole> Roles { get; set; }
    public DbSet<Artist> Artists { get; set; }
    public DbSet<Paintings> Paintings { get; set; }        
}

Schließlich habe ich die folgende Seed-Methode, um einige Daten hinzuzufügen, mit denen ich während der Entwicklung arbeiten kann:

protected override void Seed(DXContext context)
{
    try
    {

        if (!context.Roles.Any(r => r.Name == "Admin"))
        {
            var store = new RoleStore<IdentityRole>(context);
            var manager = new RoleManager<IdentityRole>(store);
            var role = new IdentityRole { Name = "Admin" };

            manager.Create(role);
        }

        context.SaveChanges();

        if (!context.Users.Any(u => u.UserName == "James"))
        {
            var store = new UserStore<ApplicationUser>(context);
            var manager = new UserManager<ApplicationUser>(store);
            var user = new ApplicationUser { UserName = "James" };

            manager.Create(user, "ChangeAsap1@");
            manager.AddToRole(user.Id, "Admin");
        }

        context.SaveChanges();

        string userId = "";

        userId = context.Users.FirstOrDefault().Id;

        var artists = new List<Artist>
        {
            new Artist { FName = "Salvador", LName = "Dali", ImgURL = "http://i62.tinypic.com/ss8txxn.jpg", UrlFriendly = "salvador-dali", Verified = true, ApplicationUserId = userId },
        };

        artists.ForEach(a => context.Artists.Add(a));
        context.SaveChanges();

        var paintings = new List<Painting>
        {
            new Painting { Title = "The Persistence of Memory", ImgUrl = "http://i62.tinypic.com/xx8tssn.jpg", ArtistId = 1, Verified = true, ApplicationUserId = userId }
        };

        paintings.ForEach(p => context.Paintings.Add(p));
        context.SaveChanges();
    }
    catch (DbEntityValidationException ex)
    {
        foreach (var validationErrors in ex.EntityValidationErrors)
        {
            foreach (var validationError in validationErrors.ValidationErrors)
            {
                Trace.TraceInformation("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
            }
        }
    }
    
}

Meine Lösung funktioniert einwandfrei, aber wenn ich versuche, auf einen Controller zuzugreifen, der Zugriff auf die Datenbank erfordert, wird die folgende Fehlermeldung angezeigt:

DX.DOMAIN.Context.IdentityUserLogin :: EntityType 'IdentityUserLogin' hat keinen Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType.

DX.DOMAIN.Context.IdentityUserRole :: Für EntityType 'IdentityUserRole' ist kein Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType.

Was mache ich falsch? Liegt es daran, dass ich zwei Kontexte habe?

AKTUALISIEREN

Nachdem ich Augustos Antwort gelesen hatte, entschied ich mich für Option 3 . So sieht meine DXContext-Klasse jetzt aus:

public class DXContext : DbContext
{
    public DXContext() : base("DXContext")
    {
        // remove default initializer
        Database.SetInitializer<DXContext>(null);
        Configuration.LazyLoadingEnabled = false;
        Configuration.ProxyCreationEnabled = false;

    }

    public DbSet<User> Users { get; set; }
    public DbSet<Role> Roles { get; set; }
    public DbSet<Artist> Artists { get; set; }
    public DbSet<Painting> Paintings { get; set; }

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

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<User>().ToTable("Users");
        modelBuilder.Entity<Role>().ToTable("Roles");
    }

    public DbQuery<T> Query<T>() where T : class
    {
        return Set<T>().AsNoTracking();
    }
}

Ich habe auch eine User.csund eine Role.csKlasse hinzugefügt , sie sehen so aus:

public class User
{
    public int Id { get; set; }
    public string FName { get; set; }
    public string LName { get; set; }
}

public class Role
{
    public int Id { set; get; }
    public string Name { set; get; }
}

Ich war mir nicht sicher, ob ich eine Kennworteigenschaft für den Benutzer benötigen würde, da der Standardanwendungsbenutzer diese und eine Reihe anderer Felder hat!

Wie auch immer, die obige Änderung funktioniert einwandfrei, aber ich erhalte erneut diesen Fehler, wenn die Anwendung ausgeführt wird:

Ungültiger Spaltenname UserId

UserId ist eine ganzzahlige Eigenschaft auf meiner Artist.cs

Antworten:


116

Das Problem ist, dass Ihr ApplicationUser von IdentityUser erbt , das wie folgt definiert ist:

IdentityUser : IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser
....
public virtual ICollection<TRole> Roles { get; private set; }
public virtual ICollection<TClaim> Claims { get; private set; }
public virtual ICollection<TLogin> Logins { get; private set; }

und ihre Primärschlüssel werden in der Methode OnModelCreating der Klasse IdentityDbContext zugeordnet :

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

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

und da Ihr DXContext nicht davon abgeleitet ist, werden diese Schlüssel nicht definiert.

Wenn Sie in die Quellen von graben Microsoft.AspNet.Identity.EntityFramework, werden Sie alles verstehen.

Ich bin vor einiger Zeit auf diese Situation gestoßen und habe drei mögliche Lösungen gefunden (vielleicht gibt es noch mehr):

  1. Verwenden Sie separate DbContexts für zwei verschiedene Datenbanken oder dieselbe Datenbank, aber unterschiedliche Tabellen.
  2. Führen Sie Ihren DXContext mit ApplicationDbContext zusammen und verwenden Sie eine Datenbank.
  3. Verwenden Sie separate DbContexts für dieselbe Tabelle und verwalten Sie deren Migrationen entsprechend.

Option 1: Siehe Aktualisierung unten.

Option 2: Sie erhalten einen DbContext wie diesen:

public class DXContext : IdentityDbContext<User, Role,
    int, UserLogin, UserRole, UserClaim>//: DbContext
{
    public DXContext()
        : base("name=DXContext")
    {
        Database.SetInitializer<DXContext>(null);// Remove default initializer
        Configuration.ProxyCreationEnabled = false;
        Configuration.LazyLoadingEnabled = false;
    }

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

    //Identity and Authorization
    public DbSet<UserLogin> UserLogins { get; set; }
    public DbSet<UserClaim> UserClaims { get; set; }
    public DbSet<UserRole> UserRoles { get; set; }
    
    // ... your custom DbSets
    public DbSet<RoleOperation> RoleOperations { get; set; }

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

        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

        // Configure Asp Net Identity Tables
        modelBuilder.Entity<User>().ToTable("User");
        modelBuilder.Entity<User>().Property(u => u.PasswordHash).HasMaxLength(500);
        modelBuilder.Entity<User>().Property(u => u.Stamp).HasMaxLength(500);
        modelBuilder.Entity<User>().Property(u => u.PhoneNumber).HasMaxLength(50);

        modelBuilder.Entity<Role>().ToTable("Role");
        modelBuilder.Entity<UserRole>().ToTable("UserRole");
        modelBuilder.Entity<UserLogin>().ToTable("UserLogin");
        modelBuilder.Entity<UserClaim>().ToTable("UserClaim");
        modelBuilder.Entity<UserClaim>().Property(u => u.ClaimType).HasMaxLength(150);
        modelBuilder.Entity<UserClaim>().Property(u => u.ClaimValue).HasMaxLength(500);
    }
}

Option 3: Sie haben einen DbContext, der der Option 2 entspricht. Nennen wir ihn IdentityContext. Und Sie werden einen weiteren DbContext namens DXContext haben:

public class DXContext : DbContext
{        
    public DXContext()
        : base("name=DXContext") // connection string in the application configuration file.
    {
        Database.SetInitializer<DXContext>(null); // Remove default initializer
        Configuration.LazyLoadingEnabled = false;
        Configuration.ProxyCreationEnabled = false;
    }

    // Domain Model
    public DbSet<User> Users { get; set; }
    // ... other custom DbSets
    
    public static DXContext Create()
    {
        return new DXContext();
    }

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

        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        // IMPORTANT: we are mapping the entity User to the same table as the entity ApplicationUser
        modelBuilder.Entity<User>().ToTable("User"); 
    }

    public DbQuery<T> Query<T>() where T : class
    {
        return Set<T>().AsNoTracking();
    }
}

wo Benutzer ist:

public class User
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; }

    [Required, StringLength(128)]
    public string SomeOtherColumn { get; set; }
}

Mit dieser Lösung ordne ich den Entitätsbenutzer derselben Tabelle zu wie den Entitätsanwendungsbenutzer.

Anschließend müssen Sie mithilfe von Code First Migrations die Migrationen für den IdentityContext und DANN für den DXContext generieren. Folgen Sie diesem großartigen Beitrag von Shailendra Chauhan: Code First Migrations mit mehreren Datenkontexten

Sie müssen die für DXContext generierte Migration ändern. So etwas hängt davon ab, welche Eigenschaften von ApplicationUser und User gemeinsam genutzt werden:

        //CreateTable(
        //    "dbo.User",
        //    c => new
        //        {
        //            Id = c.Int(nullable: false, identity: true),
        //            Name = c.String(nullable: false, maxLength: 100),
        //            SomeOtherColumn = c.String(nullable: false, maxLength: 128),
        //        })
        //    .PrimaryKey(t => t.Id);
        AddColumn("dbo.User", "SomeOtherColumn", c => c.String(nullable: false, maxLength: 128));

und dann die Migrationen in der Reihenfolge (zuerst die Identitätsmigrationen) von global.asax oder einem anderen Ort Ihrer Anwendung mit dieser benutzerdefinierten Klasse ausführen:

public static class DXDatabaseMigrator
{
    public static string ExecuteMigrations()
    {
        return string.Format("Identity migrations: {0}. DX migrations: {1}.", ExecuteIdentityMigrations(),
            ExecuteDXMigrations());
    }

    private static string ExecuteIdentityMigrations()
    {
        IdentityMigrationConfiguration configuration = new IdentityMigrationConfiguration();
        return RunMigrations(configuration);
    }

    private static string ExecuteDXMigrations()
    {
        DXMigrationConfiguration configuration = new DXMigrationConfiguration();
        return RunMigrations(configuration);
    }

    private static string RunMigrations(DbMigrationsConfiguration configuration)
    {
        List<string> pendingMigrations;
        try
        {
            DbMigrator migrator = new DbMigrator(configuration);
            pendingMigrations = migrator.GetPendingMigrations().ToList(); // Just to be able to log which migrations were executed

            if (pendingMigrations.Any())                
                    migrator.Update();     
        }
        catch (Exception e)
        {
            ExceptionManager.LogException(e);
            return e.Message;
        }
        return !pendingMigrations.Any() ? "None" : string.Join(", ", pendingMigrations);
    }
}

Auf diese Weise erben meine n-Tier-Querschnittsentitäten nicht von AspNetIdentity-Klassen, und daher muss ich dieses Framework nicht in jedes Projekt importieren, in dem ich sie verwende.

Entschuldigung für den umfangreichen Beitrag. Ich hoffe, es könnte eine Anleitung dazu geben. Ich habe bereits die Optionen 2 und 3 in Produktionsumgebungen verwendet.

UPDATE: Erweitern Sie Option 1

Für die letzten beiden Projekte habe ich die erste Option verwendet: eine AspNetUser-Klasse, die von IdentityUser abgeleitet ist, und eine separate benutzerdefinierte Klasse namens AppUser. In meinem Fall sind die DbContexts IdentityContext bzw. DomainContext. Und ich habe die ID des AppUser folgendermaßen definiert:

public class AppUser : TrackableEntity
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
    // This Id is equal to the Id in the AspNetUser table and it's manually set.
    public override int Id { get; set; }

(TrackableEntity ist eine benutzerdefinierte abstrakte Basisklasse, die ich in der überschriebenen SaveChanges-Methode meines DomainContext-Kontexts verwende.)

Ich erstelle zuerst den AspNetUser und dann den AppUser. Der Nachteil dieses Ansatzes besteht darin, dass Sie sicherstellen müssen, dass Ihre "CreateUser" -Funktionalität transaktional ist (denken Sie daran, dass es zwei DbContexts gibt, die SaveChanges separat aufrufen). Die Verwendung von TransactionScope hat aus irgendeinem Grund bei mir nicht funktioniert, daher habe ich etwas Hässliches getan, aber das funktioniert bei mir:

        IdentityResult identityResult = UserManager.Create(aspNetUser, model.Password);

        if (!identityResult.Succeeded)
            throw new TechnicalException("User creation didn't succeed", new LogObjectException(result));

        AppUser appUser;
        try
        {
            appUser = RegisterInAppUserTable(model, aspNetUser);
        }
        catch (Exception)
        {
            // Roll back
            UserManager.Delete(aspNetUser);
            throw;
        }

(Bitte, wenn jemand einen besseren Weg findet, diesen Teil zu erledigen, schätze ich es, diese Antwort zu kommentieren oder zu bearbeiten.)

Die Vorteile sind, dass Sie die Migrationen nicht ändern müssen und jede verrückte Vererbungshierarchie über den AppUser verwenden können, ohne sich mit dem AspNetUser herumzuschlagen . Und tatsächlich verwende ich automatische Migrationen für meinen IdentityContext (den Kontext, der von IdentityDbContext abgeleitet ist):

public sealed class IdentityMigrationConfiguration : DbMigrationsConfiguration<IdentityContext>
{
    public IdentityMigrationConfiguration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = false;
    }

    protected override void Seed(IdentityContext context)
    {
    }
}

Dieser Ansatz hat auch den Vorteil, dass vermieden wird, dass Ihre n-Tier-Querschnittsentitäten von AspNetIdentity-Klassen erben.


Danke @Augusto für den umfangreichen Beitrag. Muss man muss Migrationen verwenden Option 3 zur Arbeit zu kommen? Soweit ich weiß, dienen EF-Migrationen dazu, Änderungen rückgängig zu machen? Wenn ich meine Datenbank lösche und sie dann neu erstelle und bei jedem neuen Build einsetze, muss ich das alles tun, was mit Migrationen zu tun hat?
J86

Ich habe es nicht ohne Migrationen versucht. Ich weiß nicht, ob Sie das erreichen können, ohne sie zu benutzen. Vielleicht ist es möglich. Ich musste immer Migrationen verwenden, um benutzerdefinierte Daten beizubehalten, die in die Datenbank eingefügt wurden.
Augusto Barreto

Eine Sache, auf die Sie hinweisen sollten, wenn Sie Migrationen verwenden ... sollten Sie das AddOrUpdate(new EntityObject { shoes = green})auch als "Upsert" bekannte verwenden. Im Gegensatz zum einfachen Hinzufügen zum Kontext erstellen Sie sonst nur doppelte / redundante Entitätskontextinformationen.
Chef_Code

Ich möchte mit der 3. Option arbeiten, aber ich verstehe es irgendwie nicht. Kann mir bitte jemand sagen, wie genau der IdentityContext aussehen soll? denn es kann nicht genau so sein wie in Option 2! Können Sie mir @AugustoBarreto helfen? Ich habe einen Thread über etwas Ähnliches gemacht, vielleicht kannst du mir dort helfen
Arianit

Wie sieht Ihre 'TrackableEntity' aus?
Ciaran Gallagher

224

In meinem Fall hatte ich korrekt vom IdentityDbContext geerbt (mit meinen eigenen benutzerdefinierten Typen und dem definierten Schlüssel), aber den Aufruf des OnModelCreating der Basisklasse versehentlich entfernt:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder); // I had removed this
    /// Rest of on model creating here.
}

Dadurch wurden meine fehlenden Indizes aus den Identitätsklassen behoben und ich konnte dann Migrationen generieren und Migrationen entsprechend aktivieren.


Hatte das gleiche Problem "die Leitung entfernt". Ihre Lösung hat funktioniert. :) ty.
Entwickler Marius Žilėnas

2
Dies behebt mein Problem, bei dem ich die OnModelCreating-Methode überschreiben musste, um eine benutzerdefinierte Zuordnung mit fließender API für eine komplexe Entitätsbeziehung einzuschließen. Es stellte sich heraus, dass ich vergessen habe, die Zeile in die Antwort einzufügen, bevor ich meine Zuordnung deklarierte, da ich denselben Kontext wie Identität verwende. Prost.
Dan

Es funktioniert, wenn es keine 'Override void OnModelCreating' gibt, aber wenn Sie überschreiben, müssen Sie 'base.OnModelCreating (modelBuilder)' hinzufügen. zum Override. Mein Problem wurde behoben.
Joe

13

Für diejenigen, die ASP.NET Identity 2.1 verwenden und den Primärschlüssel vom Standard stringauf entweder intoder geändert haben Guid, falls Sie noch erhalten

Für EntityType 'xxxxUserLogin' ist kein Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType.

Für EntityType 'xxxxUserRole' ist kein Schlüssel definiert. Definieren Sie den Schlüssel für diesen EntityType.

Sie haben wahrscheinlich nur vergessen, den neuen Schlüsseltyp anzugeben für IdentityDbContext:

public class AppIdentityDbContext : IdentityDbContext<
    AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
    public AppIdentityDbContext()
        : base("MY_CONNECTION_STRING")
    {
    }
    ......
}

Wenn Sie nur haben

public class AppIdentityDbContext : IdentityDbContext
{
    ......
}

oder auch

public class AppIdentityDbContext : IdentityDbContext<AppUser>
{
    ......
}

Wenn Sie versuchen, Migrationen hinzuzufügen oder die Datenbank zu aktualisieren, wird der Fehler "Kein Schlüssel definiert" angezeigt.


Ich versuche auch, die ID in ein Int zu ändern, und habe dieses Problem. Ich habe jedoch meinen DbContext geändert, um den neuen Schlüsseltyp anzugeben. Gibt es irgendwo anders, wo ich nachsehen sollte? Ich dachte, ich würde die Anweisungen sehr sorgfältig befolgen.
Kyle

1
@Kyle: Versuchen Sie, die ID aller Entitäten in int zu ändern, dh AppRole, AppUser, AppUserClaim, AppUserLogin und AppUserRole? In diesem Fall müssen Sie möglicherweise auch sicherstellen, dass Sie den neuen Schlüsseltyp für diese Klassen angegeben haben. Wie 'öffentliche Klasse AppUserLogin: IdentityUserLogin <int> {}'
David Liang

1
Dies ist das offizielle Dokument zum Anpassen des Datentyps für Primärschlüssel
AdrienTorris

1
Ja, mein Problem war, dass ich von der allgemeinen DbContext-Klasse anstelle von IdentityDbContext <AppUser> geerbt habe. Danke, das hat sehr geholfen
yibe

13

Durch Ändern des DbContext wie folgt;

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
    }

Gerade die Zugabe in OnModelCreatingzu base.OnModelCreating Methodenaufruf (Modelbuilder); und es wird gut. Ich benutze EF6.

Besonderer Dank geht an #The Senator


1
 protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            //foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
            //    relationship.DeleteBehavior = DeleteBehavior.Restrict;

            modelBuilder.Entity<User>().ToTable("Users");

            modelBuilder.Entity<IdentityRole<string>>().ToTable("Roles");
            modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserTokens");
            modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims");
            modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins");
            modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("RoleClaims");
            modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRoles");

        }
    }

0

Mein Problem war ähnlich - ich hatte eine neue Tabelle, die ich erstellte, um sie an die Identitätsbenutzer zu binden. Nachdem ich die obigen Antworten gelesen hatte, wurde mir klar, dass dies mit IsdentityUser und den geerbten Eigenschaften zu tun hatte. Ich hatte Identity bereits als eigenen Kontext eingerichtet. Um zu vermeiden, dass die beiden inhärent miteinander verknüpft werden, anstatt die zugehörige Benutzertabelle als echte EF-Eigenschaft zu verwenden, habe ich eine nicht zugeordnete Eigenschaft mit der Abfrage eingerichtet, um die zugehörigen Entitäten abzurufen. (DataManager ist so eingerichtet, dass der aktuelle Kontext abgerufen wird, in dem OtherEntity vorhanden ist.)

    [Table("UserOtherEntity")]
        public partial class UserOtherEntity
        {
            public Guid UserOtherEntityId { get; set; }
            [Required]
            [StringLength(128)]
            public string UserId { get; set; }
            [Required]
            public Guid OtherEntityId { get; set; }
            public virtual OtherEntity OtherEntity { get; set; }
        }

    public partial class UserOtherEntity : DataManager
        {
            public static IEnumerable<OtherEntity> GetOtherEntitiesByUserId(string userId)
            {
                return Connect2Context.UserOtherEntities.Where(ue => ue.UserId == userId).Select(ue => ue.OtherEntity);
            }
        }

public partial class ApplicationUser : IdentityUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> 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;
        }

        [NotMapped]
        public IEnumerable<OtherEntity> OtherEntities
        {
            get
            {
                return UserOtherEntities.GetOtherEntitiesByUserId(this.Id);
            }
        }
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.