21
6

Entity Framework Core:一对多关系

0
归档:2024年6月分类:C#和.NET

当单个实体与任意数量的其他实体关联时,将使用一对多关系。 例如,Blog 可以有多个关联的 Posts,但每个 Post 都只与一个 Blog 相关联。

本文档采用围绕大量示例展开的结构。 这些示例从常见情况着手,还引入了一些概念。 后面的示例介绍了不太常见的配置类型。 此处介绍了一个不错的方法,即了解前几个示例和概念,再根据特定需求转到后面的示例。 基于此方法,我们将从简单的“必需”和“可选”的一对多关系开始。

提示

可在 OneToMany.cs 中找到以下所有示例的代码。

必需的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

一对多关系由以下部分组成:

主体实体上的一个或多个主键或备用键属性,即关系的“一”端。 例如 Blog.Id。
依赖实体上的一个或多个外键属性,即关系的“多”端。 例如 Post.BlogId。
(可选)引用依赖实体的主体实体上的集合导航。 例如 Blog.Posts。
(可选)引用主体实体的依赖实体上的引用导航。 例如 Post.Blog。
因此,对于此示例中的关系:

外键属性 Post.BlogId 不可为空。 这会使关系成为“必需”关系,因为每个依赖实体 (Post) 必须与某个主体实体 (Blog) 相关,而其外键属性必须设置为某个值。
这两个实体都有指向关系另一端的相关实体的导航。
备注

必需的关系可确保每个依赖实体都必须与某个主体实体相关联。 但是,主体实体可以在没有任何依赖实体的情况下始终存在。 也就是说,必需的关系并不表示始终存在至少一个依赖实体。 无论是在 EF 模型,还是在关系数据库中,都没有确保主体实体与特定数量的依赖实体相关联的标准方法。 如果需要,则必须在应用程序(业务)逻辑中实现它。 有关详细信息,请参阅必需的导航。

提示

具有两个导航的关系(一个是从依赖实体到主体实体,一个是从主体实体到依赖实体)称为双向关系。

此关系按约定发现。 即:

Blog 作为关系中的主体实体被发现,Post 作为依赖实体被发现。
Post.BlogId 作为引用主体实体的 Blog.Id 主键的依赖实体的外键被发现。 由于 Post.BlogId 不可为空,所以发现这一关系是必需的。
Blog.Posts 作为集合导航被发现。
Post.Blog 作为引用导航被发现。
重要

使用 C# 可为空引用类型时,如果外键属性可为空,则引用导航必须不可为空。 如果外键属性不可为空,则引用导航可以为空,也可以不为空。 在这种情况下,Post.BlogId 和 Post.Blog 皆不可为空。 = null!; 构造用于将此标记为 C# 编译器的有意行为,因为 EF 通常会对 Blog 实例进行设置,并且对于完全加载的关系,它不能为空。 有关详细信息,请参阅使用可为空引用类型。

对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

在上面的示例中,关系的配置从 主体实体类型 (Blog) 上的 HasMany 开始,然后是 WithOne。 与所有关系一样,它完全等效于从依赖实体类型开始 (Post),然后依次使用 HasOne 和 WithMany。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(e => e.Blog)
        .WithMany(e => e.Posts)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

与另一个选项相比,这两个选项并没有什么优势:它们都会导致完全相同的配置。

提示

没有必要对关系进行两次配置,即先从主体实体开始,又从依赖实体开始。 此外,尝试单独配置关系的主体实体和依赖实体通常不起作用。 选择从一端或另一端配置每个关系,然后只编写一次配置代码。

可选的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int? BlogId { get; set; } // Optional foreign key property
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

这与上一个示例相同,只不过外键属性和到主体实体的导航现在可为空。 这会使关系成为“可选”关系,因为依赖实体 (Post) 可以在无需与任何主体实体 (Blog) 相关的情况下存在。

重要

使用 C# 可为空引用类型时,如果外键属性可为空,则引用导航必须不可为空。 在本例中,Post.BlogId 不可为空,因此 Post.Blog 也不得为空。 有关详细信息,请参阅使用可为空引用类型。

如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired(false);
}

具有阴影外键的必需的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

在某些情况下,你可能不需要模型中的外键属性,因为外键是关系在数据库中表示方式的详细信息,而在完全以面向对象的方式使用关系时,不需要外键属性。 但是,如果要序列化实体(例如通过网络发送),则当实体不采用对象形式时,外键值可能是保持关系信息不变的有用方法。 因此,为实现此目的,务实的做法是在 .NET 类型中保留外键属性。 外键属性可以是私有的,这是一个折中的办法,既可以避免公开外键,又允许其值随实体一起传输。

继前面的两个示例后,此示例从依赖实体类型中删除外键属性。 EF 因此会创建一个名为 BlogId 的 int 类型的阴影外键属性。

此处需要注意的一个要点是,正在使用 C# 可为空引用类型,因此引用导航的可为空性用于确定外键属性是否可为空,进而确定关系是可选的还是必需的。 如果未使用可为空引用类型,则默认情况下,阴影外键属性将为空,使关系默认为可选。 在这种情况下,使用 IsRequired 强制阴影外键属性为不可为空,并使关系成为必需关系。

如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("BlogId")
        .IsRequired();
}

具有阴影外键的可选一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

与前面的示例一样,外键属性已从依赖实体类型中移除。 EF 因此会创建一个名为 BlogId 的 int? 类型的阴影外键属性。 与前面的示例不同,这次外键属性创建为可为空,因为正在使用 C# 可为空引用类型,并且依赖实体类型的导航可为空。 这会使关系成为可选关系。

如果未使用 C# 可为空引用类型,则默认情况下,外键属性也将创建为可为空。 这意味着,与自动创建的阴影属性的关系默认可选。

如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasForeignKey("BlogId")
        .IsRequired(false);
}

无需导航到主体实体的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

在此示例中,外键属性已重新引入,但依赖实体上的导航已被移除。

提示

只有一个导航的关系(即从依赖实体到主体实体,或从主体实体到依赖实体,但只有其中一个)的被称单向关系。

如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne()
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

请注意,对 WithOne 的调用没有参数。 这是告知 EF 没有从 Post 到 Blog 的导航的方式。

如果从没有导航的实体开始配置,则必须使用泛型 HasOne<>() 调用显式指定关系另一端的实体类型。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne<Blog>()
        .WithMany(e => e.Posts)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

无需导航到主体实体且有阴影外键的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
}

此示例通过移除外键属性和依赖实体上的导航来合并上述两个示例。

此关系按约定作为可选关系被发现。 由于代码中没有任何内容可以用来指示它是必需的,因此需要使用 IsRequired 进行最小配置来创建必需关系。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne()
        .IsRequired();
}

可以使用更完整的配置来显式配置导航和外键名称,并根据需要对 IsRequired() 或 IsRequired(false) 进行适当的调用。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne()
        .HasForeignKey("BlogId")
        .IsRequired();
}

无需导航到依赖实体的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

前两个示例具有从主体实体到依赖实体的导航,但没有从依赖实体到主体实体的导航。 在接下来的几个示例中,将重新引入依赖实体上的导航,而主体上的导航将被移除。

如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(e => e.Blog)
        .WithMany()
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

请注意,WithMany() 调用时没有参数,可指示此方向没有导航。

如果从没有导航的实体开始配置,则必须使用泛型 HasMany<>() 调用显式指定关系另一端的实体类型。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne(e => e.Blog)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

无导航的一对多
有时,配置无导航的关系可能很有用。 此类关系只能通过直接更改外键值来操作。

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

此关系不会按约定发现,因为没有任何导航指示这两种类型是相关的。 可以在 OnModelCreating 中显式配置它。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne();
}

使用此配置时,按照约定,Post.BlogId 属性仍被检测为外键,并且关系是必需的,因为外键属性不可为空。 通过将外键属性设为“可为空”,可以使关系成为“可选”关系。

此关系的更完整显式配置是:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany<Post>()
        .WithOne()
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

具有备用键的一对多
在到目前为止的所有示例中,依赖实体上的外键属性被约束为主体实体上的主键属性。 外键可以改为被约束为不同的属性,该属性随后成为主体实体类型的备用键。 例如:

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public int AlternateId { get; set; } // Alternate key as target of the Post.BlogId foreign key
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

此关系不是按约定发现的,因为 EF 始终按照约定创建与主键的关系。 可以使用对 HasPrincipalKey 的调用在 OnModelCreating 中显式配置它。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId);
}

HasPrincipalKey 可与其他调用结合使用,以显式配置导航、外键属性和必需/可选性质。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .HasPrincipalKey(e => e.AlternateId)
        .HasForeignKey(e => e.BlogId)
        .IsRequired();
}

具有复合外键的一对多
在到目前为止的所有示例中,主体实体的主键或备用键属性由单个属性组成。 利用多个属性也可以形成主键或备用键,这些键称为“组合键”。 当关系的主体实体具有组合键时,依赖实体的外键也必须是具有相同属性数的组合键。 例如:

// Principal (parent)
public class Blog
{
    public int Id1 { get; set; } // Composite key part 1
    public int Id2 { get; set; } // Composite key part 2
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId1 { get; set; } // Required foreign key property part 1
    public int BlogId2 { get; set; } // Required foreign key property part 2
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

此关系按约定发现。 但是,需要显式配置组合键本身:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(e => new { e.Id1, e.Id2 });
}

重要

如果组合外键值的任何属性值为空,则认为其值为 null。 具有一个属性“空”和另一个属性“非空”的组合外键不会被视为与具有相同值的主键或备用键匹配。 两者都将被视为 null。

HasForeignKey 和 HasPrincipalKey 都可用于显式指定具有多个属性的键。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>(
        nestedBuilder =>
        {
            nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });

            nestedBuilder.HasMany(e => e.Posts)
                .WithOne(e => e.Blog)
                .HasPrincipalKey(e => new { e.Id1, e.Id2 })
                .HasForeignKey(e => new { e.BlogId1, e.BlogId2 })
                .IsRequired();
        });
}

提示

在上面的代码中,对 HasKey 和 HasMany 的调用已组合到嵌套生成器中。 使用嵌套生成器,无需为同一实体类型多次调用 Entity<>(),但在功能上等效于多次调用 Entity<>()。

无需级联删除的一对多

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public ICollection<Post> Posts { get; } = new List<Post>(); // Collection navigation containing dependents
}

// Dependent (child)
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

按照约定,必需关系配置为级联删除,这意味着,删除主体实体后,也会删除其所有依赖实体,因为依赖实体无法存在于每月主体实体的数据库中。 可以将 EF 配置为引发异常,而不是自动删除不再存在的依赖行:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(e => e.Posts)
        .WithOne(e => e.Blog)
        .OnDelete(DeleteBehavior.Restrict);
}

自引用一对多
在前面的所有示例中,主体实体类型与依赖实体类型有所不同。 情况不一定如此。 例如,在下面的类型中,每个 Employee 都与另一个 Employees 相关。

public class Employee
{
    public int Id { get; set; }

    public int? ManagerId { get; set; } // Optional foreign key property
    public Employee? Manager { get; set; } // Optional reference navigation to principal
    public ICollection<Employee> Reports { get; } = new List<Employee>(); // Collection navigation containing dependents
}

此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Employee>()
        .HasOne(e => e.Manager)
        .WithMany(e => e.Reports)
        .HasForeignKey(e => e.ManagerId)
        .IsRequired(false);
}
20
6

Entity Framework Core:一对一关系

0
归档:2024年6月分类:C#和.NET

当一个实体与最多一个其他实体关联时,将使用一对一关系。 例如,Blog 有一个 BlogHeader,并且 BlogHeader 属于单个 Blog。

本文档采用围绕大量示例展开的结构。 这些示例从常见情况着手,还引入了一些概念。 后面的示例介绍了不太常见的配置类型。 此处介绍了一个不错的方法,即了解前几个示例和概念,再根据特定需求转到后面的示例。 基于此方法,我们将从简单的“必需”和“可选”的一对一关系开始。

可在 OneToOne.cs 中找到以下所有示例的代码。

必需的一对一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

一对一关系由以下部分组成:

主体实体上的一个或多个主键或备用键属性。 例如 Blog.Id。
依赖实体上的一个或多个外键属性。 例如 BlogHeader.BlogId。
(可选)引用依赖实体的主体实体上的引用导航。 例如 Blog.Header。
(可选)引用主体实体的依赖实体上的引用导航。 例如,BlogHeader.Blog。
提示

一对一关系的哪一端应是主体实体,哪一端应是依赖实体,这并不总是显而易见的。 请注意以下事项:

如果两种类型的数据库表已存在,则具有外键列的表必须映射到依赖类型。
如果某个类型在逻辑上不能在没有另一个类型的情况下存在,则它通常是依赖类型。 例如,对于不存在的博客,它的标题是没有意义的,因此 BlogHeader 自然是依赖类型。
如果存在自然的父/子关系,则子级通常是依赖类型。
因此,对于此示例中的关系:

外键属性 BlogHeader.BlogId 不可为空。 这会使关系成为“必需”关系,因为每个依赖实体 (BlogHeader) 必须与某个主体实体 (Blog) 相关,而其外键属性必须设置为某个值。
这两个实体都有指向关系另一端的相关实体的导航。
备注

必需的关系可确保每个依赖实体都必须与某个主体实体相关联。 但是,主体实体可以在没有任何依赖实体的情况下始终存在。 也就是说,必需的关系并不表示始终存在依赖实体。 无论是在 EF 模型,还是在关系数据库中,都没有确保主体实体与依赖实体相关联的标准方法。 如果需要,则必须在应用程序(业务)逻辑中实现它。 有关详细信息,请参阅必需的导航。

提示

具有两个导航的关系(一个是从依赖实体到主体实体,一个是从主体实体到依赖实体)称为双向关系。

此关系按约定发现。 即:

Blog 作为关系中的主体实体被发现,BlogHeader 作为依赖实体被发现。
BlogHeader.BlogId 作为引用主体实体的 Blog.Id 主键的依赖实体的外键被发现。 由于 BlogHeader.BlogId 不可为空,所以发现这一关系是必需的。
Blog.BlogHeader 作为引用导航被发现。
BlogHeader.Blog 作为引用导航被发现。
重要

使用 C# 可为空引用类型时,如果外键属性可为空,则从依赖实体到主体实体的导航必须可为空。 如果外键属性不可为空,则导航可以为空,也可以不为空。 在这种情况下,BlogHeader.BlogId 和 BlogHeader.Blog 皆不可为空。 = null!; 构造用于将此标记为 C# 编译器的有意行为,因为 EF 通常会对 Blog 实例进行设置,并且对于完全加载的关系,它不能为空。 有关详细信息,请参阅使用可为空引用类型。

对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

在上面的示例中,关系的配置将启动主体实体类型 (Blog)。 与所有关系一样,它完全等效于从依赖实体类型 (BlogHeader) 开始。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne(e => e.Blog)
        .WithOne(e => e.Header)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

与另一个选项相比,这两个选项并没有什么优势:它们都会导致完全相同的配置。

提示

没有必要对关系进行两次配置,即先从主体实体开始,又从依赖实体开始。 此外,尝试单独配置关系的主体实体和依赖实体通常不起作用。 选择从一端或另一端配置每个关系,然后只编写一次配置代码。

可选的一对一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int? BlogId { get; set; } // Optional foreign key property
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

这与上一个示例相同,只不过外键属性和到主体实体的导航现在可为空。 这会使关系成为“可选”关系,因为依赖实体 (BlogHeader) 不能通过将其外键属性和导航设置为 null 来与任何主体 (Blog) 相关。

使用 C# 可为空引用类型时,如果外键属性可为空,则从依赖实体到主体实体的导航属性必须可为空。 在本例中,BlogHeader.BlogId 不可为空,因此 BlogHeader.Blog 也不得为空。 有关详细信息,请参阅使用可为空引用类型。

如前所述,此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired(false);
}

具有主键到主键关系的必需的一对一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

与一对多关系不同,一对一关系的依赖端可以将其主键属性用作外键属性。 这通常称为 PK 到 PK 关系。 仅当主体类型和依赖类型具有相同的主键类型,并且始终需要生成的关系时才有可能,因为依赖类型的主键不可为空。

必须配置未按约定发现外键的任何一对一关系,以指示关系的主体端和依赖端。 这通常是使用对 HasForeignKey 的调用来完成的。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>();
}

HasPrincipalKey 也可以用于此目的,但这样做并不常见。

如果对 HasForeignKey 的调用中未指定任何属性,并且主键适用,则将其用作外键。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.Id)
        .IsRequired();
}

具有阴影外键的必需的一对一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

在某些情况下,你可能不需要模型中的外键属性,因为外键是关系在数据库中表示方式的详细信息,而在完全以面向对象的方式使用关系时,不需要外键属性。 但是,如果要序列化实体(例如通过网络发送),则当实体不采用对象形式时,外键值可能是保持关系信息不变的有用方法。 因此,为实现此目的,务实的做法是在 .NET 类型中保留外键属性。 外键属性可以是私有的,这是一个折中的办法,既可以避免公开外键,又允许其值随实体一起传输。

继前面的示例后,此示例从依赖实体类型中删除外键属性。 但会指示 EF 创建一个名为 BlogId 的 int 类型的阴影外键属性,而不是使用主键。

此处需要注意的一个要点是,正在使用 C# 可为空引用类型,因此从依赖实体到主体实体的导航的可为空性用于确定外键属性是否可为空,进而确定关系是可选的还是必需的。 如果未使用可为空引用类型,则默认情况下,阴影外键属性将为空,使关系默认为可选。 在这种情况下,使用 IsRequired 强制阴影外键属性为不可为空,并使关系成为必需关系。

此关系再次需要一些配置来指示主体端和依赖端:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId");
}

对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

具有阴影外键的可选一对一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public Blog? Blog { get; set; } // Optional reference navigation to principal
}

与前面的示例一样,外键属性已从依赖实体类型中移除。 但是,与前面的示例不同,这次外键属性创建为“可为空”,因为正在使用 C# 可为空引用类型,并且依赖实体类型的导航可为空。 这会使关系成为可选关系。

如果未使用 C# 可为空引用类型,则默认情况下,外键属性将创建为“可为空”。 这意味着,与自动创建的阴影属性的关系默认可选。

如前所述,此关系需要一些配置来指示主体端和依赖端:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId");
}

对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired(false);
}

无需导航到主体实体的一对一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

在此示例中,外键属性已重新引入,但依赖实体上的导航已被移除。

只有一个导航的关系(即从依赖实体到主体实体,或从主体实体到依赖实体,但只有其中一个)称为单向关系。

此关系按约定发现,因为会发现外键,从而指示依赖端。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

请注意,对 WithOne 的调用没有参数。 这是告知 EF 没有从 BlogHeader 到 Blog 的导航的方式。

如果从没有导航的实体开始配置,则必须使用泛型 HasOne<>() 调用显式指定关系另一端的实体类型。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne<Blog>()
        .WithOne(e => e.Header)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

无需导航到主体实体且有阴影外键的一对一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
}

此示例通过移除外键属性和依赖实体上的导航来合并上述两个示例。

如前所述,此关系需要一些配置来指示主体端和依赖端:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

可以使用更完整的配置来显式配置导航和外键名称,并根据需要对 IsRequired() 或 IsRequired(false) 进行适当的调用。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne()
        .HasForeignKey<BlogHeader>("BlogId")
        .IsRequired();
}

无需导航到依赖实体的一对一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

前两个示例具有从主体实体到依赖实体的导航,但没有从依赖实体到主体实体的导航。 在接下来的几个示例中,将重新引入依赖实体上的导航,而主体上的导航将被移除。

按照约定,EF 会将其视为一对多关系。 需要一些最小配置才能实现一对一:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne(e => e.Blog)
        .WithOne();
}

请注意,WithOne() 调用时没有参数,可指示此方向没有导航。

对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<BlogHeader>()
        .HasOne(e => e.Blog)
        .WithOne()
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

如果从没有导航的实体开始配置,则必须使用泛型 HasOne<>() 调用显式指定关系另一端的实体类型。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne<BlogHeader>()
        .WithOne(e => e.Blog)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

无导航的一对一
有时,配置无导航的关系可能很有用。 此类关系只能通过直接更改外键值来操作。

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
}

此关系不会按约定发现,因为没有任何导航指示这两种类型是相关的。 可以在 OnModelCreating 中显式配置它。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne<BlogHeader>()
        .WithOne();
}

使用此配置时,按照约定,BlogHeader.BlogId 属性仍被检测为外键,并且关系是“必需的”,因为外键属性不可为空。 通过将外键属性设为“可为空”,可以使关系成为“可选”关系。

此关系的更完整显式配置是:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne<BlogHeader>()
        .WithOne()
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

具有备用键的一对一
在到目前为止的所有示例中,依赖实体上的外键属性被约束为主体实体上的主键属性。 外键可以改为被约束为不同的属性,该属性随后成为主体实体类型的备用键。 例如:

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public int AlternateId { get; set; } // Alternate key as target of the BlogHeader.BlogId foreign key
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

此关系不是按约定发现的,因为 EF 始终按照约定创建与主键的关系。 可以使用对 HasPrincipalKey 的调用在 OnModelCreating 中显式配置它。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasPrincipalKey<Blog>(e => e.AlternateId);
}

HasPrincipalKey 可与其他调用结合使用,以显式配置导航、外键属性和必需/可选性质。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .HasPrincipalKey<Blog>(e => e.AlternateId)
        .HasForeignKey<BlogHeader>(e => e.BlogId)
        .IsRequired();
}

具有复合外键的一对一
在到目前为止的所有示例中,主体实体的主键或备用键属性由单个属性组成。 主键或备用键也可以形成多个属性,这些属性称为“组合键”。 当关系的主体实体具有组合键时,依赖实体的外键也必须是具有相同属性数的组合键。 例如:

// Principal (parent)
public class Blog
{
    public int Id1 { get; set; } // Composite key part 1
    public int Id2 { get; set; } // Composite key part 2
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId1 { get; set; } // Required foreign key property part 1
    public int BlogId2 { get; set; } // Required foreign key property part 2
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
此关系按约定发现。 但是,只有在已显式配置组合键时才会发现它,因为不会自动发现组合键。 例如:

```csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity()
        .HasKey(e => new { e.Id1, e.Id2 });
}

如果组合外键值的任何属性值为空,则认为其值为 null。 具有一个属性“空”和另一个属性“非空”的组合外键不会被视为与具有相同值的主键或备用键匹配。 两者都将被视为 null。

HasForeignKey 和 HasPrincipalKey 都可用于显式指定具有多个属性的键。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity(
        nestedBuilder =>
        {
            nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });

            nestedBuilder.HasOne(e => e.Header)
                .WithOne(e => e.Blog)
                .HasPrincipalKey(e => new { e.Id1, e.Id2 })
                .HasForeignKey(e => new { e.BlogId1, e.BlogId2 })
                .IsRequired();
        });
}

在上面的代码中,对 HasKey 和 HasOne 的调用已组合到嵌套生成器中。 使用嵌套生成器,无需为同一实体类型多次调用 Entity<>(),但在功能上等效于多次调用 Entity<>()。

无需级联删除的一对一

// Principal (parent)
public class Blog
{
    public int Id { get; set; }
    public BlogHeader? Header { get; set; } // Reference navigation to dependent
}

// Dependent (child)
public class BlogHeader
{
    public int Id { get; set; }
    public int BlogId { get; set; } // Required foreign key property
    public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}

根据约定,所需的关系配置为级联删除。 这是因为在删除主体实体后,数据库中就不能存在依赖实体。 可以将数据库配置为生成错误,通常会使应用程序崩溃,而不是自动删除不再存在的依赖行。 这需要一些配置:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity()
        .HasOne(e => e.Header)
        .WithOne(e => e.Blog)
        .OnDelete(DeleteBehavior.Restrict);
}

自引用一对一
在前面的所有示例中,主体实体类型与依赖实体类型有所不同。 情况不一定如此。 例如,在下面的类型中,每个 Person 类型都可选择与另一个 Person 相关。

public class Person
{
    public int Id { get; set; }

    public int? HusbandId { get; set; } // Optional foreign key property
    public Person? Husband { get; set; } // Optional reference navigation to principal
    public Person? Wife { get; set; } // Reference navigation to dependent
}

此关系按约定发现。 对于未按约定发现关系的导航、外键或必需/可选性质的情况,可以显式配置这些内容。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity()
        .HasOne(e => e.Husband)
        .WithOne(e => e.Wife)
        .HasForeignKey(e => e.HusbandId)
        .IsRequired(false);
}

对于一对一自引用关系,由于主体实体类型和依赖实体类型相同,指定包含外键的类型不会阐明依赖端。 在本例中,从依赖实体到主体实体以 HasOne 点为单位指定的导航,以及从主体实体到依赖实体以 WithOne 点为单位指定的导航。

16
6

父亲节,女儿很乖,儿子大闹,我又发了一轮火-_-

我们不算是鸡娃的家长,但是对子女有最基本的要求,为了引导和教育子女,我这段时间发现自己对小孩们发脾气的次数变多了。每次发完火之后,我知道这很不好,但是总是不能克制住自己,其实对小孩或者家人发火,只是情绪的宣泄,对谁都不好。我总结自己发火的点:

1、子女们的行为习惯。主要是生活习惯和本来应该养成的好习惯,有时候他们屡教不改,这方面是应该严厉、严格,但是不要动火,否则无济于事。

2、对于子女们的兴趣培养,比如小提琴,总是会有“恨铁不成刚”的想法。如果静下来想想,他们的水平已经远远在我之上,所以很多时候更多是自己的期望值太高。如果能认识到兴趣要坚持和长期培养,那么我确实应该要更有耐心,毕竟核心是要让他们感兴趣地坚持下去。

3、辅导子女们的学业过程。其实想想小孩子需要时间去成长,有时候我是以大人的要求去教育小孩,我应该给他们更多的时间来消化及成长。

我今天反思自己,就是希望自己能控制情绪,作为一个男人,应该能担当、沉住气,这样才能真的起到教育的作用。

“父母所遭受的文化和心理压力或伤害可能会传递到孩子身上。我绝不是危言耸听,我想提醒父母们,我们赋予孩子的一切中,有一半来自我们自己,因此,我们首先需要让自己变成身心健康的人。”

立帖为证,做好自己。

12
6

本教程提供了使用 SignalR 和 Blazor 生成实时应用的基本工作经验。 本文适用于已经熟悉 SignalR 并正在寻求了解如何在 SignalR 应用中使用 Blazor 的开发人员。 有关 SignalR 和 Blazor 框架的详细指南,请参阅以下参考文档集和 API 文档:

ASP.NET Core SignalR 概述
ASP.NET Core Blazor
.NET API 浏览器
了解如何:

创建 Blazor 应用
添加 SignalR 客户端库
添加 SignalR 集线器
添加 SignalR 服务和 SignalR 中心的终结点
添加用于聊天的 Razor 组件代码
在本教程结束时,你将拥有一个正常运行的聊天应用。

先决条件
Visual Studio
Visual Studio Code
.NET CLI
具有“ASP.NET 和 Web 开发”工作负载的 Visual Studio 2022 或更高版本

示例应用
本教程不需要下载教程的示例聊天应用。 示例应用是按照本教程的步骤生成的最终工作应用。

查看或下载示例代码(如何下载)

创建 Blazor Web 应用
按照所选工具的指南进行操作:

Visual Studio
Visual Studio Code
.NET CLI
备注

需要 Visual Studio 2022 或更高版本以及 .NET Core SDK 8.0.0 或更高版本。

创建新项目。

选择“Blazor Web 应用”模板。 选择“下一步”。

在“项目名称”字段中键入 BlazorSignalRApp。 确认“位置”条目正确无误或为项目提供位置。 选择下一步。

确认 Framework 是 .NET 8 或更高版本。 选择“创建”。

添加 SignalR 客户端库
Visual Studio
Visual Studio Code
.NET CLI
在“解决方案资源管理器”中,右键单击 BlazorSignalRApp 项目,然后选择“管理 NuGet 包” 。

在“管理 NuGet 包”对话框中,确认“包源”设置为“nuget.org” 。

选择“浏览”后,在搜索框中键入“Microsoft.AspNetCore.SignalR.Client”。

在搜索结果中,选择 Microsoft.AspNetCore.SignalR.Client 包的最新版本。 选择“安装” 。

如果出现“预览更改”对话框,则选择“确定”。

如果出现“许可证接受”对话框,如果你同意许可条款,请选择“我接受”。

添加 SignalR 集线器
创建 Hubs(复数)文件夹,并将以下 ChatHub 类 (Hubs/ChatHub.cs) 添加到应用的根目录:

C#

复制
using Microsoft.AspNetCore.SignalR;

namespace BlazorSignalRApp.Hubs;

public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
为 SignalR 中心添加服务和终结点
打开 Program 文件。

将 Microsoft.AspNetCore.ResponseCompression 和 ChatHub 类的命名空间添加到文件的顶部:

C#

复制
using Microsoft.AspNetCore.ResponseCompression;
using BlazorSignalRApp.Hubs;
添加响应压缩中间件服务:

C#

复制
builder.Services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
在处理管道的配置顶部使用响应压缩中间件:

C#

复制
app.UseResponseCompression();
紧接在映射 Razor 组件 (app.MapRazorComponents()) 的行后面,为行添加一个终结点:

C#

复制
app.MapHub("/chathub");
添加用于聊天的 Razor 组件代码
打开 Components/Pages/Home.razor 文件。

将标记替换为以下代码:


@page "/"
@rendermode InteractiveServer
@using Microsoft.AspNetCore.SignalR.Client
@inject NavigationManager Navigation
@implements IAsyncDisposable

Home


    @foreach (var message in messages) {
  • @message
  • }
@code { private HubConnection? hubConnection; private List messages = new List(); private string? userInput; private string? messageInput; protected override async Task OnInitializedAsync() { hubConnection = new HubConnectionBuilder() .WithUrl(Navigation.ToAbsoluteUri("/chathub")) .Build(); hubConnection.On("ReceiveMessage", (user, message) => { var encodedMsg = $"{user}: {message}"; messages.Add(encodedMsg); InvokeAsync(StateHasChanged); }); await hubConnection.StartAsync(); } private async Task Send() { if (hubConnection is not null) { await hubConnection.SendAsync("SendMessage", userInput, messageInput); } } public bool IsConnected => hubConnection?.State == HubConnectionState.Connected; public async ValueTask DisposeAsync() { if (hubConnection is not null) { await hubConnection.DisposeAsync(); } } }
12
6

Blazor server VS Blazor WebAssembly

0
归档:2024年6月分类:C#和.NET

Blazor WebAssembly
主要的 Blazor 托管模型在 WebAssembly 上的浏览器中运行客户端。 将 Blazor 应用、其依赖项以及 .NET 运行时下载到浏览器。 应用将在浏览器线程中直接执行。 UI 更新和事件处理在同一进程中进行。 应用资产作为静态文件部署到可为客户端提供静态内容的 Web 服务器或服务中。

file
Blazor WebAssembly:Blazor 应用在浏览器内部的 UI 线程上运行。

如果创建了 Blazor WebAssembly 应用进行部署,但没有后端 ASP.NET Core 应用来为其文件提供服务,那么该应用被称为独立 Blazor WebAssembly 应用。 如果创建了应用进行部署,但没有后端应用来为其文件提供服务,那么该应用被称为托管的 Blazor WebAssembly 应用。 托管的 Blazor WebAssembly Client 应用通常使用 Web API 调用或 SignalR(结合使用 ASP.NET Core SignalR 和 Blazor)通过网络与后端 Server 应用交互。

blazor.webassembly.js 脚本由框架和句柄提供:

下载 .NET 运行时、应用和应用依赖项。
初始化运行应用的运行时。
Blazor WebAssembly 托管模型具有以下优点:

没有 .NET 服务器端依赖项。 应用下载到客户端后即可正常运行。
可充分利用客户端资源和功能。
工作可从服务器转移到客户端。
无需 ASP.NET Core Web 服务器即可托管应用。 无服务器部署方案可行,例如通过内容分发网络 (CDN) 为应用提供服务的方案。
Blazor WebAssembly 托管模型具有以下局限性:

应用仅可使用浏览器功能。
需要可用的客户端硬件和软件(例如 WebAssembly 支持)。
下载项大小较大,应用加载耗时较长。
.NET 运行时和工具支持不够完善。 例如,.NET Standard 支持和调试方面存在限制。
若要创建 Blazor WebAssembly 应用,请参阅 用于 ASP.NET Core Blazor 的工具。

Blazor 托管应用模型支持 Docker 容器。 对于 Visual Studio 中的 Docker 支持,请右键单击托管的 Blazor WebAssembly 解决方案的 Server 项目,然后选择“添加” > “Docker 支持”。

Blazor Server
使用 Blazor Server 托管模型可从 ASP.NET Core 应用中在服务器上执行应用。 UI 更新、事件处理和 JavaScript 调用是通过 SignalR 连接进行处理。

浏览器通过 SignalR 连接与服务器上的应用(该应用托管在 ASP.NET Core 应用内部)进行交互。

ASP.NET Core 应用会引用应用的 Startup 类以添加以下内容:

服务器端服务。
用于请求处理管道的应用。
在客户端上,blazor.server.js 脚本与服务器建立 SignalR 连接。 脚本由 ASP.NET Core 共享框架中的嵌入资源提供给客户端应用。 客户端应用负责根据需要保持和还原应用状态。
file
Blazor Server 托管模型具有以下优点:

下载项大小明显小于 Blazor WebAssembly 应用,且应用加载速度快得多。
应用可充分利用服务器功能,包括使用任何与 .NET Core 兼容的 API。
服务器上的 .NET Core 用于运行应用,因此调试等现有 .NET 工具可按预期正常工作。
支持瘦客户端。 例如,Blazor Server 应用适用于不支持 WebAssembly 的浏览器以及资源受限的设备。
应用的 .NET/C# 代码库(其中包括应用的组件代码)不适用于客户端。
重要

Blazor Server 应用预呈现以响应第一个客户端请求,这会在服务器上创建 UI 状态。 客户端尝试创建 SignalR 连接时,“必须重新连接到同一服务器”。 使用多个后端服务器的 Blazor Server 应用应实现粘滞会话,从而建立 SignalR 连接。 有关更多信息,请参见连接到服务器一节。

Blazor Server 托管模型具有以下局限性:

通常延迟较高。 每次用户交互都涉及到网络跃点。
不支持脱机工作。 如果客户端连接失败,应用会停止工作。
如果具有多名用户,则应用扩缩性存在挑战。 服务器必须管理多个客户端连接并处理客户端状态。
需要 ASP.NET Core 服务器为应用提供服务。 无服务器部署方案不可行,例如通过内容分发网络 (CDN) 为应用提供服务的方案。
若要创建 Blazor Server 应用,请参阅 用于 ASP.NET Core Blazor 的工具。

Blazor Server 应用模型支持 Docker 容器。 对于 Visual Studio 中的 Docker 支持,请右键单击 Visual Studio 中的项目,然后选择“添加” > “Docker 支持” 。

与服务器呈现的 UI 进行比较
理解 Blazor Server 应用的一种方法是,了解其与使用 Razor 视图或 Razor Pages 在 ASP.NET Core 应用中呈现 UI 的惯用模型之间的差异。 这两种模型都使用 Razor 语言描述 HTML 内容,但两者在标记的呈现方式上差别显著。

呈现 Razor 页面或视图时,每行 Razor 代码都以文本形式发出 HTML。 呈现后,服务器会丢弃页面或视图实例,包括生成的任何状态。 出现另一个对该页面的请求时,例如,服务器验证失败并显示验证摘要时:

整个页面将再次重新呈现为 HTML 文本。
页面会发送到客户端。
Blazor 应用由 UI 的可重用元素组成,这些元素称为组件。 组件包含 C# 代码、标记和其他成分。 呈现组件时,Blazor 会生成所含组件的图,类似于 HTML 或 XML 文档对象模型 (DOM)。 此图包含属性和字段中保存的组件状态。 Blazor 会评估组件图,生成二进制形式的标记。 二进制格式可以:

转换为 HTML 文本(预呈现 † 期间)。
用于在定期呈现期间高效更新标记。
†预呈现:请求获取的 Razor 组件在服务器上编译为静态 HTML,并发送到客户端,然后呈现给用户。 客户端与服务器之间建立连接后,组件的静态预呈现元素会替换为交互式元素。 预呈现会使应用对用户的响应更加迅速。

Blazor 中的 UI 更新由以下内容触发:

用户交互,例如选中按钮。
应用触发器,例如计时器。
组件组已预呈现,且已计算 UI diff(差异)。 此差异是更新客户端上的 UI 所需的最小一组 DOM 编辑。 差异以二进制格式发送到客户端,并由浏览器应用。

用户在客户端上退出组件之后,组件会被丢弃。 用户与组件交互时,组件的状态(服务、资源)必须保存在服务器的内存中。 由于服务器可能同时保存多个组件的状态,因此必须解决内存不足的问题。 要了解如何创作 Blazor Server 应用以确保充分使用服务器内存,请参阅 ASP.NET Core Blazor Server 的威胁缓解指南。

线路
Blazor Server 应用基于 ASP.NET CoreSignalR 构建。 每个客户端通过一个或多个称为“线路”的 SignalR 连接与服务器通信。 线路是 Blazor 对可容忍短暂网络中断的 SignalR 连接的抽象。 Blazor 客户端发现 SignalR 连接已断开时,它会尝试使用新的 SignalR 连接来重新连接到服务器。

连接到 Blazor Server 应用的每个浏览器屏幕(浏览器标签页或 iframe)均使用 SignalR 连接。 与典型服务器呈现应用相比,这是另一个关键差异。 在服务器呈现应用的多个浏览器屏幕中打开同一应用通常不需要服务器上的其他资源。 在 Blazor Server 应用中,若服务器要管理浏览器屏幕,则每个浏览器屏幕均需要独立线路和组件状态的独立实例。

Blazor 将关闭浏览器标签页或访问外部 URL 视为正常终止。 如果正常终止,则会立即释放线路和关联的资源。 例如,由于网络中断,客户端也可能异常地断开连接。 Blazor Server 会将断开连接的路线存储一段时间(可配置),以便客户端重新连接。

Blazor Server 允许代码定义线路处理程序,后者允许在用户线路的状态发生更改时运行代码。 有关详细信息,请参阅 ASP.NET Core Blazor SignalR 指南。

UI 延迟
UI 延迟是指从启动操作到 UI 更新所需的时间。 要使应用对用户进行响应,需要 UI 延迟值较小。 在 Blazor Server 应用中,每个操作都会发送到服务器并进行处理,然后发回 UI 差异。 因此,UI 延迟是网络延迟和处理操作时的服务器延迟的总和。

如果是仅用于专用公司网络的商业应用程序,用户通常不易感受到因网络延迟而导致的延迟。 如果是通过 Internet 部署的应用,用户可能会更容易感受到延迟,用户分布广泛时感受尤为明显。

内存使用也会导致应用延迟。 内存使用率增大会导致垃圾收集频繁或内存分页到磁盘,两者均会降低应用性能,进而增大 UI 延迟。

Blazor Server 应用应降低网络延迟和内存使用率,从而优化以最大限度地降低 UI 延迟。 有关测量网络延迟的方法,请参阅 托管和部署 ASP.NET Core Blazor Server。 有关 SignalR 和 Blazor 的详细信息,请参阅以下内容:

托管和部署 ASP.NET Core Blazor Server
ASP.NET Core Blazor Server 的威胁缓解指南
连接到服务器
Blazor Server 应用需要与服务器建立有效的 SignalR 连接。 如果连接丢失,应用会尝试重新连接到服务器。 只要客户端的状态仍在服务器的内存中,客户端会话即可恢复,且不会失去状态。

Blazor Server 应用预呈现以响应第一个客户端请求,这会在服务器上创建 UI 状态。 客户端尝试创建 SignalR 连接时,必须重新连接到同一服务器。 使用多个后端服务器的 Blazor Server 应用应实现粘滞会话,从而建立 SignalR 连接。

我们建议将 Azure SignalR 服务用于 Blazor Server 应用。 该服务允许将 Blazor Server 应用扩展到大量并发 SignalR 连接。 可将服务的 ServerStickyMode 选项或配置值设置为 Required,从而为 Azure SignalR 服务启用粘滞会话。 有关详细信息,请参阅 托管和部署 ASP.NET Core Blazor Server。

使用 IIS 时,粘滞会话通过应用程序请求路由启用。 有关详细信息,请参阅使用应用程序请求路由实现 HTTP 负载均衡。

10
6

1.问题原因
报错信息:

fatal: unable to access 'https://github.com/xxx/autowrite.git/':
OpenSSL SSL_read: Connection was reset, errno 10054
1
2
又或者

fatal: unable to access 'https://github.com/xxx/autowrite.git/':
Failed to connect to github.com port 443: Timed out
1
2
因为git在拉取或者提交项目时,中间会有git的http和https代理,但是我们本地环境本身就有SSL协议了,所以取消git的https代理即可,不行再取消http的代理。

后续
原因还有一个,当前代理网速过慢,所以偶尔会成功,偶尔失败。

2.解决方案
1.在项目文件夹的命令行窗口执行下面代码,然后再git commit 或git clone
取消git本身的https代理,使用自己本机的代理,如果没有的话,其实默认还是用git的

//取消http代理
git config --global --unset http.proxy
//取消https代理
git config --global --unset https.proxy
1
2
3
4
2.科学上网(vpn)
这样就能提高服务器连接速度,能从根本解决 time out 443问题

原文链接:https://blog.csdn.net/good_good_xiu/article/details/118567249

08
6

EF Add-Migration总结

0
归档:2024年6月分类:C#和.NET

EF CodeFirst对数据库任何的操作,千万不要手工去修改。

解释:add-migration命令是code first migration中的关键命令之一。当您对领域域模型进行更改并需要将它们时添加到数据库中,您将创建一个新的迁移。这是通过Add-Migration命令完成的。用最简单的形式,你只需要提供迁移名称

展现形式:命令将您的更改构建到一个cs文件中。这个cs文件与配置文件放在同一个文件夹中,服务于您要瞄准的DbContext

1.常用的命令:

Add-Migration 、 Update-DataBase 、 Script-Migration

(1)vs的程序包管理控制台输入 get-help Add-Migration -detailed以查看详细信息

Add-Migration
[-Name] :指定自定义脚本 的名字
[-Force] [-ProjectName ] :如果要重新构建现有迁移,必须使用-Force参数。然而,只有在迁移尚未应用到数据库时,才能重新构建框架。否则你 需要回复到要重新构建的迁移之前的迁移
[-StartUpProjectName ] :是从 解决方案资源管理器中选择一个项目 作为启动项目。如果我们忽略的话,就会默认为解决方案资源管理器中的启动项目。
[-ConfigurationTypeName ] :项目中有多个DbContext,那么您需要指出哪个数据库会更新。这可以用-ConfigurationTypeName做。ConfigurationTypeName方法是迁移文件夹中配置类的名称。
[-ConnectionStringName ] :从应用程序的配置文件中指定要使用的连接字符串的名字。我们用两个参数-ConnectionString -ConnectionProviderName ,或者用这样一个参数代替
[-IgnoreChanges] :假设目标数据库模式与当前的模型是一致的。构建一个空迁移和对应的空的迁移文件,忽略在当前模型中检测到的任何挂起的更改。可用于创建初始的空迁移,以支持对现有数据库的迁移。
[-AppDomainBaseDirectory ]:指定用于运行数据迁移代码的app-domain的路径,从而app-domain 可以找到所有需要的程序集。这是一个高级选项,只有当解决方案包含多个项目时才会需要。这样的话,context和configuration所需要的程序集就不仅仅从那些包含context和包含migrations的项目中获取
CommonParameters

Add-Migration (你的迁移文件名称)

若在一个项目里,操作多个DbConext的方法则需要指定context名称

add-migration 迁移名称 -c ConfigurationDbContext

update-database -c ConfigurationDbContext

05
6

MediatR Official Document

0
归档:2024年6月分类:C#和.NET

MediatR is a low-ambition library trying to solve a simple problem — decoupling the in-process sending of messages from handling messages. Cross-platform, supporting netstandard2.0.

Setup

Install the package via NuGet first:
Install-Package MediatR

MediatR directly references Microsoft.Extensions.DependencyInjection.Abstractions leveraging IServiceProvider. Typical usage is to use IServiceCollection directly:

services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
});



This method registers the known MediatR types:

  • IMediator as transient
  • ISender as transient
  • IPublisher as transient

For each assembly registered, the AddMediatR method will scan those assemblies for MediatR types (excluding behaviors):

  • IRequestHandler<,> concrete implementations as transient
  • IRequestHandler<> concrete implementations as transient
  • INotificationHandler<> concrete implementations as transient
  • IStreamRequestHandler<> concrete implementations as transient
  • IRequestExceptionHandler<,,> concrete implementations as transient
  • IRequestExceptionAction<,>) concrete implementations as transient

Behaviors and pre/post processors must be registered explicitly through the AddXyz methods.

Basics

MediatR has two kinds of messages it dispatches:

  • Request/response messages, dispatched to a single handler
  • Notification messages, dispatched to multiple handlers

Request/response

The request/response interface handles both command and query scenarios. First, create a message:

public class Ping : IRequest<string> { }



Next, create a handler:

public class PingHandler : IRequestHandler<Ping, string>
{
    public Task<string> Handle(Ping request, CancellationToken cancellationToken)
    {
        return Task.FromResult("Pong");
    }
}



Finally, send a message through the mediator:

var response = await mediator.Send(new Ping());
Debug.WriteLine(response); // "Pong"



In the case your message does not require a response, implement the non-generic IRequest interface and subsequent handler:

public class OneWay : IRequest { }
public class OneWayHandler : IRequestHandler<OneWay>
{
    public Task Handle(OneWay request, CancellationToken cancellationToken)
    {
        // do work
        return Task.CompletedTask;
    }
}



Request types

There are two flavors of requests in MediatR - ones that return a value, and ones that do not:

  • IRequest<TResponse> - the request returns a value
  • IRequest - the request does not return a value

Each request type has its own handler interface:

  • IRequestHandler<TRequest, TResponse> - implement this and return Task<TResponse>

Then for requests without return values:

  • IRequestHandler<TRequest> - implement this and you will return Task.

Streams and AsyncEnumerables

To create a stream from a request, first implement the stream request and its response:

  • IStreamRequest<TResponse>

    public sealed class CounterStreamRequest : IStreamRequest<int> { }



Stream request handlers are separate from the normal IRequestHandler and require implementing:

  • IStreamRequestHandler<TRequest, TResponse>

    public sealed class CounterStreamHandler : IStreamRequestHandler<CounterStreamRequest, int>
    {
        public async IAsyncEnumerable<int> Handle(CounterStreamRequest request, [EnumeratorCancellation] CancellationToken cancellationToken)
        {
            int count = 0;
            while (!cancellationToken.IsCancellationRequested)
            {
                await Task.Delay(500, cancellationToken);
                yield return count;
                count++;
            }
        }
    }



Unlike normal request handlers that return a single TResponse, a stream handler returns an IAsyncEnumerable<TResponse>:

IAsyncEnumerable<TResponse> Handle(TRequest request, CancellationToken cancellationToken);



To create a stream request handler, create a class that implements IStreamRequestHandler<TRequest, TResponse> and implement the above Handle method.

Finally, send a stream message through the mediator:

    CancellationTokenSource cts = new();
    int count = 10;
    await foreach (var item in mediator.CreateStream<int>(new CounterStreamRequest(), cts.Token))
    {
        count--;
        if (count == 0)
        {
            cts.Cancel();
        }
        Debug.WriteLine(item);
    }



Notifications

For notifications, first create your notification message:

public class Ping : INotification { }



Next, create zero or more handlers for your notification:

public class Pong1 : INotificationHandler<Ping>
{
    public Task Handle(Ping notification, CancellationToken cancellationToken)
    {
        Debug.WriteLine("Pong 1");
        return Task.CompletedTask;
    }
}

public class Pong2 : INotificationHandler<Ping>
{
    public Task Handle(Ping notification, CancellationToken cancellationToken)
    {
        Debug.WriteLine("Pong 2");
        return Task.CompletedTask;
    }
}



Finally, publish your message via the mediator:

await mediator.Publish(new Ping());



Custom notification publishers

MediatR (starting with version 12) supports custom publish strategies:

public interface INotificationPublisher
{
    Task Publish(IEnumerable<NotificationHandlerExecutor> handlerExecutors, INotification notification,
        CancellationToken cancellationToken);
}



Custom publish strategies are injected into the Mediator class, and are registered with AddMediatR:

services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssemblyContaining<Program>();
    cfg.NotificationPublisher = new MyCustomPublisher(); // this will be singleton
    cfg.NotificationPublisherType = typeof(MyCustomPublisher); // this will be the ServiceLifetime
});



There are two built-in notification publishers:

  • ForeachAwaitPublisher - the default, existing implementation
  • TaskWhenAllPublisher - awaits Task.WhenAll for all notification handler tasks. Does NOT use Task.Run

Custom notification publishers can also use the handler instance to do custom logic like ordering, skipping handlers, etc. The handler instance is on the NotificationHandlerExecutor class, as well as the delegate to call the handler instance itself.

Polymorphic dispatch

Handler interfaces are contravariant:

public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse>
{
    Task<TResponse> Handle(TRequest message, CancellationToken cancellationToken);
}

public interface INotificationHandler<in TNotification>
{
    Task Handle(TNotification notification, CancellationToken cancellationToken);
}



Containers that support generic variance will dispatch accordingly. For example, you can have an INotificationHandler<INotification> to handle all notifications.

Async

Send/publish are asynchronous from the IMediator side, with corresponding synchronous and asynchronous-based interfaces/base classes for requests/responses/notification handlers.

Your handlers can use the async/await keywords as long as the work is awaitable:

public class PingHandler : IRequestHandler<Ping, Pong>
{
    public async Task<Pong> Handle(Ping request, CancellationToken cancellationToken)
    {
        await DoPong(); // Whatever DoPong does
    }
}



You will also need to register these handlers with the IoC container of your choice, similar to the synchronous handlers shown above.

Exceptions handling

Exception handler pipeline step

Exception handler implemented by using IPipelineBehavior concept. It requires to add the RequestExceptionProcessorBehavior to the request execution Pipeline. This behavior is not registered unless AddMediatR finds request exception behaviors.

namespace MediatR.Pipeline;

/// <summary>
/// Behavior for executing all <see cref="IRequestExceptionHandler{TRequest,TResponse,TException}"/> instances
///     after an exception is thrown by the following pipeline steps
/// </summary>
/// <typeparam name="TRequest">Request type</typeparam>
/// <typeparam name="TResponse">Response type</typeparam>
public class RequestExceptionProcessorBehavior<TRequest, TResponse, TException> : IRequestExceptionHandler<TRequest, TResponse, TException>
    where TRequest : notnull
{



Exception action pipeline step

Exception action implemented by using IPipelineBehavior concept. It requires to add the RequestExceptionActionProcessorBehavior to the request execution Pipeline.
If place RequestExceptionActionProcessorBehavior before RequestExceptionProcessorBehavior, actions will be called only for unhandled exceptions.

namespace MediatR.Pipeline;

/// <summary>
/// Behavior for executing all <see cref="IRequestExceptionAction{TRequest,TException}"/> instances
///     after an exception is thrown by the following pipeline steps
/// </summary>
/// <typeparam name="TRequest">Request type</typeparam>
/// <typeparam name="TResponse">Response type</typeparam>
public class RequestExceptionActionProcessorBehavior<TRequest, TResponse> : IRequestExceptionAction<TRequest, TException>
    where TRequest : notnull
{



Handlers and actions priority execution

All available handlers/actions will be sorted by applying next rules:

  • The handler/action has a higher priority if it belongs to the current assembly (same assembly with request) and the other is not. If none of the objects belong to the current assembly, they can be considered equal. If both objects belong to the current assembly, they can't be compared only by this criterion - compare by next rule;
  • The handler/action has a higher priority if it belongs to the current/child request namespace and the other is not. If both objects belong to the current/child request namespace, they can be considered equal. If none of the objects belong to the current/child request namespace, they can't be compared by this criterion - compare by next rule;
  • The handler/action has a higher priority if it namespace is part of the current location (request namespace) and the other is not. If both objects are part of the current location, the closest has higher priority. If none of the objects are part of the current location, they can be considered equal.

Override handler/action

Create a new handler / action that inherits the handler / action that you want to override, and save it in accordance with the priority rules.

Exception handler vs Exception action

  • All actions will be performed for the thrown exception. However, only one handler can handle the thrown exception;
  • The exception will be re-thrown after all actions are completed. But the exception can be handled by the exception handler, and the result will be returned to the caller;
  • The actions execution process faster. Because the exception handler works like try / catch block with several catch and search handlers for all base exceptions separately;
  • Both support priority sorting;
  • Both support overriding.

公告栏

欢迎大家来到我的博客,我是dodoro,希望我的博客能给你带来帮助。