Типы сущностей

Включение DbSet типа в ваш контекст означает, что он включен в модель EF Core; мы называем такой тип сущностью. EF Core может считывать и записывать экземпляры сущностей из базы данных или в базу данных, а если используется реляционная база данных, EF Core может создавать таблицы для сущностей с помощью миграций.

Включение типов в модель

По соглашению, типы, предоставляемые в свойствах DbSet вашего контекста, включаются в модель в качестве сущностей. Типы сущностей, указанные в OnModelCreating методе, также включаются, как и любые типы, найденные путем рекурсивного изучения свойств навигации других обнаруженных типов сущностей.

В приведенном ниже примере кода включены все типы:

  • Blog включается, потому что он доступен в свойстве DbSet в контексте.
  • Post включён, поскольку он обнаружен с помощью свойства навигации Blog.Posts.
  • AuditEntry поскольку он указан в OnModelCreating.
internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<AuditEntry>();
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public Blog Blog { get; set; }
}

public class AuditEntry
{
    public int AuditEntryId { get; set; }
    public string Username { get; set; }
    public string Action { get; set; }
}

Исключение типов из модели

Если вы не хотите, чтобы тип был включен в модель, его можно исключить:

[NotMapped]
public class BlogMetadata
{
    public DateTime LoadedFromDatabase { get; set; }
}

Исключение из миграций

Иногда полезно иметь один и тот же тип сущности, сопоставленный в нескольких DbContext типах. Это особенно верно при использовании ограничивающих контекстов, для которых обычно используется другой DbContext тип для каждого ограниченного контекста.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<IdentityUser>()
        .ToTable("AspNetUsers", t => t.ExcludeFromMigrations());
}

При данной конфигурации миграции не будут создавать таблицу AspNetUsers, но IdentityUser все еще включена в модель и может использоваться обычно.

Если вам нужно снова начать управление таблицей с помощью миграций, необходимо создать новую миграцию, где AspNetUsers не исключается. Следующая миграция теперь будет содержать любые изменения, внесенные в таблицу.

Имя таблицы

По умолчанию каждый тип сущности устанавливается для сопоставления с таблицей базы данных, имеющей то же имя, что и свойство DbSet, которое представляет сущность. Если для данной сущности нет dbSet, используется имя класса.

Вы можете вручную настроить имя таблицы:

[Table("blogs")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Схема таблицы

При использовании реляционной базы данных таблицы создаются по соглашению в схеме по умолчанию базы данных. Например, Microsoft SQL Server будет использовать схему dbo (SQLite не поддерживает схемы).

Таблицы можно настроить для создания в определенной схеме следующим образом:

[Table("blogs", Schema = "blogging")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Вместо указания схемы для каждой таблицы можно также определить схему по умолчанию на уровне модели с помощью API fluent:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasDefaultSchema("blogging");
}

Обратите внимание, что настройка схемы по умолчанию также влияет на другие объекты базы данных, такие как последовательности.

Триггеры таблиц

Api Fluent можно использовать для указания того, что триггер базы данных существует для таблицы:

modelBuilder
    .Entity<BlogWithTrigger>()
    .ToTable(tb => tb.HasTrigger("TRG_InsertUpdateBlog"));

Вызов HasTrigger регистрирует триггер только в модели EF. Миграции EF не создают триггер в базе данных; добавьте DDL триггера в миграцию с помощью migrationBuilder.Sql.

Сопоставление представлений

Типы сущностей можно сопоставить с представлениями базы данных с помощью API Fluent.

Замечание

EF предполагает, что указанное представление уже существует в базе данных, оно не создаст его автоматически в миграции.

modelBuilder.Entity<Blog>()
    .ToView("blogsView", schema: "blogging");

Сопоставление с представлением приведет к удалению сопоставления таблиц по умолчанию, но тип сущности также можно сопоставить с таблицей явным образом. В этом случае сопоставление представлений будет использоваться для запросов, а сопоставление таблиц будет использоваться для обновлений.

Подсказка

Чтобы протестировать типы сущностей без ключа, сопоставленные с представлениями с помощью поставщика в памяти, сопоставите их с запросом через ToInMemoryQuery. Дополнительные сведения см. в документации поставщика в памяти .

Сопоставление функций с табличным значением

Можно сопоставить тип сущности с табличной функцией (TVF) вместо таблицы в базе данных. Чтобы проиллюстрировать это, давайте определим другую сущность, представляющую блог с несколькими записями. В примере сущность не имеет ключа, но он не обязательно должен быть таким.

public class BlogWithMultiplePosts
{
    public string Url { get; set; }
    public int PostCount { get; set; }
}

Затем создайте в базе данных следующую табличное значение функцию, которая возвращает только блоги с несколькими записями, а также количество записей, связанных с каждым из этих блогов:

CREATE FUNCTION dbo.BlogsWithMultiplePosts()
RETURNS TABLE
AS
RETURN
(
    SELECT b.Url, COUNT(p.BlogId) AS PostCount
    FROM Blogs AS b
    JOIN Posts AS p ON b.BlogId = p.BlogId
    GROUP BY b.BlogId, b.Url
    HAVING COUNT(p.BlogId) > 1
)

Теперь сущность BlogWithMultiplePosts можно сопоставить с этой функцией следующим образом:

modelBuilder.Entity<BlogWithMultiplePosts>().HasNoKey().ToFunction("BlogsWithMultiplePosts");

Замечание

Чтобы сопоставить сущность с табличным значением функции, функция должна быть без параметров.

Обычно свойства сущности сопоставляются с соответствующими столбцами, возвращаемыми TVF. Если столбцы, возвращаемые TVF, имеют разные имена, отличные от свойства сущности, столбцы сущности можно настроить с помощью HasColumnName метода, как при сопоставлении с обычной таблицей.

Если тип сущности сопоставляется с табличным значением функции, запрос:

var query = from b in context.Set<BlogWithMultiplePosts>()
            where b.PostCount > 3
            select new { b.Url, b.PostCount };

Создает следующий SQL:

SELECT [b].[Url], [b].[PostCount]
FROM [dbo].[BlogsWithMultiplePosts]() AS [b]
WHERE [b].[PostCount] > 3

Примечания к таблицам

Можно задать произвольный текстовый комментарий, который устанавливается в таблице базы данных, что позволяет документировать схему в базе данных:

[Comment("Blogs managed on the website")]
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

Типы сущностей общего типа

Типы сущностей, которые используют один и тот же тип CLR, известны как сущности общих типов. Эти типы сущностей должны быть настроены с уникальным именем, которое необходимо указать при использовании сущности общего типа, в дополнение к типу CLR. Это означает, что соответствующее DbSet свойство должно быть реализовано с помощью вызова Set.

internal class MyContext : DbContext
{
    public DbSet<Dictionary<string, object>> Blogs => Set<Dictionary<string, object>>("Blog");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.SharedTypeEntity<Dictionary<string, object>>(
            "Blog", bb =>
            {
                bb.Property<int>("BlogId");
                bb.Property<string>("Url");
                bb.Property<DateTime>("LastUpdated");
            });
    }
}