【问题标题】:Creating inheritance users from base asp.net identity user从基本 asp.net 身份用户创建继承用户
【发布时间】:2015-01-19 11:03:15
【问题描述】:

我有一个问题,我想创建 N,示例中的两个用户对象(例如客户和供应商)都是 asp.net IdentityUser 对象固有的。除了来自 IdentityUser 的数据之外,这些对象还有非常不同的附加数据。我想使用 IdentityUser 用户,因为这为我提供了一种灵活的方式来处理身份验证和授权。

这个例子已经非常精简,但应该提供足够的信息来说明无法创建一个具体的用户(例如供应商的客户)。看来我需要使用 UserManager 对象,因为它还负责创建例如密码哈希和其他安全信息。

我收到以下错误:

{“附加‘供应商’类型的实体失败,因为同一类型的另一个实体已经具有相同的主键值。当使用‘附加’方法或将实体的状态设置为如果图中的任何实体具有冲突的键值,则为“未更改”或“已修改”。这可能是因为某些实体是新的并且尚未接收到数据库生成的键值。在这种情况下,请使用“添加”方法或“添加” ' 实体状态来跟踪图表,然后根据需要将非新实体的状态设置为 'Unchanged' 或 'Modified'。"}

IdentityUser 固有的类

 public class Customer : IdentityUser
 {
    public string CustomerProperty { get; set; }
 }

 public class Supplier : IdentityUser
 {
    public string SupplierProperty { get; set; }
 }

数据库上下文类

 public class ApplicationDbContext : IdentityDbContext {

      public ApplicationDbContext() : base("ApplicationDbContext")
      {
         Database.SetInitializer(new ApplicationDbInitializer());
      }

      public DbSet<Customer> CustomerCollection { get; set; }
      public DbSet<Supplier> SupplierCollection { get; set; }
 }

引发异常的种子类

 public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
 {
    protected override void Seed(ApplicationDbContext context)
    {
        var userStore = new UserStore(context);
        var userManager = new UserManager(userStore);


        // Seed customer user which inherents from asp.net IdentityUser 
        var user = userManager.FindByEmail("customer@customer.com");
        if (user == null)
        {
            user = new User()
            {
                UserName = "customer@customer.com",
                Email = "customer@customer.com"
            };

            userManager.Create(user, userPassword);

            var customerUser = new Customer()
            {
                Id = user.Id,
                CustomerProperty = "Additional Info"
            };

            context.Entry(customerUser).State = EntityState.Modified;
            context.SaveChanges();
        }

        // Seed supplier user which inherents from asp.net IdentityUser 
        var user = userManager.FindByEmail("supplier@supplier.com");
        if (user == null)
        {
            user = new User()
            {
                UserName = "supplier@supplier.com",
                Email = "supplier@supplier.com"
            };

            userManager.Create(user, userPassword);

            var supplierUser = new Supplier()
            {
                Id = user.Id,
                IBAN = "212323424342234",
                Relationship = "OK"
            };

            context.Entry(supplierUser).State = EntityState.Modified;
            context.SaveChanges();
        }
    }
}

**** 更新 ****

以下解决方案有效,但我仍在努力解决两个问题:

  1. 我总是希望将一种用户类型(例如供应商的客户)与 IdentityUser 相关联。我想使用一个界面,但这不起作用。
  2. 如果我还在用户类型上添加了对 IdentityUser 的虚拟引用,我会得到一个“无法确定“ApplicaitonUser”和“供应商”类型之间关联的主体端。必须使用关系流式 API 或数据注释显式配置此关联的主体端。例外。

 public class Customer 
 {
    [Key]
    public int CustomerId { get;set; }
    public string CustomerProperty { get; set; }

    *public virtual User User { get; set; }*

 }

 public class Supplier 
 {
    [Key]
    public int SupplierId { get;set; }
    public string SupplierProperty { get; set; }

    *public virtual User User { get; set; }*
 }

**Class IdentityUser(有效)**

public class User : IdentityUser
{
    public virtual Supplier Supplier { get; set; }
    public virtual Customer Customer { get; set; }
}

**Class IdentityUser(我想要的)**

public class User : IdentityUser
{
    public virtual IConcreteUser ConcreteUser{ get; set; }
}

数据库上下文类

 public class ApplicationDbContext : IdentityDbContext {

      public ApplicationDbContext() : base("ApplicationDbContext")
      {
         Database.SetInitializer(new ApplicationDbInitializer());
      }

      public DbSet<Customer> CustomerCollection { get; set; }
      public DbSet<Supplier> SupplierCollection { get; set; }
 }

**播种类**

 public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext>
 {
protected override void Seed(ApplicationDbContext context)
{
    var userStore = new UserStore(context);
    var userManager = new UserManager(userStore);
    var roleManager = new RoleManager(roleStore);

    var user = userManager.FindByEmail("customer@customer.com");
    if (user == null)
    {
        user = new ApplicationUser()
        {
            UserName = "customer@customer.com",
            Email = "customer@customer.com"
            Customer = new Customer()
            {
                CustomerProperty = "Additional Info"
            }
        };

        userManager.Create(user, userPassword);
        roleManager.AddUserToRole("Customer");
    }

    user = userManager.FindByEmail("supplier@supplier.com");
    if (user == null)
    {
        user = new ApplicationUser()
        {
            UserName = "supplier@supplier.com",
            Email = "supplier@supplier.com",
            Supplier = new Supplier()
            {
                IBAN = "212323424342234",
                Relationship = "OK"
            }
        };

        userManager.Create(user, userPassword);
        roleManager.AddUserToRole("Supplier");
    }
}

}

【问题讨论】:

  • 我强烈建议修复损坏的设计。在 ASP.NET 级别上使用继承的需求为零。登录用户与您使用的任何数据库管理或功能组中的基础实体根本不同。 IE。做一个简单的 asp.net 用户,不要将 ot 绑定到您的复杂模型。将它们分开。
  • 您是否尝试按照错误中的说明设置 EntityState.Addedd 而不是 EntityState.Modified?
  • 我认为问题在于“混合状态”:它既不是EntityState.Modified也不是EntityState.Added,因为已经添加了User,但是例如Supplier不是...
  • @TomTom:如果我保留用户(例如客户和供应商),我怎么知道哪个用户登录?他们都将使用来自 IdentityUser 的凭据登录。
  • 这又是 IMO 糟糕的设计。要求供应商提供 TFA 很好,但那也很可能成为客户,不是吗?因此,如果用户是供应商,只需启用/执行 TFA。无论如何,其他安全规则应该是基于角色的 IMO...

标签: c# asp.net entity-framework asp.net-mvc-4 asp.net-identity


【解决方案1】:

我刚刚解决了一个类似的问题。我在 AppUser 中创建了一个抽象类型 DomainUser 的导航属性(继承自 Identity User)

public class AppUser : IdentityUser
{
    public DomainUser DomainUser { get; set; }
}

域用户如下所示:

public abstract class DomainUser : IAggregateRoot
{
    public Guid Id { get; set; }
    public AppUser IdentityUser { get; set; }
}

我在所有具体的域用户类型中都从 DomainUser 继承:

public class AdministrationUser : DomainUser
{
    public string SomeAdministrationProperty { get; set; }
}

public class SupplierUser : DomainUser
{
    public string SomeSupplierProperty { get; set; }
}

public class Customer : DomainUser
{
    public string SomeCustomerProperty { get; set; }
}

在 OnModelCreating 方法的 DbContext 中,我配置了实体框架,以便将从 DomainUser 继承的所有实体存储在单独的表中(称为 Table per Concrete Type)。并配置了IdentityUser和DomainUser的一对一关系:

modelBuilder.Entity<DomainUser>()
            .Map<AdministrationUser>(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("AdministrationUsers");
            })
            .Map<SupplierUser>(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("SupplierUsers");
            })
            .Map<Customer>(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("Customers");
            });

modelBuilder.Entity<DomainUser>()
            .HasRequired(domainUser => domainUser.IdentityUser)
            .WithRequiredPrincipal(groomUser => groomUser.DomainUser);

此代码将“DomainUser_Id”列添加到表 AspNetUsers 中,现在我可以访问每个域用户中的 IdentityUser 导航属性和 AppUser 中的 DomainUser 导航属性。

【讨论】:

    【解决方案2】:

    和其他人一样,我认为这是一个设计问题。有一些替代方法,例如:

    1. 使用角色定义“用户类型”(用户可以是供应商和客户)
    2. 使SupplierCustomer 实体成为关系而不是用户的扩展

    例如:

    public class ApplicationUser : IdentityUser
    {
        public virtual Customer Customer { get; set; }
        public virtual Supplier Supplier { get; set; }
    }
    
    public class Customer
    {
        [Key]
        public int Id { get; set; }
    
        public virtual ApplicationUser User { get; set; }
        public string CustomerProperty { get; set; }
    }
    
    public class Supplier
    {
        [Key]
        public int Id { get; set; }
    
        public virtual ApplicationUser User { get; set; }
        public string SupplierProperty { get; set; }
    }
    
    public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public DbSet<Customer> Customers { get; set; }
        public DbSet<Supplier> Suppliers { get; set; }
    }
    
    public class ApplicationDbInitializer
                 : DropCreateDatabaseAlways<ApplicationDbContext>
    {
        protected override void Seed(ApplicationDbContext context)
        {
            var userStore = new UserStore(context);
            var userManager = new UserManager(userStore);
            var roleManager = new RoleManager(roleStore);
    
            var user = userManager.FindByEmail("customer@customer.com");
            if (user == null)
            {
                user = new ApplicationUser()
                {
                    UserName = "customer@customer.com",
                    Email = "customer@customer.com"
                    Customer = new Customer()
                    {
                        CustomerProperty = "Additional Info"
                    }
                };
    
                userManager.Create(user, userPassword);
                roleManager.AddUserToRole("Customer");
            }
    
            user = userManager.FindByEmail("supplier@supplier.com");
            if (user == null)
            {
                user = new ApplicationUser()
                {
                    UserName = "supplier@supplier.com",
                    Email = "supplier@supplier.com",
                    Supplier = new Supplier()
                    {
                        IBAN = "212323424342234",
                        Relationship = "OK"
                    }
                };
    
                userManager.Create(user, userPassword);
                roleManager.AddUserToRole("Supplier");
            }
        }
    }
    

    在您的逻辑中,您可以执行以下操作:

    if (User.IsInRole("Customer"))
    {
        // do something
    }
    

    免责声明:这不是“复制和粘贴”示例,只是让您了解不同的方法。

    【讨论】:

    • 一个很好的例子,但供应商和客户对象不需要自己的密钥(例如供应商 ID 和客户 ID)吗?而且我还不明白如何在数据库级别知道 SupplierId、CustomerId 和 ApplicationUser 之间的关系。 ApplicationUser 是否对供应商和客户有外键或相反?除了使用虚拟属性链接对象之外,这是如何在代码中强制执行的?
    • need their own key - 是的,声明不是 c&p 示例,但我现在添加了它们。 how the relation between the SupplierId, CustomerId and the ApplicationUser is known on a database level - public virtual ApplicationUser User { get; set; } 导航属性将被 EF 转换为 FK。
    • 如果我只有 ApplicationUser 端的虚拟关系,这很好用。如果我还向供应商对象中的 ApplicationUser 添加此虚拟关系,我会得到:'无法确定类型'ApplicationUser'和'Supplier'之间关联的主体端。必须使用关系流式 API 或数据注释显式配置此关联的主体端。例外。因为我也只想要一种类型的用户(供应商/客户)与我想到的使用接口的 ApplicationUser 相关联。这不起作用?
    • 你能用你当前的代码更新问题吗?如果您正确定义它,这应该不是问题。我在“我的用户”上有多个这样的关系......
    • appending: '[Key, ForeignKey("ApplicationUser")]' 到用户类型解决了用户到应用程序之间导航的问题:)
    猜你喜欢
    • 2021-11-24
    • 2011-07-19
    • 2021-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-11
    • 1970-01-01
    • 2020-10-14
    相关资源
    最近更新 更多