新悬赏2017/10/31
不幸的是,由于TPC的限制,这个自动接受的答案不适用于我当前的实体模型.我迫切需要找到一种通过接口或抽象类来促进双向导航的方法,所以我开始了另一项工作。
请注意,我必须使用现有的模型设计,因此重构不是一种选择。
下面的原始问题
我有一个父实体,它与多个可能的表有一对一的关系(FK在子表上)。因为子节点的导航属性是由接口定义的,所以我没有导航到关系的另一端。
我理解这是一个自然的限制,但仍然在使用抽象类型或泛型时寻求实现双向导航的方法。我遇到了许多类似我想要做的事情,但它们要么很老,要么我认为它们与我想要达到的目标不完全匹配。我寻求一个更符合我困境的最新答案。
这是我的代码,可以很容易地复制/粘贴到测试应用程序中:
编辑(作为对Ivan的回答的回应):当我尝试实现您的解决方案时,在尝试创建迁移时遇到了这个错误:
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.
因此,我似乎需要编辑我的原始代码,以反映更复杂的模型,这是我最初为了简洁而省略的。我很抱歉,因为我认为这些额外的代码直到现在才会有意义。
注意,我使所有实体现在都继承自MyEntity。
端编辑
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; }
}我想到的一个解决方案是创建一个方法,将GetById委托传递给实现ISoftwareApplicationData的实体的repos集合。我不喜欢在迭代中执行GetById的想法,但是可能只有五种类型需要这样做,所以这是一个失败的解决方案。
发布于 2017-10-21 17:11:05
因为子节点的导航属性是由接口定义的,所以我没有导航到关系的另一端。我理解这是一个自然的限制,但仍然在使用抽象类型或泛型时寻找实现导航的方法。
此设计中的主要问题是接口,因为EF只适用于类。但是,如果您可以用抽象类替换它,并且如果子表中的FK也是PK (即遵循表示一对一关系的共享主密钥协会模式),那么您可以使用EF 每种混凝土类型表(TPC)继承策略来映射现有的子表,从而允许EF自动为您提供所需的导航。
下面是修改后的样本模型(不包括不相关的ISoftwareApplicationBase和SystemConfiguration ):
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; }
}配置:
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查询:
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]不是最好看的,但为你做肮脏的工作:)
编辑: MyEntity基类和每个实体类必须从它继承的要求高度限制了选项。由于在基类中定义导航属性的关系(另一个EF限制),TPC不再适用。因此,唯一可行的自动EF选项是使用其他两种EF继承策略中的一些,但它们需要更改数据库结构。
如果您可以使用中间表来保存常见的SoftwareApplicationData属性和关系,您可以使用每种类型表(TPT)策略,如下所示:
型号:
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; }
}配置:
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继承,并使用相应的约束。如果它们都不适用于您的场景,则必须使用与问题末尾提到的选项类似的自定义代码处理选项。
https://stackoverflow.com/questions/46811017
复制相似问题