Ответ 1
Поскольку свойство навигации для дочернего элемента определяется интерфейсом, у меня нет навигации к другому концу отношения.
Я понимаю, что это естественное ограничение, но по-прежнему ищут средства для навигации, используя абстрактные типы или дженерики.
Основная проблема в этом дизайне - это интерфейс, поскольку EF работает только с классами. Но если вы можете заменить его на абстрактный класс, и если FK в дочерних таблицах также является PK (т.е. Следуйте Shared Primary Key Asociation для представления взаимно-однозначного отношения), вы можете использовать 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
базовый класс и требование, которое должен унаследовать каждый класс сущности, сильно ограничивает параметры. TPC больше не применим из-за отношения, определяющего свойство навигации внутри базового класса (другое ограничение EF). Следовательно, единственной жизнеспособной автоматической опцией 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 с соответствующими ограничениями. Если ни один из них не применим в вашем сценарии, вам нужно прибегнуть к настраиваемым параметрам обработки кода, аналогичным тем, которые указаны в конце вопроса.