Entity Framework/Core und LINQ to Entities (2) Modellierungsdatenbank:Objektrelationale Zuordnung

Entity Framework/Core und LINQ to Entities (2) Modellierungsdatenbank:Objektrelationale Zuordnung

[LINQ via C#-Reihe]

[Entity Framework Core-Reihe]

[Entity Framework-Reihe]

Neueste EF Core-Version dieses Artikels: https://weblogs.asp.net/dixin/entity-framework-core-and-linq-to-entities-2-modeling-database-object-relational-mapping

EF-Version dieses Artikels: https://weblogs.asp.net/dixin/entity-framework-and-linq-to-entities-3-logging

.NET- und SQL-Datenbank und haben 2 verschiedene Datentypsysteme. Beispielsweise hat .NET System.Int64 und System.String, während die SQL-Datenbank bigint und nvarchar hat; .NET hat Sequenzen und Objekte, während die SQL-Datenbank Tabellen und Zeilen hat usw. Objektrelationales Mapping ist eine beliebte Technologie zum Zuordnen und Konvertieren zwischen Anwendungsdatenobjekten und relationalen Datenbankdaten. In LINQ to Entities basieren die Abfragen auf objektrelationaler Zuordnung.

Im Vergleich zur Codegenerierung aus Entitätsdatenmodellen (.edmx) ist es intuitiver und transparenter, Code von Grund auf neu zu erstellen. Auch in Bezug auf EF Core, das keine Entitätsdatenmodelle (.edmx) unterstützt und nur Code zuerst unterstützt, folgt dieses Tutorial dem Code-First-Ansatz.

Datentypen

EF/Core kann die meisten SQL-Datentypen .NET-Typen zuordnen:

SQL-Typkategorie SQL-Typ .NET-Typ C#-Primitiv
Genau numerisch Bit System.Boolesch Bool
kleine Zahl System.Byte Byte
smallint System.Int16 kurz
int System.Int32 int
große Ganzzahl System.Int64 lang
kleingeld, geld, dezimal, numerisch System.Dezimal dezimal
Ungefähre Zahl echt System.Single schweben
schweben System.Double doppelt
Zeichenkette Zeichen, Varchar, Text System.String Zeichenfolge
nchar, nvarchar, ntext System.String Zeichenfolge
Binärer String binär, varbinär System.Byte[] Byte[]
Bild System.Byte[] Byte[]
Zeilenversion (Zeitstempel) System.Byte[] Byte[]
Datum Uhrzeit Datum System.DateTime
Zeit System.Zeitspanne
smalldatetime, datetime, datetime2 System.DateTime
datetimeoffset System.DateTimeOffset
Räumlicher Typ Geographie System.Data.Entity.Spatial.DbGeography*
Geometrie System.Data.Entity.Spatial.DbGeometry*
Andere Hierarchie-ID Keine integrierte Zuordnung oder Unterstützung
xml System.String Zeichenfolge
eindeutige Kennung System.Guid
sql_variante Keine integrierte Zuordnung oder Unterstützung

Datenbank

Eine SQL-Datenbank wird einem Typ zugeordnet, der von DbContext:

abgeleitet ist
public partial class AdventureWorks : DbContext { }

DbContext wird bereitgestellt als:

namespace Microsoft.EntityFrameworkCore
{
    public class DbContext : IDisposable, IInfrastructure<IServiceProvider>
    {
        public DbContext(DbContextOptions options);

        public virtual ChangeTracker ChangeTracker { get; }

        public virtual DatabaseFacade Database { get; }

        public virtual void Dispose();

        public virtual int SaveChanges();

        public virtual DbSet<TEntity> Set<TEntity>() where TEntity : class;

        protected internal virtual void OnModelCreating(ModelBuilder modelBuilder);

        // Other members.
    }
}

DbContext implementiert IDisposable. Im Allgemeinen sollte für jede Arbeitseinheit eine Datenbankinstanz erstellt und verworfen werden – eine Sammlung von Datenoperationen, die als Einheit erfolgreich sein oder fehlschlagen sollten:

internal static void Dispose()
{
    using (AdventureWorks adventureWorks = new AdventureWorks())
    {
        // Unit of work.
    }
}

In EF/Core kann der Großteil der objektrelationalen Zuordnung deklarativ implementiert werden, und der Rest der Zuordnung kann zwingend implementiert werden, indem DbContext.OnModelCreating überschrieben wird, das von EF/Core beim Initialisieren der Entitätsmodelle aufgerufen wird:

public partial class AdventureWorks
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        MapCompositePrimaryKey(modelBuilder);
        MapManyToMany(modelBuilder);
        MapDiscriminator(modelBuilder);
    }
}

Die obigen Methoden MapCompositePrimaryKey, MapManyToMany, MapDiscriminator werden bald später implementiert.

Verbindungsresilienz und Ausführungswiederholungsstrategie

Als Abbildung der Datenbank verwaltet AdventureWorks auch die Verbindung zur Datenbank, die vom Konstruktor injiziert werden kann:

public partial class AdventureWorks
{
    public AdventureWorks(DbConnection connection = null)
        : base(new DbContextOptionsBuilder<AdventureWorks>().UseSqlServer(
            connection: connection ?? new SqlConnection(ConnectionStrings.AdventureWorks),
            sqlServerOptionsAction: options => options.EnableRetryOnFailure(
                maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null)).Options) { }
}

Wenn dem Konstruktor hier keine Datenbankverbindung bereitgestellt wird, wird eine neue Datenbankverbindung mit der zuvor definierten Verbindungszeichenfolge erstellt. Auch in Bezug auf die Verbindung zwischen Anwendung und SQL-Datenbank, die unterbrochen werden kann (aufgrund des Netzwerks usw.), unterstützt EF/Core die Ausfallsicherheit der Verbindung für die SQL-Datenbank. Dies ist besonders hilfreich für Azure SQL-Datenbanken, die in der Cloud statt im lokalen Netzwerk bereitgestellt werden. Im obigen Beispiel ist EF Core so angegeben, dass es automatisch bis zu fünf Wiederholungen mit einem Wiederholungsintervall von 30 Sekunden durchführt.

Tabellen

Es gibt Dutzende von Tabellen in der AdventureWorks-Datenbank, aber keine Panik, dieses Tutorial umfasst nur ein paar Tabellen und einige Spalten dieser Tabellen. In EF/Core kann eine Tabellendefinition einer Entitätstypdefinition zugeordnet werden, wobei jede Spalte einer Entitätseigenschaft zugeordnet wird. Beispielsweise hat die AdventureWorks-Datenbank eine Production.ProductCategory-Tabelle, die wie folgt definiert ist:

CREATE SCHEMA [Production];
GO

CREATE TYPE [dbo].[Name] FROM nvarchar(50) NULL;
GO

CREATE TABLE [Production].[ProductCategory](
    [ProductCategoryID] int IDENTITY(1,1) NOT NULL
        CONSTRAINT [PK_ProductCategory_ProductCategoryID] PRIMARY KEY CLUSTERED,

    [Name] [dbo].[Name] NOT NULL, -- nvarchar(50).

    [rowguid] uniqueidentifier ROWGUIDCOL NOT NULL -- Ignored in mapping.
        CONSTRAINT [DF_ProductCategory_rowguid] DEFAULT (NEWID()),
    
    [ModifiedDate] datetime NOT NULL -- Ignored in mapping.
        CONSTRAINT [DF_ProductCategory_ModifiedDate] DEFAULT (GETDATE()));
GO

Diese Tabellendefinition kann einer ProductCategory-Entitätsdefinition zugeordnet werden:

public partial class AdventureWorks
{
    public const string Production = nameof(Production); // Production schema.
}

[Table(nameof(ProductCategory), Schema = AdventureWorks.Production)]
public partial class ProductCategory
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ProductCategoryID { get; set; }

    [MaxLength(50)]
    [Required]
    public string Name { get; set; }

    // Other columns are ignored.
}

Das Attribut [Table] gibt den Tabellennamen und das Schema an. [Table] kann weggelassen werden, wenn der Tabellenname mit dem Entitätsnamen identisch ist und die Tabelle unter dem Standard-dbo-Schema liegt. In der Tabellenentitätszuordnung:

  • Die ProductCategoryID-Spalte vom int-Typ wird einer System.Int32-Eigenschaft mit demselben Namen zugeordnet. Das Attribut [Schlüssel] gibt an, dass es sich um einen Primärschlüssel handelt. EF/Core erfordert, dass eine Tabelle über einen zuzuordnenden Primärschlüssel verfügt. [DatabaseGenerated] gibt an, dass es sich um eine Identitätsspalte handelt, deren Wert von der Datenbank generiert wird.
  • Die Spalte Name ist vom Typ dbo.Name. Das ist eigentlich nvarchar(50), also wird es der Name-Eigenschaft vom Typ System.String zugeordnet. Das Attribut [MaxLength] gibt an, dass die maximale Länge des Zeichenfolgenwerts 50 beträgt. [Erforderlich] gibt an, dass es sich nicht um eine Null- oder Leerzeichenfolge oder eine Leerzeichenzeichenfolge handeln sollte.
  • Die anderen Spalten rowguid und ModifiedDate werden nicht gemappt. Sie werden in diesem Tutorial nicht verwendet, um die Codebeispiele einfach zu halten.

Zur Laufzeit wird jede Zeile der Production.ProductCategory-Tabelle einer ProductCategory-Instanz zugeordnet.

Die Zeilen der gesamten Tabelle können Objekten in einer IQueryable-Datenquelle zugeordnet werden, die als Eigenschaft des Datenbanktyps verfügbar gemacht wird. DbSet implementiert IQueryable und wird bereitgestellt, um eine Tabellendatenquelle darzustellen:

public partial class AdventureWorks
{
    public DbSet<ProductCategory> ProductCategories { get; set; }
}

Beziehungen

In der SQL-Datenbank können Tabellen Fremdschlüsselbeziehungen haben, einschließlich Eins-zu-eins-, Eins-zu-viele- und Viele-zu-viele-Beziehungen.

Eins-zu-eins

Die folgende Person.Person-Tabelle und HumanResources.Employee-Tabelle haben eine Eins-zu-eins-Beziehung:

Die BusinessEntityID-Spalte der HumanResources.Employee-Tabelle ist ein Fremdschlüssel, der auf den Primärschlüssel der Person.Person-Tabelle verweist:

CREATE TABLE [Person].[Person](
    [BusinessEntityID] int NOT NULL
        CONSTRAINT [PK_Person_BusinessEntityID] PRIMARY KEY CLUSTERED,

    [FirstName] [dbo].[Name] NOT NULL,

    [LastName] [dbo].[Name] NOT NULL

    /* Other columns. */);
GO

CREATE TABLE [HumanResources].[Employee](
    [BusinessEntityID] int NOT NULL
        CONSTRAINT [PK_Employee_BusinessEntityID] PRIMARY KEY CLUSTERED
        CONSTRAINT [FK_Employee_Person_BusinessEntityID] FOREIGN KEY
        REFERENCES [Person].[Person] ([BusinessEntityID]),
    
    [JobTitle] nvarchar(50) NOT NULL,

    [HireDate] date NOT NULL

    /* Other columns. */);
GO

Jede Zeile in der HumanResources.Employee-Tabelle verweist also auf eine Zeile in der Person.Person-Tabelle (ein Mitarbeiter muss eine Person sein). Andererseits kann auf jede Zeile in der Person.Person-Tabelle durch 0 oder 1 Zeile in der HumanResources.Employee-Tabelle verwiesen werden (eine Person kann ein Mitarbeiter sein oder nicht). Diese Beziehung kann durch eine Navigationseigenschaft des Entitätstyps dargestellt werden:

public partial class AdventureWorks
{
    public const string Person = nameof(Person);

    public const string HumanResources = nameof(HumanResources);

    public DbSet<Person> People { get; set; }

    public DbSet<Employee> Employees { get; set; }
}

[Table(nameof(Person), Schema = AdventureWorks.Person)]
public partial class Person
{
    [Key]
    public int BusinessEntityID { get; set; }

    [Required]
    [MaxLength(50)]
    public string FirstName { get; set; }

    [Required]
    [MaxLength(50)]
    public string LastName { get; set; }

    public virtual Employee Employee { get; set; } // Reference navigation property.
}

[Table(nameof(Employee), Schema = AdventureWorks.HumanResources)]
public partial class Employee
{
    [Key]
    [ForeignKey(nameof(Person))]
    public int BusinessEntityID { get; set; }
        
    [Required]
    [MaxLength(50)]
    public string JobTitle { get; set; }

    public DateTime HireDate { get; set; }

    public virtual Person Person { get; set; } // Reference navigation property.
}

Das Attribut [ForeignKey] gibt an, dass die BusinessEntityID-Eigenschaft der Employee-Entität der Fremdschlüssel für die Beziehung ist, die durch die Navigationseigenschaft dargestellt wird. Hier wird Person als primäre Entität und Employee als abhängige Entität bezeichnet. Ihre Navigationseigenschaften werden als Referenznavigationseigenschaften bezeichnet, da jede Navigationseigenschaft auf eine einzelne Entität verweisen kann.

Eins-zu-viele

Die Tabellen Production.ProductCategory und Production.ProductSubcategory haben eine 1:n-Beziehung, ebenso wie Production.ProductSubcategory und Production.Product:

Jede Zeile in der Tabelle „Production.ProductCategory“ kann auf viele Zeilen in der Tabelle „Production.ProductSubcategory“ verweisen (Kategorie kann viele Unterkategorien haben), und jede Zeile in der Tabelle „Production.ProductSubcategory“ kann auf viele Zeilen in der Tabelle „Production.Product“ verweisen (Unterkategorie kann viele Produkte haben). :

CREATE TABLE [Production].[ProductSubcategory](
    [ProductSubcategoryID] int IDENTITY(1,1) NOT NULL
        CONSTRAINT [PK_ProductSubcategory_ProductSubcategoryID] PRIMARY KEY CLUSTERED,

    [Name] [dbo].[Name] NOT NULL, -- nvarchar(50).

    [ProductCategoryID] int NOT NULL
        CONSTRAINT [FK_ProductSubcategory_ProductCategory_ProductCategoryID] FOREIGN KEY
        REFERENCES [Production].[ProductCategory] ([ProductCategoryID]),

    /* Other columns. */)
GO

CREATE TABLE [Production].[Product](
    [ProductID] int IDENTITY(1,1) NOT NULL
        CONSTRAINT [PK_Product_ProductID] PRIMARY KEY CLUSTERED,

    [Name] [dbo].[Name] NOT NULL, -- nvarchar(50).

    [ListPrice] money NOT NULL,

    [ProductSubcategoryID] int NULL
        CONSTRAINT [FK_Product_ProductSubcategory_ProductSubcategoryID] FOREIGN KEY
        REFERENCES [Production].[ProductSubcategory] ([ProductSubcategoryID])
    
    /* Other columns. */)
GO

Diese 1:n-Beziehungen können durch eine Navigationseigenschaft des Typs ICollection:

dargestellt werden
public partial class ProductCategory
{
    public virtual ICollection<ProductSubcategory> ProductSubcategories { get; set; } // Collection navigation property.
}

[Table(nameof(ProductSubcategory), Schema = AdventureWorks.Production)]
public partial class ProductSubcategory
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ProductSubcategoryID { get; set; }

    [MaxLength(50)]
    [Required]
    public string Name { get; set; }

    public int ProductCategoryID { get; set; }

    public virtual ProductCategory ProductCategory { get; set; } // Reference navigation property.

    public virtual ICollection<Product> Products { get; set; } // Collection navigation property.
}

[Table(nameof(Product), Schema = AdventureWorks.Production)]
public partial class Product
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ProductID { get; set; }

    [MaxLength(50)]
    [Required]
    public string Name { get; set; }

    public decimal ListPrice { get; set; }

    public int? ProductSubcategoryID { get; set; }

    public virtual ProductSubcategory ProductSubcategory { get; set; } // Reference navigation property.
}

Beachten Sie, dass die ProductSubcategoryID-Spalte der Tabelle „Production.Product“ NULL-Werte zulässt, sodass sie einer System.Nullable-Eigenschaft zugeordnet wird. Hier wird das Attribut [ForeignKey] weggelassen, da sich die Fremdschlüssel der abhängigen Entitäten von ihren Primärschlüsseln unterscheiden und jeder Fremdschlüssel denselben Namen wie sein Primärschlüssel hat, sodass sie automatisch von EF/Core erkannt werden können.

Many-to-many

Die Tabellen Production.Product und Production.ProductPhoto haben eine Viele-zu-Viele-Beziehung.

Dies wird durch zwei 1:n-Beziehungen mit einer anderen Production.ProductProductPhoto-Verbindungstabelle implementiert:

CREATE TABLE [Production].[ProductPhoto](
    [ProductPhotoID] int IDENTITY(1,1) NOT NULL
        CONSTRAINT [PK_ProductPhoto_ProductPhotoID] PRIMARY KEY CLUSTERED,

    [LargePhotoFileName] nvarchar(50) NULL,
    
    [ModifiedDate] datetime NOT NULL 
        CONSTRAINT [DF_ProductPhoto_ModifiedDate] DEFAULT (GETDATE())

    /* Other columns. */)
GO

CREATE TABLE [Production].[ProductProductPhoto](
    [ProductID] int NOT NULL
        CONSTRAINT [FK_ProductProductPhoto_Product_ProductID] FOREIGN KEY
        REFERENCES [Production].[Product] ([ProductID]),

    [ProductPhotoID] int NOT NULL
        CONSTRAINT [FK_ProductProductPhoto_ProductPhoto_ProductPhotoID] FOREIGN KEY
        REFERENCES [Production].[ProductPhoto] ([ProductPhotoID]),

    CONSTRAINT [PK_ProductProductPhoto_ProductID_ProductPhotoID] PRIMARY KEY NONCLUSTERED ([ProductID], [ProductPhotoID])
    
    /* Other columns. */)
GO

Die Viele-zu-Viele-Beziehung kann also auf 2 Eins-zu-Viele-Beziehungen mit der Junction abgebildet werden:

public partial class Product
{
    public virtual ICollection<ProductProductPhoto> ProductProductPhotos { get; set; } // Collection navigation property.
}

[Table(nameof(ProductPhoto), Schema = AdventureWorks.Production)]
public partial class ProductPhoto
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ProductPhotoID { get; set; }

    [MaxLength(50)]
    public string LargePhotoFileName { get; set; }

    [ConcurrencyCheck]
    public DateTime ModifiedDate { get; set; }

    public virtual ICollection<ProductProductPhoto> ProductProductPhotos { get; set; } // Collection navigation property.
}

[Table(nameof(ProductProductPhoto), Schema = AdventureWorks.Production)]
public partial class ProductProductPhoto
{
    [Key]
    [Column(Order = 0)]
    public int ProductID { get; set; }

    [Key]
    [Column(Order = 1)]
    public int ProductPhotoID { get; set; }

    public virtual Product Product { get; set; } // Reference navigation property.

    public virtual ProductPhoto ProductPhoto { get; set; } // Reference navigation property.
}

ProductPhoto.ModifiedDate verfügt über ein [ConcurrencyCheck]-Attribut für die Parallelitätskonfliktprüfung, die im Parallelitätsteil erläutert wird. Die Tabelle Production.ProductProductPhoto hat einen zusammengesetzten Primärschlüssel. Als Verbindungstabelle hat jede Zeile in der Tabelle eine eindeutige Kombination aus ProductID und ProductPhotoID. EF Core erfordert zusätzliche Informationen für den zusammengesetzten Primärschlüssel, der als anonymer Typ in OnModelCreating:

bereitgestellt werden kann
public partial class AdventureWorks
{
    private static void MapCompositePrimaryKey(ModelBuilder modelBuilder) // Called by OnModelCreating.
    {
        modelBuilder.Entity<ProductProductPhoto>()
            .HasKey(productProductPhoto => new
            {
                ProductID = productProductPhoto.ProductID,
                ProductPhotoID = productProductPhoto.ProductPhotoID
            });
    }
}

EF Core erfordert außerdem zusätzliche Informationen für Viele-zu-Viele-Beziehungen, dargestellt durch zwei Eins-zu-Viele-Beziehungen, die auch in OnModelCreating bereitgestellt werden können:

public partial class AdventureWorks
{
    private static void MapManyToMany(ModelBuilder modelBuilder) // Called by OnModelCreating.
    {
        modelBuilder.Entity<ProductProductPhoto>()
            .HasOne(productProductPhoto => productProductPhoto.Product)
            .WithMany(product => product.ProductProductPhotos)
            .HasForeignKey(productProductPhoto => productProductPhoto.ProductID);

        modelBuilder.Entity<ProductProductPhoto>()
            .HasOne(productProductPhoto => productProductPhoto.ProductPhoto)
            .WithMany(photo => photo.ProductProductPhotos)
            .HasForeignKey(productProductPhoto => productProductPhoto.ProductPhotoID);
    }
}

Schließlich können die Zeilen jeder obigen Tabelle als IQueryable-Datenquelle verfügbar gemacht werden:

public partial class AdventureWorks
{
    public DbSet<Person> People { get; set; }

    public DbSet<Employee> Employees { get; set; }

    public DbSet<ProductSubcategory> ProductSubcategories { get; set; }

    public DbSet<Product> Products { get; set; }

    public DbSet<ProductPhoto> ProductPhotos { get; set; }
}

Vererbung

EF/Core unterstützt auch die Vererbung für Entitätstypen.

EF Core unterstützt die Vererbung von Tabellen pro Hierarchie (TPH), was auch die Standardstrategie von EF ist. Mit TPH werden Zeilen in einer Tabelle vielen Entitäten in der Vererbungshierarchie zugeordnet, sodass eine Diskriminatorspalte benötigt wird, um die Zuordnungsentität jeder spezifischen Zeile zu identifizieren. Nehmen Sie die folgende Production.TransactionHistory-Tabelle als Beispiel:

CREATE TABLE [Production].[TransactionHistory](
    [TransactionID] int IDENTITY(100000,1) NOT NULL
        CONSTRAINT [PK_TransactionHistory_TransactionID] PRIMARY KEY CLUSTERED,

    [ProductID] int NOT NULL
        CONSTRAINT [FK_TransactionHistory_Product_ProductID] FOREIGN KEY
        REFERENCES [Production].[Product] ([ProductID]),

    [TransactionDate] datetime NOT NULL,

    [TransactionType] nchar(1) NOT NULL
        CONSTRAINT [CK_Product_Style] 
        CHECK (UPPER([TransactionType]) = N'P' OR UPPER([TransactionType]) = N'S' OR UPPER([TransactionType]) = N'W'),

    [Quantity] int NOT NULL,

    [ActualCost] money NOT NULL

    /* Other columns. */);
GO

Seine TransactionType-Spalte ermöglicht den Wert „P“, „S“ oder „W“, um jede Zeile anzugeben, die eine Kauftransaktion, Verkaufstransaktion oder Arbeitstransaktion darstellt. Die Mapping-Hierarchie kann also sein:

[Table(nameof(TransactionHistory), Schema = AdventureWorks.Production)]
public abstract class TransactionHistory
{
    [Key]
    public int TransactionID { get; set; }

    public int ProductID { get; set; }

    public DateTime TransactionDate { get; set; }

    public int Quantity { get; set; }

    public decimal ActualCost { get; set; }
}

public class PurchaseTransactionHistory : TransactionHistory { }

public class SalesTransactionHistory : TransactionHistory { }

public class WorkTransactionHistory : TransactionHistory { }

Dann muss der Diskriminator über OnModelCreating angegeben werden. Die APIs von EF und EF Core sind unterschiedlich:

public enum TransactionType { P, S, W }

public partial class AdventureWorks
{
    private static void MapDiscriminator(ModelBuilder modelBuilder) // Called by OnModelCreating.
    {
#if EF
        modelBuilder
            .Entity<TransactionHistory>()
            .Map<PurchaseTransactionHistory>(mapping => mapping.Requires(nameof(TransactionType))
                .HasValue(nameof(TransactionType.P)))
            .Map<SalesTransactionHistory>(mapping => mapping.Requires(nameof(TransactionType))
                .HasValue(nameof(TransactionType.S)))
            .Map<WorkTransactionHistory>(mapping => mapping.Requires(nameof(TransactionType))
                .HasValue(nameof(TransactionType.W)));
#else
        modelBuilder.Entity<TransactionHistory>()
            .HasDiscriminator<string>(nameof(TransactionType))
            .HasValue<PurchaseTransactionHistory>(nameof(TransactionType.P))
            .HasValue<SalesTransactionHistory>(nameof(TransactionType.S))
            .HasValue<WorkTransactionHistory>(nameof(TransactionType.W));
#endif
    }
}

Jetzt können diese Entitäten alle als Datenquellen verfügbar gemacht werden:

public partial class AdventureWorks
{
    public DbSet<TransactionHistory> Transactions { get; set; }

    public DbSet<PurchaseTransactionHistory> PurchaseTransactions { get; set; }

    public DbSet<SalesTransactionHistory> SalesTransactions { get; set; }

    public DbSet<WorkTransactionHistory> WorkTransactions { get; set; }
}

Aufrufe

Eine View kann auch wie eine Tabelle abgebildet werden, wenn die View eine oder mehrere Spalten hat, die als Primärschlüssel angesehen werden können. Nehmen Sie als Beispiel die Ansicht Production.vEmployee:

CREATE VIEW [HumanResources].[vEmployee] 
AS 
SELECT 
    e.[BusinessEntityID],
    p.[FirstName],
    p.[LastName],
    e.[JobTitle]  
    -- Other columns.
FROM [HumanResources].[Employee] e
    INNER JOIN [Person].[Person] p
    ON p.[BusinessEntityID] = e.[BusinessEntityID]
    /* Other tables. */;
GO

Die BusinessEntityID ist eindeutig und kann als Primärschlüssel angesehen werden. Es kann also der folgenden Entität zugeordnet werden:

[Table(nameof(vEmployee), Schema = AdventureWorks.HumanResources)]
public class vEmployee
{
    [Key]
    public int BusinessEntityID { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string JobTitle { get; set; }
}

Und dann als Datenquelle verfügbar machen:

public partial class AdventureWorks
{
    public DbSet<vEmployee> vEmployees { get; set; }
}

Gespeicherte Prozeduren und Funktionen