9

我在 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
4

1 回答 1

10

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

public class User : TrackableEntity

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

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

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

现在,这个问题可以通过显式定义主体和依赖来解决 - 正如异常所表明的那样。但是在您的模型中,约定 - 即假设一对一的关系 - 是不正确的。您实际上需要两种关系,一种来自CreatedBy(使用自己的外键),另一种来自ModifiedBy(使用另一个外键)。两种关系都是一对多的(因为 aUser可以是许多其他用户的创建者或修改者)并且在关系的另一端没有导航集合。您必须使用 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 可以为空),因为 - 至少对于自动递增的 Ids - 您不能使用 aCreatedBy或被ModifiedBy设置来创建第一个用户 - 它只能指本身,但没有任何有效的 FK 值可以使用,因为第一个 PK 尚未创建。

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

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

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只是一个基类,并且基类中的每个属性都将被视为派生实体中的属性并映射到数据库表。

于 2012-10-31T22:42:15.797 回答