如何定义类内封装属性的导航?

Jan*_*kan 7 c# entity-framework-core

我继承了一个共享项目,其中定义了模型。为了更容易的 XML 序列化,它们采用以下形式:

    public class Blog
    {
        public int Id { get; set; }
        public Posts Posts { get; set; }
    }

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

    public class Post
    {
        public int BlogId { get; set; }
        public int PostId { get; set; }
        public string Content { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

如何在OnModelCreating方法中指定 EF DbContext以Posts.PostsCollection用作导航属性?让我们假设,我不允许在 Post 和 Blog 类中更改任何内容。我只需要以编程方式指定 EF 的关系。是否可以?我已阅读有关在 MS 站点上定义关系的内容以及有关在此站点和其他各种站点上定义模型的其他主题,但找不到适合我的场景的任何内容。

Iva*_*oev 5

这是可能的,但中间类必须映射为假实体,作为一对多关系的主体,并依赖于与实际主体的一对一关系。

拥有的实体类型看起来不错,但由于 EF Core 不允许拥有的实体类型作为主体的限制,它必须配置为与“所有者”共享同一张表的常规“实体”(所谓的表拆分) 和影子“PK”/“FK”属性实现所谓的共享主键关联

由于中间的“实体”和与所有者的“关系”是通过影子属性处理的,因此所涉及的模型类都不需要修改。

以下是示例模型的流畅配置

modelBuilder.Entity<Posts>(entity =>
{
    // Table splitting
    entity.ToTable("Blogs");
    // Shadow PK
    entity.Property<int>(nameof(Blog.Id));
    entity.HasKey(nameof(Blog.Id));
    // Ownership
    entity.HasOne<Blog>()
        .WithOne(related => related.Posts)
        .HasForeignKey<Posts>(nameof(Blog.Id));
    // Relationship
    entity
        .HasMany(posts => posts.PostsCollection)
        .WithOne()
        .HasForeignKey(related => related.BlogId);
});
Run Code Online (Sandbox Code Playgroud)

影子 PK/FK 属性的名称可以是任何名称,但您需要知道所有者表名称/模式以及 PK 属性名称和类型。所有这些信息都可以从 EF Core 模型元数据中获得,因此可以将更安全和可重用的配置提取到这样的自定义扩展方法中(EF Core 3.0+,可以针对 2.x 进行调整)

namespace Microsoft.EntityFrameworkCore
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using Metadata.Builders;

    public static class CustomEntityTypeBuilderExtensions
    {
        public static CollectionNavigationBuilder<TContainer, TRelated> HasMany<TEntity, TContainer, TRelated>(
            this EntityTypeBuilder<TEntity> entityTypeBuilder,
            Expression<Func<TEntity, TContainer>> containerProperty,
            Expression<Func<TContainer, IEnumerable<TRelated>>> collectionProperty)
            where TEntity : class where TContainer : class where TRelated : class
        {
            var entityType = entityTypeBuilder.Metadata;
            var containerType = entityType.Model.FindEntityType(typeof(TContainer));
            // Table splitting
            containerType.SetTableName(entityType.GetTableName());
            containerType.SetSchema(entityType.GetSchema());
            // Shadow PK
            var key = containerType.FindPrimaryKey() ?? containerType.SetPrimaryKey(entityType
                .FindPrimaryKey().Properties
                .Select(p => containerType.FindProperty(p.Name) ?? containerType.AddProperty(p.Name, p.ClrType))
                .ToArray());
            // Ownership
            entityTypeBuilder
                .HasOne(containerProperty)
                .WithOne()
                .HasForeignKey<TContainer>(key.Properties.Select(p => p.Name).ToArray());
            // Relationship
            return new ModelBuilder(entityType.Model)
                .Entity<TContainer>()
                .HasMany(collectionProperty);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用上面的自定义方法,示例模型的配置将是

modelBuilder.Entity<Blog>()
    .HasMany(entity => entity.Posts, container => container.PostsCollection)
    .WithOne()
    .HasForeignKey(related => related.BlogId);
Run Code Online (Sandbox Code Playgroud)

如果集合导航属性直接打开,这与标准配置几乎相同(只有一个额外的 lambda 参数) Blog

modelBuilder.Entity<Blog>()
    .HasMany(entity => entity.PostsCollection)
    .WithOne()
    .HasForeignKey(related => related.BlogId);
Run Code Online (Sandbox Code Playgroud)