导航到一般定义的实体的正确方法 [英] Proper method of navigation to generically-defined entity

查看:203
本文介绍了导航到一般定义的实体的正确方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

新赏金2017/10/31



遗憾的是,自动接受的答案不适用于我当前的实体型号TPC限制。我迫切需要找到一种方法来促进通过接口或抽象类的双向导航,所以我开始另一个赏金。



注意我必须使用现有的模型设计,所以重构不是一种选择。



下面的原始问题



I拥有一个父实体,它与多个可能的表具有一对一的关系(FK在子表上)。因为子节点的导航属性是由接口定义的,所以我没有导航到关系的另一端。



我知道这是一个自然限制,但是在使用抽象类型或泛型时,仍然寻求实现双向导航的方法。我遇到了一些类似于我想要做的问题,但是它们要么已经很老了,要么我认为它们与我想要实现的完全匹配。我寻求一个特定于我的困境的更新答案。



这是我的代码,可以很容易地复制/粘贴到测试应用程序中:



编辑(响应Ivan Stoev的回答):当我尝试实施您的解决方案时,我在尝试创建迁移时遇到此错误:



实体类型SoftwareApplicationData和AppUser之间的关联SoftwareApplicationData_CreatedBy无效。在TPC层次结构中,只允许在大多数派生类型上使用独立关联。



所以看来我需要编辑原始代码以反映为简洁而我最初省略的更复杂的模型。我很抱歉,因为我认为其他代码到目前为止并不相关。



请注意,我现在所有实体都是从 MyEntity继承而来的



结束修改

 公共抽象类MyEntity 
{
public int Id {get;组; }

public AppUser CreatedBy {get;组; }
}

公共类AppUser:MyEntity {}

公共接口ISoftwareApplicationData
{
SoftwareApplicationBase Application {get;组; }
}

//表示系统安装和安装在其上的软件的父实体。
//集合属性是* not *我之前提到的通用实体。
公共类SystemConfiguration:MyEntity
{
public ICollection< SoftwareApplicationBase>应用程序{get;组; }
}

//代表软件本身。还有其他通用属性我为了简洁而省略了。
// Data属性表示其他特定于应用程序的属性。我需要能够
//从SoftwareApplicationBase导航到另一端的任何内容
公共类SoftwareApplicationBase:MyEntity
{
public SystemConfiguration Configuration {get;组; }

公共字符串ApplicationName {get;组; }

public ISoftwareApplicationData Data {get;组; }
}

//这是一个通用的catch-all应用程序类,遵循基本的Application / Version
//约定。大多数软件将使用此类
公共类SoftwareApplication:MyEntity,ISoftwareApplicationData
{
public SoftwareApplicationBase Application {get;组; }

public string Version {get;组; }
}

//操作系统具有特殊属性,因此它们可以获得自己的类。
公共类OperatingSystem:MyEntity,ISoftwareApplicationData
{
public SoftwareApplicationBase Application {get;组; }

public string Version {get;组; }

public string ServicePack {get;组; }
}

//另一种具有独特属性的软件
公共类VideoGame:MyEntity,ISoftwareApplicationData
{
public SoftwareApplicationBase Application {得到;组; }

public string Publisher {get;组; }

public string类型{get;组; }
}

我想到的一个解决方案是创建一个方法来传递一个GetById委托给实现 ISoftwareApplicationData 的实体的回购集合。我不喜欢在迭代中做一个GetById的想法,但可能只有五种类型我需要这样做,所以这是一个成功的解决方案,其他所有的都没有。

解决方案


因为子节点的导航属性是由接口定义的,所以我没有导航到关系的另一端。



我知道这是一个自然的限制,但仍在寻求一种在使用抽象类型或泛型时实现导航的方法。


此设计中的主要问题是界面,因为EF仅适用于类。但是如果你可以用抽象类替换它,并且如果子表中的FK也是PK(即遵循共享主键关联模式,用于表示一对一关系) ,那么你可以使用EF 每个具体类型的表(TPC)继承策略,用于映射现有子表,这反过来将允许EF提供所需的导航自动为你服务。



以下是示例修改模型(不包括 ISoftwareApplicationBase SystemConfiguration 无关紧要):

 公共类SoftwareApplicationBase 
{
public int Id {get;组; }
公共字符串ApplicationName {get;组; }
public SoftwareApplicationData Data {get;组; }
}

公共抽象类SoftwareApplicationData
{
public int ApplicationId {get;组; }
public SoftwareApplicationBase Application {get;组; }
}

公共类SoftwareApplication:SoftwareApplicationData
{
public string Version {get;组; }
}

公共类OperatingSystem:SoftwareApplicationData
{
public string Version {get;组; }
公共字符串ServicePack {get;组; }
}

公共类VideoGame:SoftwareApplicationData
{
public string Publisher {get;组; }
public string类型{get;组; }
}

配置:

  modelBuilder.Entity< SoftwareApplicationBase>()
.HasOptional(e => e.Data)
.WithRequired(e => e.Application);

modelBuilder.Entity< SoftwareApplicationData>()
.HasKey(e => e.ApplicationId);

modelBuilder.Entity< SoftwareApplication>()
.Map(m => m.MapInheritedProperties()。ToTable(SoftwareApplication));

modelBuilder.Entity< OperatingSystem>()
.Map(m => m.MapInheritedProperties()。ToTable(OperatingSystem));

modelBuilder.Entity< VideoGame>()
.Map(m => m.MapInheritedProperties()。ToTable(VideoGame));

生成的表和关系:

  CreateTable(
dbo.SoftwareApplicationBase,
c => new
{
Id = c.Int(nullable:false,identity:true ),
ApplicationName = c.String(),
})
.PrimaryKey(t => t.Id);

CreateTable(
dbo.SoftwareApplication,
c => new
{
ApplicationId = c.Int(nullable:false),
Version = c.String(),
})
.PrimaryKey(t => t.ApplicationId)
.ForeignKey(dbo.SoftwareApplicationBase,t => t。 ApplicationId)
.Index(t => t.ApplicationId);

CreateTable(
dbo.OperatingSystem,
c => new
{
ApplicationId = c.Int(nullable:false),
Version = c.String(),
ServicePack = c.String(),
})
.PrimaryKey(t => t.ApplicationId)
.ForeignKey( dbo.SoftwareApplicationBase,t => t.ApplicationId)
.Index(t => t.ApplicationId);

CreateTable(
dbo.VideoGame,
c => new
{
ApplicationId = c.Int(nullable:false),
Publisher = c.String(),
Genre = c.String(),
})
.PrimaryKey(t => t.ApplicationId)
.ForeignKey( dbo.SoftwareApplicationBase,t => t.ApplicationId)
.Index(t => t.ApplicationId);

导航测试:

  var test = db.Set< SoftwareApplicationBase>()
.Include(e => e.Data)
.ToList();

EF从上面生成的SQL查询:



< pre class =lang-sql prettyprint-override> SELECT
[Extent1]。[Id] AS [Id],
[Extent1]。[ApplicationName] AS [ApplicationName] ,
CASE WHEN([UnionAll4]。[ApplicationId] IS NULL)THEN CAST(NULL as varchar(1))WHEN([UnionAll4]。[C5] = 1)那么'2X0X'WWEN([UnionAll4]。 [C6] = 1)那么'2X1X'ELSE'2X2X'END为[C1],
[UnionAll4]。[ApplicationId] AS [C2],
CASE WHEN([UnionAll4]。[ApplicationId] IS NULL)那么CAST(NULL as varchar(1))WHEN([UnionAll4]。[C5] = 1)THEN [UnionAll4]。[C1] WHEN([UnionAll4]。[C6] = 1)THEN CAST(NULL AS varchar(1))END AS [C3],
CASE WHEN([UnionAll4]。[ApplicationId] IS NULL)THEN CAST(NULL as varchar(1))WHEN([UnionAll4]。[C5] = 1)然后[UnionAll4]。[C2] WHEN([UnionAll4]。[C6] = 1)THEN CAST(NULL as varchar(1))END AS [C4],
CASE WHEN([UnionAll4]。[ApplicationId] IS NULL)那么CAST(NULL as varchar(1))WHEN([Unio nAll4]。[C5] = 1)THEN CAST(NULL as varchar(1))WHEN([UnionAll4]。[C6] = 1)然后[UnionAll4]。[版本] END AS [C5],
CASE WHEN([UnionAll4]。[ApplicationId]为空)THEN CAST(NULL AS varchar(1))WHEN([UnionAll4]。[C5] = 1)THEN CAST(NULL AS varchar(1))WHEN([UnionAll4]。 [C6] = 1)THEN CAST(NULL as varchar(1))ELSE [UnionAll4]。[C3] END AS [C6],
CASE WHEN([UnionAll4]。[ApplicationId] IS NULL)THEN CAST( NULL AS varchar(1))WHEN([UnionAll4]。[C5] = 1)THEN CAST(NULL AS varchar(1))WHEN([UnionAll4]。[C6] = 1)THEN CAST(NULL AS varchar(1) )ELSE [UnionAll4]。[C4] END AS [C7]
FROM [dbo]。[SoftwareApplicationBase] AS [Extent1]
LEFT OUTER JOIN(SELECT
[Extent2]。[ApplicationId] AS [ApplicationId]
FROM [dbo]。[SoftwareApplication] AS [Extent2]
UNION ALL
SELECT
[Extent3]。[ApplicationId] AS [ApplicationId]
FROM [dbo]。[VideoGame] AS [Extent3]
UNION ALL
SELECT
[Extent4]。[ApplicationId] AS [ApplicationId]
FROM [dbo]。[OperatingSystem] AS [Extent4])AS [UnionAll2] ON [Extent1]。[Id] = [UnionAll2]。[ApplicationId]
LEFT OUTER JOIN(SELECT
[Extent5]。[ApplicationId] AS [ApplicationId],
CAST(NULL AS varchar(1))AS [C1],
CAST(NULL AS varchar (1))AS [C2],
[Extent5]。[版本] AS [版本],
CAST(NULL AS varchar(1))AS [C3],
CAST(NULL AS varchar(1))AS [C4],
强制转换(0为位)AS [C5],
强制转换(1为位)AS [C6]
FROM [dbo]。[ SoftwareApplication] AS [Extent5]
UNION ALL
SELECT
[Extent6]。[ApplicationId] AS [ApplicationId],
CAST(NULL AS varchar(1))AS [C1] ,
CAST(NULL AS varchar(1))AS [C2],
CAST(NULL AS varchar(1))AS [C3],
[Extent6]。[Publisher] AS [发布者],
[Extent6]。[流派] AS [流派],
演员阵容(0作为位)AS [C4],
cast(0作为位)AS [C5]
FROM [dbo]。[VideoGame] AS [Extent6]
UNION ALL
SELECT
[Extent7]。[ApplicationId] AS [ ApplicationId],
[Extent7]。[Version] AS [Version],
[Extent7]。[ServicePack] AS [ServicePack],
CAST(NULL AS varchar(1))AS [ C1],
CAST(NULL AS varchar(1))AS [C2],
CAST(NULL AS varchar(1))AS [C3],
cast(1作为位)AS [C4],
强制转换(0为位)AS [C5]
FROM [dbo]。[OperatingSystem] AS [Extent7])AS [UnionAll4] ON [Extent1]。[Id] = [UnionAll4 ]。[ApplicationId]

不是最好看的,但脏的工作对你来说:)



编辑: MyEntity 基类以及每个实体类必须从中高度继承的要求限制选项。由于在基类内定义导航属性的关系(另一个EF限制),TPC不再适用。因此,唯一可行的自动EF选项是使用其他两种EF继承策略中的一些,但它们需要更改数据库结构。



如果你能负担得起介绍持有公共 SoftwareApplicationData 属性和关系的中间表,你可以利用每种类型的表格(TPT)策略如下:



型号:

  public class SoftwareApplicationBase:MyEntity 
{
public string ApplicationName {get;组; }
public SoftwareApplicationData Data {get;组; }
}

公共抽象类SoftwareApplicationData:MyEntity
{
public SoftwareApplicationBase Application {get;组; }
}

公共类SoftwareApplication:SoftwareApplicationData
{
public string Version {get;组; }
}

公共类OperatingSystem:SoftwareApplicationData
{
public string Version {get;组; }
公共字符串ServicePack {get;组; }
}

公共类VideoGame:SoftwareApplicationData
{
public string Publisher {get;组; }
public string类型{get;组; }
}

配置:

  modelBuilder.Entity< SoftwareApplicationBase>()
.HasOptional(e => e.Data)
.WithRequired(e = > e.Application);

modelBuilder.Entity< SoftwareApplicationData>()
.ToTable(SoftwareApplicationData);

modelBuilder.Entity< SoftwareApplication>()
.ToTable(SoftwareApplication);

modelBuilder.Entity< OperatingSystem>()
.ToTable(OperatingSystem);

modelBuilder.Entity< VideoGame>()
.ToTable(VideoGame);

相关表格:

  CreateTable(
dbo.SoftwareApplicationData,
c => new
{
Id = c.Int(nullable: false),
CreatedBy_Id = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey(dbo.AppUser,t => t.CreatedBy_Id)
.ForeignKey(dbo.SoftwareApplicationBase,t => t.Id)
.Index(t => t.Id)
.Index( t => t.CreatedBy_Id);

CreateTable(
dbo.SoftwareApplication,
c => new
{
Id = c.Int(nullable:false),
Version = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey(dbo.SoftwareApplicationData,t => t。 Id)
.Index(t => t.Id);

CreateTable(
dbo.OperatingSystem,
c => new
{
Id = c.Int(nullable:false),
Version = c.String(),
ServicePack = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey( dbo.SoftwareApplicationData,t => t.Id)
.Index(t => t.Id);

CreateTable(
dbo.VideoGame,
c => new
{
Id = c.Int(nullable:false),
Publisher = c.String(),
Genre = c.String(),
})
.PrimaryKey(t => t.Id)
.ForeignKey( dbo.SoftwareApplicationData,t => t.Id)
.Index(t => t.Id);

所需的导航与以前一样,奖励允许急切加载基本导航属性:

  var test = db.Set< SoftwareApplicationBase>()
.Include(e => e.Data)
.Include(e => e.Data.CreatedBy)
.ToList();

回顾一下,在EF中获取自动导航的唯一方法是使用抽象类和EF继承,与相应的约束。如果它们都不适用于您的方案,则必须使用类似于问题末尾提到的自定义代码处理选项。


New bounty 2017/10/31

The auto-accepted answer, unfortunately, does not work with my current entity model due to TPC limitations. I am in dire need of finding a way to facilitate two-way navigation via interface or abstract class, so I am starting another bounty.

Note that I must use existing model design, so refactoring is not an option.

Original question below

I have an parent entity that has a one-to-one relationship to multiple possible tables (FK is on child tables). Because the navigation property to the child is defined by an interface, I have no navigation to the other end of the relationship.

I understand that this is a natural limitation, but still seek a means to achieve two-way navigation while using abstract types or generics. I've come across a number of questions similar to what I want to do, but they are either quite old or I don't think they are an exact match for what I am trying to achieve. I seek a more current answer that's specific to my dilemma.

This is my code and can easily be copied/pasted into a test app:

Edit (in response to Ivan Stoev's answer): When I tried implementing your solution, I got this error when trying to create migration:

The association 'SoftwareApplicationData_CreatedBy' between entity types 'SoftwareApplicationData' and 'AppUser' is invalid. In a TPC hierarchy independent associations are only allowed on the most derived types.

So it appears that I need to edit my original code to reflect the more complex model which I originally omitted for brevity. My apologies as I didn't think the additional code would be relevant until now.

Notice that I made all entities now inherit from MyEntity.

End edit

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

    public AppUser CreatedBy { get; set; }
}

public class AppUser : MyEntity { }

public interface ISoftwareApplicationData
{
    SoftwareApplicationBase Application { get; set; }
}

//Parent entity representing a system installation and the software installed on it.
//The collection property is *not* the generic entity I mentioned earlier.
public class SystemConfiguration : MyEntity
{
    public ICollection<SoftwareApplicationBase> Applications { get; set; }
}

//Represents the software itself. Has other generic attributes that I've ommitted for brevity.
//The Data property represents additional, application-specific attributes. I need to be able
//to navigate from SoftwareApplicationBase to whatever may be on the other end
public class SoftwareApplicationBase : MyEntity
{
    public SystemConfiguration Configuration { get; set; }

    public string ApplicationName { get; set; }

    public ISoftwareApplicationData Data { get; set; }
}

//This is a generic, catch-all application class that follows a basic Application/Version
//convention. Most software will use this class
public class SoftwareApplication : MyEntity, ISoftwareApplicationData
{
    public SoftwareApplicationBase Application { get; set; }

    public string Version { get; set; }
}

//Operating systems have special attributes, so they get their own class.
public class OperatingSystem : MyEntity, ISoftwareApplicationData
{
    public SoftwareApplicationBase Application { get; set; }

    public string Version { get; set; }

    public string ServicePack { get; set; }
}

//Yet another type of software with its own distinct attributes
public class VideoGame : MyEntity, ISoftwareApplicationData
{
    public SoftwareApplicationBase Application { get; set; }

    public string Publisher { get; set; }

    public string Genre { get; set; }
}

One solution I have in mind is to create a method that will pass a GetById delegate to a collection of repos of entities that implement ISoftwareApplicationData. I don't like the idea of doing a GetById within iterations, but there will probably only ever be five types for which I need to do this, so it's a tenable solution failing all else.

解决方案

Because the navigation property to the child is defined by an interface, I have no navigation to the other end of the relationship.

I understand that this is a natural limitation, but still seek a means to achieve navigation while using abstract types or generics.

The main problem in this design is the interface since EF works only with classes. But if you can replace it with abstract class, and if the FK in the child tables are also PK (i.e. follow the Shared Primary Key Asociation pattern for representing one-to-one relationship), then you can use EF Table per Concrete Type (TPC) inheritance strategy to map the existing child tables, which in turn would allow EF providing the desired navigation automatically for you.

Here is the sample modified model (excluding ISoftwareApplicationBase and SystemConfiguration which are irrelevant):

public class SoftwareApplicationBase
{
    public int Id { get; set; }
    public string ApplicationName { get; set; }
    public SoftwareApplicationData Data { get; set; }
}

public abstract class SoftwareApplicationData
{
    public int ApplicationId { get; set; }
    public SoftwareApplicationBase Application { get; set; }
}

public class SoftwareApplication : SoftwareApplicationData
{
    public string Version { get; set; }
}

public class OperatingSystem : SoftwareApplicationData
{
    public string Version { get; set; }
    public string ServicePack { get; set; }
}

public class VideoGame : SoftwareApplicationData
{
    public string Publisher { get; set; }
    public string Genre { get; set; }
}

configuration:

modelBuilder.Entity<SoftwareApplicationBase>()
    .HasOptional(e => e.Data)
    .WithRequired(e => e.Application);

modelBuilder.Entity<SoftwareApplicationData>()
    .HasKey(e => e.ApplicationId);

modelBuilder.Entity<SoftwareApplication>()
    .Map(m => m.MapInheritedProperties().ToTable("SoftwareApplication"));

modelBuilder.Entity<OperatingSystem>()
    .Map(m => m.MapInheritedProperties().ToTable("OperatingSystem"));

modelBuilder.Entity<VideoGame>()
    .Map(m => m.MapInheritedProperties().ToTable("VideoGame"));

Generated tables and relationships:

CreateTable(
    "dbo.SoftwareApplicationBase",
    c => new
        {
            Id = c.Int(nullable: false, identity: true),
            ApplicationName = c.String(),
        })
    .PrimaryKey(t => t.Id);

CreateTable(
    "dbo.SoftwareApplication",
    c => new
        {
            ApplicationId = c.Int(nullable: false),
            Version = c.String(),
        })
    .PrimaryKey(t => t.ApplicationId)
    .ForeignKey("dbo.SoftwareApplicationBase", t => t.ApplicationId)
    .Index(t => t.ApplicationId);

CreateTable(
    "dbo.OperatingSystem",
    c => new
        {
            ApplicationId = c.Int(nullable: false),
            Version = c.String(),
            ServicePack = c.String(),
        })
    .PrimaryKey(t => t.ApplicationId)
    .ForeignKey("dbo.SoftwareApplicationBase", t => t.ApplicationId)
    .Index(t => t.ApplicationId);

CreateTable(
    "dbo.VideoGame",
    c => new
        {
            ApplicationId = c.Int(nullable: false),
            Publisher = c.String(),
            Genre = c.String(),
        })
    .PrimaryKey(t => t.ApplicationId)
    .ForeignKey("dbo.SoftwareApplicationBase", t => t.ApplicationId)
    .Index(t => t.ApplicationId);

Navigation test:

var test = db.Set<SoftwareApplicationBase>()
    .Include(e => e.Data)
    .ToList();

EF generated SQL query from the above:

SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[ApplicationName] AS [ApplicationName],
    CASE WHEN ([UnionAll4].[ApplicationId] IS NULL) THEN CAST(NULL AS varchar(1)) WHEN ([UnionAll4].[C5] = 1) THEN '2X0X' WHEN ([UnionAll4].[C6] = 1) THEN '2X1X' ELSE '2X2X' END AS [C1],
    [UnionAll4].[ApplicationId] AS [C2],
    CASE WHEN ([UnionAll4].[ApplicationId] IS NULL) THEN CAST(NULL AS varchar(1)) WHEN ([UnionAll4].[C5] = 1) THEN [UnionAll4].[C1] WHEN ([UnionAll4].[C6] = 1) THEN CAST(NULL AS varchar(1)) END AS [C3],
    CASE WHEN ([UnionAll4].[ApplicationId] IS NULL) THEN CAST(NULL AS varchar(1)) WHEN ([UnionAll4].[C5] = 1) THEN [UnionAll4].[C2] WHEN ([UnionAll4].[C6] = 1) THEN CAST(NULL AS varchar(1)) END AS [C4],
    CASE WHEN ([UnionAll4].[ApplicationId] IS NULL) THEN CAST(NULL AS varchar(1)) WHEN ([UnionAll4].[C5] = 1) THEN CAST(NULL AS varchar(1)) WHEN ([UnionAll4].[C6] = 1) THEN [UnionAll4].[Version] END AS [C5],
    CASE WHEN ([UnionAll4].[ApplicationId] IS NULL) THEN CAST(NULL AS varchar(1)) WHEN ([UnionAll4].[C5] = 1) THEN CAST(NULL AS varchar(1)) WHEN ([UnionAll4].[C6] = 1) THEN CAST(NULL AS varchar(1)) ELSE [UnionAll4].[C3] END AS [C6],
    CASE WHEN ([UnionAll4].[ApplicationId] IS NULL) THEN CAST(NULL AS varchar(1)) WHEN ([UnionAll4].[C5] = 1) THEN CAST(NULL AS varchar(1)) WHEN ([UnionAll4].[C6] = 1) THEN CAST(NULL AS varchar(1)) ELSE [UnionAll4].[C4] END AS [C7]
    FROM   [dbo].[SoftwareApplicationBase] AS [Extent1]
    LEFT OUTER JOIN  (SELECT
        [Extent2].[ApplicationId] AS [ApplicationId]
        FROM [dbo].[SoftwareApplication] AS [Extent2]
    UNION ALL
        SELECT
        [Extent3].[ApplicationId] AS [ApplicationId]
        FROM [dbo].[VideoGame] AS [Extent3]
    UNION ALL
        SELECT
        [Extent4].[ApplicationId] AS [ApplicationId]
        FROM [dbo].[OperatingSystem] AS [Extent4]) AS [UnionAll2] ON [Extent1].[Id] = [UnionAll2].[ApplicationId]
    LEFT OUTER JOIN  (SELECT
        [Extent5].[ApplicationId] AS [ApplicationId],
        CAST(NULL AS varchar(1)) AS [C1],
        CAST(NULL AS varchar(1)) AS [C2],
        [Extent5].[Version] AS [Version],
        CAST(NULL AS varchar(1)) AS [C3],
        CAST(NULL AS varchar(1)) AS [C4],
        cast(0 as bit) AS [C5],
        cast(1 as bit) AS [C6]
        FROM [dbo].[SoftwareApplication] AS [Extent5]
    UNION ALL
        SELECT
        [Extent6].[ApplicationId] AS [ApplicationId],
        CAST(NULL AS varchar(1)) AS [C1],
        CAST(NULL AS varchar(1)) AS [C2],
        CAST(NULL AS varchar(1)) AS [C3],
        [Extent6].[Publisher] AS [Publisher],
        [Extent6].[Genre] AS [Genre],
        cast(0 as bit) AS [C4],
        cast(0 as bit) AS [C5]
        FROM [dbo].[VideoGame] AS [Extent6]
    UNION ALL
        SELECT
        [Extent7].[ApplicationId] AS [ApplicationId],
        [Extent7].[Version] AS [Version],
        [Extent7].[ServicePack] AS [ServicePack],
        CAST(NULL AS varchar(1)) AS [C1],
        CAST(NULL AS varchar(1)) AS [C2],
        CAST(NULL AS varchar(1)) AS [C3],
        cast(1 as bit) AS [C4],
        cast(0 as bit) AS [C5]
        FROM [dbo].[OperatingSystem] AS [Extent7]) AS [UnionAll4] ON [Extent1].[Id] = [UnionAll4].[ApplicationId]

Not the best looking, but does the dirty work for you :)

Edit: MyEntity base class and the requirement that every entity class must inherit from it highly limits the options. TPC is no more applicable because of the relationship defining navigation property inside the base class (another EF limitation). Hence the only viable automatic EF option is to use some of the other two EF inheritance strategies, but they require changing the database structure.

In case you can afford introducing intermediate table holding the common SoftwareApplicationData properties and relationships, you can utilize the Table Per Type (TPT) strategy as follows:

Model:

public class SoftwareApplicationBase : MyEntity
{
    public string ApplicationName { get; set; }
    public SoftwareApplicationData Data { get; set; }
}

public abstract class SoftwareApplicationData : MyEntity
{
    public SoftwareApplicationBase Application { get; set; }
}

public class SoftwareApplication : SoftwareApplicationData
{
    public string Version { get; set; }
}

public class OperatingSystem : SoftwareApplicationData
{
    public string Version { get; set; }
    public string ServicePack { get; set; }
}

public class VideoGame : SoftwareApplicationData
{
    public string Publisher { get; set; }
    public string Genre { get; set; }
}

Configuration:

modelBuilder.Entity<SoftwareApplicationBase>()
    .HasOptional(e => e.Data)
    .WithRequired(e => e.Application);

modelBuilder.Entity<SoftwareApplicationData>()
    .ToTable("SoftwareApplicationData");

modelBuilder.Entity<SoftwareApplication>()
    .ToTable("SoftwareApplication");

modelBuilder.Entity<OperatingSystem>()
    .ToTable("OperatingSystem");

modelBuilder.Entity<VideoGame>()
    .ToTable("VideoGame");

Relevant tables:

CreateTable(
    "dbo.SoftwareApplicationData",
    c => new
        {
            Id = c.Int(nullable: false),
            CreatedBy_Id = c.Int(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.AppUser", t => t.CreatedBy_Id)
    .ForeignKey("dbo.SoftwareApplicationBase", t => t.Id)
    .Index(t => t.Id)
    .Index(t => t.CreatedBy_Id);

CreateTable(
    "dbo.SoftwareApplication",
    c => new
        {
            Id = c.Int(nullable: false),
            Version = c.String(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.SoftwareApplicationData", t => t.Id)
    .Index(t => t.Id);

CreateTable(
    "dbo.OperatingSystem",
    c => new
        {
            Id = c.Int(nullable: false),
            Version = c.String(),
            ServicePack = c.String(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.SoftwareApplicationData", t => t.Id)
    .Index(t => t.Id);

CreateTable(
    "dbo.VideoGame",
    c => new
        {
            Id = c.Int(nullable: false),
            Publisher = c.String(),
            Genre = c.String(),
        })
    .PrimaryKey(t => t.Id)
    .ForeignKey("dbo.SoftwareApplicationData", t => t.Id)
    .Index(t => t.Id);

The desired navigation is as before, with the bonus allowing eager loading of the base navigation properties:

var test = db.Set<SoftwareApplicationBase>()
    .Include(e => e.Data)
    .Include(e => e.Data.CreatedBy)
    .ToList();

To recap, the only way to get automatic navigation in EF is to use abstract class and EF inheritance, with the corresponding constraints. If none of them is applicable in your scenario, you have to resort to custom code handling options similar to the one mentioned at the end of the question.

这篇关于导航到一般定义的实体的正确方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆