【问题标题】:Have an Entity Framework field reference one of several derived tables via a discriminator (Using TPT)让实体框架字段通过鉴别器引用几个派生表之一(使用 TPT)
【发布时间】:2013-09-12 00:50:47
【问题描述】:

我正在尝试从 Entity Framework 5 的 Code First 中找到一种方法来创建这种关系。

在下面的示例中,我有一些 Stock 可以属于 Warehouse 或 Row。

库存表

'Id', 'SerialNo', 'Discriminator', 'LocationId'
345, ABC123, Warehouse, 4  
123, ABC124, Row, 12

仓库表

'Id', 'Name'  
4, WH-One  
8, WH-Two

行表

'Id', 'Name'
6, RowA  
12, RowB

Stock 表上的Discriminator 列确定LocationId 所指的相关表。例如:
Stock Id '345' 引用了 Warehouse 上 ID 为 4 (WH-One) 的行表

这种类型的关系可以用 EF Code First 映射吗?在 SQL Server 中查询这种结构当然没有问题。

SELECT Stock.SerialNo, COALESCE(Warehouse.Name, Row.Name) AS Name
FROM Stock
LEFT OUTER JOIN Warehouse ON Stock.Id = Warehouse.Id AND Stock.Discriminator = 'Warehouse'
LEFT OUTER JOIN Row ON Stock.Id = Row.Id AND Stock.Discriminator = 'Row'

应该返回:

SerialNo, Name
ABC123, WH-One
ABC124, RowB

理想情况下,我希望有一个接口或抽象基类,Warehouse 和 Row 并从中派生。还有一个名为 Location 的导航属性,该属性将引用转换为抽象/接口的任一表。

或者,我想知道是否有人有更好的建议来描述 EF Code First 中允许一个实体引用其他几个类似实体之一的关系。

【问题讨论】:

    标签: sql entity-framework ef-code-first


    【解决方案1】:

    这是代码优先,所以我会这样做:

    First - 在代码中创建我想要的类:

    public abstract class Location
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public ICollection<Stock> StockItems { get; set; }
    }
    
    public class Warehouse : Location
    {
        public int NumberOfLoadingDocks { get; set; }
    }
    
    public class Row : Location
    {
        public int NumberOfShelves { get; set; }
    }
    
    public class Stock
    {
        public int ID { get; set; }
        public string SerialNo { get; set; }
        public int LocationID { get; set; }
    }
    

    然后将它们添加到上下文中:

    public partial class MyContext : DbContext
    {
        public DbSet<Location> Locations { get; set; }
        public DbSet<Warehouse> Warehouses { get; set; }
        public DbSet<Row> Rows { get; set; }
        public DbSet<Stock> Stocks { get; set; }
    }
    

    然后在 Package-Manager 控制台中:

    Add-Migration "Locations"
    Update-Database -Script
    

    这让我看到了 Entity Framework 将创建的表:

    CREATE TABLE [dbo].[Stocks] (
        [ID] [int] NOT NULL IDENTITY,
        [SerialNo] [nvarchar](max),
        [LocationID] [int] NOT NULL,
        CONSTRAINT [PK_dbo.Stocks] PRIMARY KEY ([ID])
    )
    CREATE INDEX [IX_LocationID] ON [dbo].[Stocks]([LocationID])
    CREATE TABLE [dbo].[Locations] (
        [ID] [int] NOT NULL IDENTITY,
        [Name] [nvarchar](max),
        [NumberOfShelves] [int],
        [NumberOfLoadingDocks] [int],
        [Discriminator] [nvarchar](128) NOT NULL,
        CONSTRAINT [PK_dbo.Locations] PRIMARY KEY ([ID])
    )
    ALTER TABLE [dbo].[Stocks] ADD CONSTRAINT [FK_dbo.Stocks_dbo.Locations_LocationID] FOREIGN KEY ([LocationID]) REFERENCES [dbo].[Locations] ([ID])
    INSERT [dbo].[__MigrationHistory]([MigrationId], [ContextKey], [Model], [ProductVersion])
    VALUES (N'201309120832257_Locations'....
    

    与您拥有的表不同。这是Table-per-Hierarchy inheritance。如果您对此不满意,则需要查看Table Per Type inheritance

    编辑 要获得 TPT 继承,您可以将 Table 属性添加到继承的类中,如下所示:

       [System.ComponentModel.DataAnnotations.Schema.Table("Warehouses")]
        public class Warehouse : Location
        {
            public int NumberOfLoadingDocks { get; set; }
        }
    
        [System.ComponentModel.DataAnnotations.Schema.Table("Rows")]
        public class Row : Location
        {
            public int NumberOfShelves { get; set; }
        }
    

    现在您可以为每种类型获得单独的表格:

    CREATE TABLE [dbo].[Locations] (
        [ID] [int] NOT NULL IDENTITY,
        [Name] [nvarchar](max),
        CONSTRAINT [PK_dbo.Locations] PRIMARY KEY ([ID])
    )
    CREATE TABLE [dbo].[Rows] (
        [ID] [int] NOT NULL,
        [NumberOfShelves] [int] NOT NULL,
        CONSTRAINT [PK_dbo.Rows] PRIMARY KEY ([ID])
    )
    CREATE INDEX [IX_ID] ON [dbo].[Rows]([ID])
    CREATE TABLE [dbo].[Warehouses] (
        [ID] [int] NOT NULL,
        [NumberOfLoadingDocks] [int] NOT NULL,
        CONSTRAINT [PK_dbo.Warehouses] PRIMARY KEY ([ID])
    )
    CREATE INDEX [IX_ID] ON [dbo].[Warehouses]([ID])
    ALTER TABLE [dbo].[Stocks] ADD CONSTRAINT [FK_dbo.Stocks_dbo.Locations_LocationID] FOREIGN KEY ([LocationID]) REFERENCES [dbo].[Locations] ([ID])
    ALTER TABLE [dbo].[Rows] ADD CONSTRAINT [FK_dbo.Rows_dbo.Locations_ID] FOREIGN KEY ([ID]) REFERENCES [dbo].[Locations] ([ID])
    ALTER TABLE [dbo].[Warehouses] ADD CONSTRAINT [FK_dbo.Warehouses_dbo.Locations_ID] FOREIGN KEY ([ID]) REFERENCES [dbo].[Locations] ([ID])
    

    如果您对此不满意,请查看Table Per Concrete Type Inheritance...

    【讨论】:

    • 感谢 Colin,不过我一直在使用 Table-per-Type 继承链。您从 Location 派生的表越多,TPH 就会越混乱。大多数列为空。我怀疑这将是最佳性能,只是不是很规范或优雅。我认为我宁愿在 Stock 表上使用冗余外键,也不愿在 Location 表上使用冗余列。
    • 如果您想要每种类型的表,然后将属性添加到派生表。我在编辑中添加了一个示例
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-22
    • 2013-12-04
    • 1970-01-01
    相关资源
    最近更新 更多