【问题标题】:Entity Framework Code First Base Class Not Mapped To DB实体框架代码第一个基类未映射到数据库
【发布时间】:2012-10-20 11:42:35
【问题描述】:

我在 C# 中首先使用实体​​框架代码,并且我有许多实体具有用于跟踪的相同列。我使用的列是 Active、IsDeleted、CreatedBy、ModifiedBy、DateCreated 和 DateUpdated。必须将这些属性添加到我想要跟踪的每个实体中似乎很乏味。我想要一个我的实体可以继承的基类,如下所示。

public abstract class TrackableEntity
{
    public bool Active { get; set; }
    public bool IsDeleted { get; set; }
    public virtual User CreatedBy { get; set; }
    public virtual User ModifiedBy { get; set; }
    public DateTime DateCreated { get; set; }
    public DateTime DateModified { get; set; }
}

然后我可以从我的实体中的此类继承以获得这些属性,并且当数据库生成时,它将为每个实体提供这些列。

public class UserProfile : TrackableEntity, IValidatableObject
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }

    public bool IsValid { get { return this.Validate(null).Count() == 0; } }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (String.IsNullOrEmpty(FirstName))
            yield return new ValidationResult("First name cannot be blank", new[] { "Username" });

        if (String.IsNullOrEmpty(LastName))
            yield return new ValidationResult("Last cannot be blank", new[] { "Password" });

        //Finish Validation Rules
    }
}

我真的很想减少代码重复并使用类似这样的某种方法,但我无法让它发挥作用。我不断收到以下错误:

无法确定类型“Namespace.Models.User”和“Namespace.Models.User”之间关联的主体端。此关联的主体端必须使用关系流式 API 或数据注释显式配置。

我已经四处寻找了几天,但找不到我想做的事情的答案。我真的不想创建一个所有内容都链接到的单独表。我读过 TPT、TPH 和 TPC。 TPC 似乎非常接近我想要的,但在我几乎所有的桌子上共享 PK 是我绝对不想做的。

这是我希望我的表看起来像的示例。这些表将由继承自 TrackableEntity 的实体创建。

[UserProfile]
    [Id] [int] IDENTITY(1,1) NOT NULL
    [FirstName] [varchar](50) NOT NULL
    [LastName] [varchar](50) NOT NULL
    [Email] [varchar](255) NOT NULL
    [Phone] [varchar](20) NOT NULL
    [CreatedBy] [int] NOT NULL
    [ModifiedBy] [int] NOT NULL
    [DateCreated] [datetime] NOT NULL
    [DateModified] [datetime] NOT NULL
    [Active] [bit] NOT NULL
    [IsDeleted] [bit] NOT NULL

[CaseType]
    [Id] [int] IDENTITY(1,1) NOT NULL
    [Name] [varchar](50) NOT NULL
    [CreatedBy] [int] NOT NULL
    [ModifiedBy] [int] NOT NULL
    [DateCreated] [datetime] NOT NULL
    [DateModified] [datetime] NOT NULL
    [Active] [bit] NOT NULL
    [IsDeleted] [bit] NOT NULL

【问题讨论】:

  • 您希望 TrackableEntity 的所有属性在每个表上都是非键列吗?
  • 没错。我更新了我的问题以显示我正在寻找的表结构。
  • 这种表结构在 SQL Server 中是不可能的,因为它在User 表中引入了循环关系。

标签: sql-server-2008-r2 ef-code-first entity-framework-5


【解决方案1】:

您很可能遇到此异常,因为您的 User 类也派生自 TrackableEntity

public class User : TrackableEntity

结果是User 实体现在包含两个继承的属性

public virtual User CreatedBy { get; set; }
public virtual User ModifiedBy { get; set; }

和实体框架按照惯例将假定两个属性之间的关系,即 one 关系具有这两个导航属性作为其两端。因为导航属性是引用而不是集合,所以 EF 推断出 一对一 关系。因为在模型中没有指定两个导航属性中的哪一个具有关系的相关外键,所以 EF 无法确定关系的主体和依赖关系 - 这会导致异常。

现在,这个问题可以通过显式定义主体和依赖来解决 - 正如异常所表明的那样。但是在您的模型中,约定 - 即假设 一对一 关系 - 是不正确的。您实际上需要 两个 关系,一个来自 CreatedBy(带有自己的外键),一个来自 ModifiedBy(带有另一个外键)。两种关系一对多(因为User 可以是许多其他用户的创建者或修改者)并且在关系的另一端没有导航集合。您必须使用 Fluent API 提供映射以覆盖约定并定义这两个一对多关系:

public class UnicornsContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<UserProfile> UserProfiles { get; set; }
    public DbSet<CaseType> CaseTypes { get; set; }
    // ... etc.

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
            .HasOptional(u => u.CreatedBy)
            .WithMany()
            .Map(m => m.MapKey("CreatedBy")); // FK column name in DB table

        modelBuilder.Entity<User>()
            .HasOptional(u => u.ModifiedBy)
            .WithMany()
            .Map(m => m.MapKey("ModifiedBy")); // FK column name in DB table
    }
}

请注意,我使用 HasOptional 而不是 HasRequired(这意味着数据库中的 FK 可以为空),因为 - 至少使用自动递增的 Id - 您无法创建第一个用户CreatedByModifiedBy 正在设置 - 它只能引用自身,但没有任何有效的 FK 值可供使用,因为尚未创建第一个 PK。

(您还可以考虑完全避免 CreatedByModifiedBy 的关系和引用约束,而仅将用户名存储为创建者和修改者,因为出于审计和跟踪目的,保存唯一的可能就足够了人的身份。你真的需要从每个实体导航到它的创建者和修改者吗?即使在特殊情况下需要它,你仍然可以手动加入用户表。这会不会是个问题如果用户名仍存储在CreatedByModifiedBy 中,那么该用户是否从Users 表中删除?)

只要不为抽象基类引入DbSet,就不必处理任何继承映射...

public DbSet<TrackableEntity> TrackableEntities { get; set; } // NO!

...或该类的 Fluent API 中的映射...

modelBuilder.Entity<TrackableEntity>()... // NO!

...或将该类用作任何其他类中的导航属性:

public class SomeEntity
{
    //...
    public virtual TrackableEntity Something { get; set; } // NO!
    public virtual ICollection<TrackableEntity> Somethings { get; set; } // NO!
}

任何这些 EF 都会将 TrackableEntity 推断为一个实体,并在模型和数据库表之间引入继承映射(默认为 TPH)。否则TrackableEntity 只是一个基类,并且基类中的每个属性都将被视为派生实体中的属性并映射到数据库表。

【讨论】:

  • 这是我需要的。猜猜我对继承的想法是正确的,只是没有完全掌握与用户表和我的 TrackableEntity 的关系。感谢您的帮助!
猜你喜欢
  • 2011-12-17
  • 1970-01-01
  • 1970-01-01
  • 2014-06-11
  • 2010-10-14
  • 2012-11-10
  • 2011-08-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多