实体框架(代码优先)在使用子类

时间:2017-11-22 18:18:26

标签: c# sql entity-framework

概述

我正在建立一个网络应用程序(C#,MVC,EF,Azure),帮助人们玩电脑游戏(名为" DotA"),允许他们记录他们应该如何玩游戏。 DotA是一款5v5团队游戏,每个玩家控制着100多个独特的英雄"具有各种能力和属性。

在我的应用中,您可以制作两种不同类型的音符:提示和关系。

提示

Tip是关于如何有效地扮演英雄的提示。

[Table(nameof(Tip))]
public class Tip: UserOwnedEntity
{
    /// <summary>
    /// The "HeroSubject" of a tip is the hero this tip applies to.
    /// </summary>
    public Hero HeroSubject { get; set; }
    public int? HeroSubjectId { get; set; }

    //Tips can be categorized
    public TipType Type { get; set; } = TipType.Counter;

    [Required]
    [DataType(DataType.MultilineText)]
    public string Text { get; set; }

    /// <summary>
    /// As the game changes, tips may go out of date; tracking the
    /// applicable patch assists with this, and users can flag a
    /// specific tip as out of date ("deprecated").
    /// </summary>
    [Required]
    public string Patch { get; set; }
    public bool Deprecated { get; set; } = false;

    /// <summary>
    /// Where the tip was found, possibly a URL. Can give context to
    /// a tip that isn't very intuitive.
    /// </summary>
    public string Source { get; set; }
}

UserOwnedEntity

您会注意到Tip继承自UserOwnedEntity,这是一个简单的类,包含与给定User相关的实体的共享属性:

public abstract class UserOwnedEntity
{
    public int Id { get; set; }

    public int UserId { get; set; }
    public virtual User User { get; set; }
}

关系

A Relationship对两个不同的英雄进行比较(例如&#34;英雄X与英雄Y和#34协同作用)。 Relationship是提示的超集;他们有一个主题(英雄X),但他们也有一个对象(英雄Y)。这是代码:

[Table(nameof(Relationship))]
public class Relationship: Tip
{
    public int? HeroObjectId { get; set; }
    public Hero HeroObject { get; set; }
}

注意:我已从这些类中删除了所有额外的方法,因此更容易理解数据关系。

用户

这给我们留下了User对象,它将所有这些结合在一起:

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

    [Required]
    [Index(IsUnique = true)]
    public string Username { get; set; }

    //Associated objects
    public virtual List<Hero> Heroes { get; set; }
    public virtual List<Tip> Tips { get; set; }
    public virtual List<Relationship> Relationships { get; set; }
}

英雄

编辑:添加Hero类以获得完整性。

public class Hero: UserOwnedEntity
{
    public string Name { get; set; }

    [DataType(DataType.MultilineText)]
    public string Notes { get; set; }

    public virtual List<Tip> Tips { get; set; } = new List<Tip>();

    [NotMapped]
    public virtual List<Relationship> Relationships { get; set; } = new List<Relationship>();
}

实体框架的SQL解释

实体框架创建一个正常/预期的Tip' table, but the Relationship`表,其中包含以下字段:

  • 编号
  • USER_ID
  • HeroObjectId

问题1:多个用户外键

Relationship表格不应包含User_Id字段;它已在Tip表中有一个UserId字段。实际上,Relationship可以与2个User记录相关联(一次在Tip表中,一次在Relationship表中),这是不可能的。它始终为null,因此某些查询在使用此UserId字段而不是继承字段时会失败。为什么要创建第二个外键?

查询失败的具体情况是通过Relationship访问User时,如:

var relationship = db.Users.First().Relationships.FirstOrDefault(r => r. Id == id);

问题2:提示与关系无法区分

除了检查匹配的Relation.Id之外,还无法判断Tip表中的记录是否实际为Relationship

首选解决方案

我想要的是两个不同的表:一个用于Tip,另一个用于Relationship。我不确定需要添加哪些属性组合才能实现这一目标。

或者,失败......

如果我可以删除额外的User_Id字段并使其成为提示和关系将加载就像它们在不同的表中一样,我会很高兴。实际上也许更开心。

1 个答案:

答案 0 :(得分:2)

问题是您的实体模型被识别为有资格使用EF继承策略(使用此特定配置 - Table per Type (TPT))。

  

我想要的是两个不同的表格:一个用于Tips,另一个用于Relationships

虽然可以通过使用流畅的API配置Table per Concrete Type (TPC)来实现这一点,但它无法解决导航属性/关系/ FK的问题。

这是因为只要EF看到ICollection<Tip>,它就会确保它还包含Relationships(因为Relationship Tip)。同时,为了支持ICollection<Relationship>,它将创建另一个FK。因此,在您的情况下,UserId将用于将Relationship映射到User.TipsUser_Id(按惯例默认名称) - RelationshipUser.Relationships 。请记住 - 在EF中,每个集合都映射到一个单独的关系。

因此,除了配置TPT继承之外,您还必须删除/忽略所有ICollection<Relationship>属性。或者,完全摆脱EF继承。以后无法使用数据注释/流畅的API设置,因此您需要提取类似于UserOwnedEntity的基本非实体类。像这样:

public abstract class TipEntity : UserOwnedEntity
{
    /// <summary>
    /// The "HeroSubject" of a tip is the hero this tip applies to.
    /// </summary>
    public Hero HeroSubject { get; set; }
    public int? HeroSubjectId { get; set; }

    //Tips can be categorized
    public TipType Type { get; set; } = TipType.Counter;

    [Required]
    [DataType(DataType.MultilineText)]
    public string Text { get; set; }

    /// <summary>
    /// As the game changes, tips may go out of date; tracking the
    /// applicable patch assists with this, and users can flag a
    /// specific tip as out of date ("deprecated").
    /// </summary>
    [Required]
    public string Patch { get; set; }
    public bool Deprecated { get; set; } = false;

    /// <summary>
    /// Where the tip was found, possibly a URL. Can give context to
    /// a tip that isn't very intuitive.
    /// </summary>
    public string Source { get; set; }
}

[Table(nameof(Tip))]
public class Tip : TipEntity
{
}

[Table(nameof(Relationship))]
public class Relationship : TipEntity
{
    public int? HeroObjectId { get; set; }
    public Hero HeroObject { get; set; }
}

当然,这会影响您应用的其他部分,因为Relationship不再是Tip。相反,TipRelationship现在都是TipEntity,因此TipEntity可以在与非EF相关的多态代码中服务与Tip相同的目的。