diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs index 48ce7a8ea..0b536de7b 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs @@ -99,7 +99,7 @@ public static CourseDTO ToCourseDto(this Course course) Name = course.Name, GroupName = course.GroupName, IsCompleted = course.IsCompleted, - MentorIds = course.MentorIds.Split('/', SplitOptions), + MentorIds = course.Mentors.Select(x => x.UserId).ToArray(), IsOpen = course.IsOpen, InviteCode = course.InviteCode, CourseMates = course.CourseMates.Select(cm => cm.ToCourseMateViewModel()).ToArray(), @@ -113,7 +113,7 @@ public static CoursePreview ToCoursePreview(this Course course) Name = course.Name, GroupName = course.GroupName, IsCompleted = course.IsCompleted, - MentorIds = course.MentorIds.Split('/', SplitOptions), + MentorIds = course.Mentors.Select(x => x.UserId).ToArray(), }; public static Criterion ToCriterion(this CriterionViewModel criterion) => new Criterion diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20240104183735_HomeworkCommonProperties.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20240104183735_HomeworkCommonProperties.cs index eec4c39bd..00265b893 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20240104183735_HomeworkCommonProperties.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20240104183735_HomeworkCommonProperties.cs @@ -7,6 +7,18 @@ public partial class HomeworkCommonProperties : Migration { protected override void Up(MigrationBuilder migrationBuilder) { + migrationBuilder.Sql(@" + DECLARE @constraint NVARCHAR(200); + SELECT @constraint = dc.name + FROM sys.default_constraints dc + JOIN sys.columns c ON dc.parent_column_id = c.column_id + AND dc.parent_object_id = c.object_id + WHERE c.object_id = OBJECT_ID('Homeworks') AND c.name = 'Date'; + + IF @constraint IS NOT NULL + EXEC('ALTER TABLE Homeworks DROP CONSTRAINT ' + @constraint); + "); + migrationBuilder.RenameColumn( name: "Date", table: "Homeworks", @@ -47,5 +59,38 @@ protected override void Up(MigrationBuilder migrationBuilder) nullable: false, defaultValue: false); } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn(name: "IsDeadlineStrict", table: "Homeworks"); + migrationBuilder.DropColumn(name: "HasDeadline", table: "Homeworks"); + migrationBuilder.DropColumn(name: "DeadlineDate", table: "Homeworks"); + + migrationBuilder.AlterColumn( + name: "HasDeadline", + table: "Tasks", + nullable: false, + oldClrType: typeof(bool), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "IsDeadlineStrict", + table: "Tasks", + nullable: false, + oldClrType: typeof(bool), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "PublicationDate", + table: "Tasks", + nullable: false, + oldClrType: typeof(DateTime), + oldNullable: true); + + migrationBuilder.RenameColumn( + name: "PublicationDate", + table: "Homeworks", + newName: "Date"); + } } } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.Designer.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.Designer.cs new file mode 100644 index 000000000..9d28fcba4 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.Designer.cs @@ -0,0 +1,516 @@ +// +using System; +using HwProj.CoursesService.API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace HwProj.CoursesService.API.Migrations +{ + [DbContext(typeof(CourseContext))] + [Migration("20261021133302_AddCourseMentorTable")] + partial class AddCourseMentorTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("MentorId") + .HasColumnType("nvarchar(max)"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Assignments"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupName") + .HasColumnType("nvarchar(max)"); + + b.Property("InviteCode") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCompleted") + .HasColumnType("bit"); + + b.Property("IsOpen") + .HasColumnType("bit"); + + b.Property("MentorIds") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FilterJson") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CourseFilters"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("CourseMates"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.HasKey("UserId", "CourseId"); + + b.HasIndex("CourseId"); + + b.HasIndex("UserId"); + + b.ToTable("CourseMentors"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Arguments") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxPoints") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TaskId"); + + b.ToTable("Criteria"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("StudentId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasAlternateKey("GroupId", "StudentId"); + + b.ToTable("GroupMates"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("DeadlineDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("HasDeadline") + .HasColumnType("bit"); + + b.Property("IsDeadlineStrict") + .HasColumnType("bit"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Homeworks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeadlineDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("HasDeadline") + .HasColumnType("bit"); + + b.Property("HomeworkId") + .HasColumnType("bigint"); + + b.Property("IsBonusExplicit") + .HasColumnType("bit"); + + b.Property("IsDeadlineStrict") + .HasColumnType("bit"); + + b.Property("MaxRating") + .HasColumnType("int"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("HomeworkId"); + + b.ToTable("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => + { + b.Property("CourseMateId") + .HasColumnType("bigint"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.HasKey("CourseMateId"); + + b.ToTable("StudentCharacteristics"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("TasksModels"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Answer") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsPrivate") + .HasColumnType("bit"); + + b.Property("LecturerId") + .HasColumnType("nvarchar(max)"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.Property("Text") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.HasKey("Id"); + + b.HasIndex("TaskId"); + + b.ToTable("Questions"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => + { + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CourseFilterId") + .HasColumnType("bigint"); + + b.HasKey("CourseId", "Id"); + + b.HasIndex("CourseFilterId"); + + b.ToTable("UserToCourseFilters"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Assignments") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("CourseMates") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Mentors") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => + { + b.HasOne("HwProj.CoursesService.API.Models.HomeworkTask", "Task") + .WithMany("Criteria") + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Task"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Group", null) + .WithMany("GroupMates") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Homeworks") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Homework", "Homework") + .WithMany("Tasks") + .HasForeignKey("HomeworkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Homework"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => + { + b.HasOne("HwProj.CoursesService.API.Models.CourseMate", null) + .WithOne("Characteristics") + .HasForeignKey("HwProj.CoursesService.API.Models.StudentCharacteristics", "CourseMateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Group", null) + .WithMany("Tasks") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => + { + b.HasOne("HwProj.CoursesService.API.Models.CourseFilter", "CourseFilter") + .WithMany() + .HasForeignKey("CourseFilterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CourseFilter"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Course", b => + { + b.Navigation("Assignments"); + + b.Navigation("CourseMates"); + + b.Navigation("Homeworks"); + + b.Navigation("Mentors"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.Navigation("Characteristics"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Group", b => + { + b.Navigation("GroupMates"); + + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.Navigation("Criteria"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.cs new file mode 100644 index 000000000..dbe525e64 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261021133302_AddCourseMentorTable.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HwProj.CoursesService.API.Migrations +{ + /// + public partial class AddCourseMentorTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CourseMentors", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + CourseId = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CourseMentors", x => new { x.UserId, x.CourseId }); + table.ForeignKey( + name: "FK_CourseMentors_Courses_CourseId", + column: x => x.CourseId, + principalTable: "Courses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CourseMentors_CourseId", + table: "CourseMentors", + column: "CourseId"); + + migrationBuilder.CreateIndex( + name: "IX_CourseMentors_UserId", + table: "CourseMentors", + column: "UserId"); + + migrationBuilder.Sql(@" + INSERT INTO CourseMentors (UserId, CourseId) + SELECT LTRIM(RTRIM(value)), c.Id + FROM Courses c + CROSS APPLY STRING_SPLIT(c.MentorIds, '/') + WHERE c.MentorIds IS NOT NULL + AND c.MentorIds != '' + AND LTRIM(RTRIM(value)) != '' + "); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CourseMentors"); + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.Designer.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.Designer.cs new file mode 100644 index 000000000..cce404a3a --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.Designer.cs @@ -0,0 +1,513 @@ +// +using System; +using HwProj.CoursesService.API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace HwProj.CoursesService.API.Migrations +{ + [DbContext(typeof(CourseContext))] + [Migration("20261022145922_DropMentorIds")] + partial class DropMentorIds + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("MentorId") + .HasColumnType("nvarchar(max)"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Assignments"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupName") + .HasColumnType("nvarchar(max)"); + + b.Property("InviteCode") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCompleted") + .HasColumnType("bit"); + + b.Property("IsOpen") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FilterJson") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CourseFilters"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("IsAccepted") + .HasColumnType("bit"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("CourseMates"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.HasKey("UserId", "CourseId"); + + b.HasIndex("CourseId"); + + b.HasIndex("UserId"); + + b.ToTable("CourseMentors"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Arguments") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxPoints") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("TaskId"); + + b.ToTable("Criteria"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("StudentId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasAlternateKey("GroupId", "StudentId"); + + b.ToTable("GroupMates"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("DeadlineDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("HasDeadline") + .HasColumnType("bit"); + + b.Property("IsDeadlineStrict") + .HasColumnType("bit"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.ToTable("Homeworks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeadlineDate") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("HasDeadline") + .HasColumnType("bit"); + + b.Property("HomeworkId") + .HasColumnType("bigint"); + + b.Property("IsBonusExplicit") + .HasColumnType("bit"); + + b.Property("IsDeadlineStrict") + .HasColumnType("bit"); + + b.Property("MaxRating") + .HasColumnType("int"); + + b.Property("PublicationDate") + .HasColumnType("datetime2"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("HomeworkId"); + + b.ToTable("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => + { + b.Property("CourseMateId") + .HasColumnType("bigint"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Tags") + .HasColumnType("nvarchar(max)"); + + b.HasKey("CourseMateId"); + + b.ToTable("StudentCharacteristics"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId"); + + b.ToTable("TasksModels"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Answer") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsPrivate") + .HasColumnType("bit"); + + b.Property("LecturerId") + .HasColumnType("nvarchar(max)"); + + b.Property("StudentId") + .HasColumnType("nvarchar(max)"); + + b.Property("TaskId") + .HasColumnType("bigint"); + + b.Property("Text") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.HasKey("Id"); + + b.HasIndex("TaskId"); + + b.ToTable("Questions"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => + { + b.Property("CourseId") + .HasColumnType("bigint"); + + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CourseFilterId") + .HasColumnType("bigint"); + + b.HasKey("CourseId", "Id"); + + b.HasIndex("CourseFilterId"); + + b.ToTable("UserToCourseFilters"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Assignment", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Assignments") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("CourseMates") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Mentors") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => + { + b.HasOne("HwProj.CoursesService.API.Models.HomeworkTask", "Task") + .WithMany("Criteria") + .HasForeignKey("TaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Task"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.GroupMate", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Group", null) + .WithMany("GroupMates") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Homeworks") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Homework", "Homework") + .WithMany("Tasks") + .HasForeignKey("HomeworkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Homework"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.StudentCharacteristics", b => + { + b.HasOne("HwProj.CoursesService.API.Models.CourseMate", null) + .WithOne("Characteristics") + .HasForeignKey("HwProj.CoursesService.API.Models.StudentCharacteristics", "CourseMateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.TaskModel", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Group", null) + .WithMany("Tasks") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.UserToCourseFilter", b => + { + b.HasOne("HwProj.CoursesService.API.Models.CourseFilter", "CourseFilter") + .WithMany() + .HasForeignKey("CourseFilterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CourseFilter"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Course", b => + { + b.Navigation("Assignments"); + + b.Navigation("CourseMates"); + + b.Navigation("Homeworks"); + + b.Navigation("Mentors"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => + { + b.Navigation("Characteristics"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Group", b => + { + b.Navigation("GroupMates"); + + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.Homework", b => + { + b.Navigation("Tasks"); + }); + + modelBuilder.Entity("HwProj.CoursesService.API.Models.HomeworkTask", b => + { + b.Navigation("Criteria"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.cs new file mode 100644 index 000000000..969202d5c --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20261022145922_DropMentorIds.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HwProj.CoursesService.API.Migrations +{ + /// + public partial class DropMentorIds : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MentorIds", + table: "Courses"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MentorIds", + table: "Courses", + type: "nvarchar(max)", + nullable: true); + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs index 199917cf8..881609de1 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs @@ -66,9 +66,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsOpen") .HasColumnType("bit"); - b.Property("MentorIds") - .HasColumnType("nvarchar(max)"); - b.Property("Name") .HasColumnType("nvarchar(max)"); @@ -117,6 +114,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CourseMates"); }); + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("CourseId") + .HasColumnType("bigint"); + + b.HasKey("UserId", "CourseId"); + + b.HasIndex("CourseId"); + + b.HasIndex("UserId"); + + b.ToTable("CourseMentors"); + }); + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => { b.Property("Id") @@ -125,12 +139,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - b.Property("MaxPoints") - .HasColumnType("int"); - b.Property("Arguments") .HasColumnType("nvarchar(max)"); + b.Property("MaxPoints") + .HasColumnType("int"); + b.Property("Name") .HasColumnType("nvarchar(max)"); @@ -380,6 +394,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMentor", b => + { + b.HasOne("HwProj.CoursesService.API.Models.Course", null) + .WithMany("Mentors") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("HwProj.CoursesService.API.Models.Criterion", b => { b.HasOne("HwProj.CoursesService.API.Models.HomeworkTask", "Task") @@ -456,6 +479,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("CourseMates"); b.Navigation("Homeworks"); + + b.Navigation("Mentors"); }); modelBuilder.Entity("HwProj.CoursesService.API.Models.CourseMate", b => diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs index 255241668..f09d9713a 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Models/Course.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using HwProj.Repositories.Net8; @@ -12,7 +13,7 @@ public class Course : IEntity public bool IsOpen { get; set; } public string InviteCode { get; set; } public bool IsCompleted { get; set; } - public string MentorIds { get; set; } + public List Mentors { get; set; } = new List(); public List CourseMates { get; set; } = new List(); public List Homeworks { get; set; } = new List(); public List Assignments { get; set; } = new List(); diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs index c32220254..c4f3418a9 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseContext.cs @@ -18,6 +18,7 @@ public sealed class CourseContext : DbContext public DbSet UserToCourseFilters { get; set; } public DbSet Questions { get; set; } public DbSet Criteria { get; set; } + public DbSet CourseMentors { get; set; } public CourseContext(DbContextOptions options) : base(options) @@ -30,6 +31,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().HasIndex(a => a.CourseId); modelBuilder.Entity().HasKey(u => new { u.CourseId, u.Id }); modelBuilder.Entity().HasIndex(t => t.TaskId); + + modelBuilder.Entity().HasKey(m => new { m.UserId, m.CourseId }); + modelBuilder.Entity().HasIndex(m => m.UserId); + modelBuilder.Entity().HasIndex(m => m.CourseId); } } } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseMentor.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseMentor.cs new file mode 100644 index 000000000..0f0b51420 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Models/CourseMentor.cs @@ -0,0 +1,8 @@ +namespace HwProj.CoursesService.API.Models +{ + public class CourseMentor + { + public string UserId { get; set; } + public long CourseId { get; set; } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs index 2c1c83e95..e8f7925db 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/CoursesRepository.cs @@ -15,12 +15,14 @@ public CoursesRepository(CourseContext context) public Task GetWithCourseMates(long id) => Context.Set() + .Include(c => c.Mentors) .Include(c => c.CourseMates) .AsNoTracking() .FirstOrDefaultAsync(c => c.Id == id); public async Task GetWithHomeworksAsync(long id) => await Context.Set() + .Include(c => c.Mentors) .Include(c => c.Homeworks) .ThenInclude(h => h.Tasks) .AsNoTracking() @@ -28,9 +30,9 @@ public CoursesRepository(CourseContext context) public async Task GetWithCourseMatesAndHomeworksAsync(long id, bool withCriteria) { - // Раздельные запросы вместо одного JOIN, иначе строки студентов перемножаются со строками задач var query = Context.Set() .AsNoTracking() + .Include(c => c.Mentors) .AsSplitQuery() .Include(c => c.CourseMates) .ThenInclude(c => c.Characteristics) @@ -42,10 +44,15 @@ public CoursesRepository(CourseContext context) : await query.FirstOrDefaultAsync(c => c.Id == id); } - public Task GetMentorIdsAsync(long id) => - Context.Set() - .Where(c => c.Id == id) - .Select(c => c.MentorIds) - .SingleAsync(); + public IQueryable GetAllWithCourseMatesAndHomeworks() + { + return Context.Set() + .Include(c => c.Mentors) + .Include(c => c.CourseMates) + .ThenInclude(c => c.Characteristics) + .Include(c => c.Homeworks) + .ThenInclude(c => c.Tasks) + .AsNoTracking(); + } } } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/ICoursesRepository.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/ICoursesRepository.cs index 9b9a9817e..adc8b0740 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/ICoursesRepository.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/ICoursesRepository.cs @@ -9,6 +9,6 @@ public interface ICoursesRepository : ICrudRepository Task GetWithCourseMates(long id); Task GetWithHomeworksAsync(long id); Task GetWithCourseMatesAndHomeworksAsync(long id, bool withCriteria = false); - Task GetMentorIdsAsync(long id); + } } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs index cdd757c7c..306ec36ee 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Services/CoursesService.cs @@ -102,7 +102,7 @@ public async Task AddAsync(CreateCourseViewModel courseViewModel, string m { baseCourse = await _coursesRepository.GetWithHomeworksAsync(courseViewModel.BaseCourseId.Value); if (baseCourse == null) throw new InvalidOperationException("Базовый курс не найден"); - if (!baseCourse.MentorIds.Contains(mentorId)) + if (!baseCourse.Mentors.Any(m => m.UserId == mentorId)) throw new ForbiddenException( $"Пользователь с id{mentorId} не является ментором указанного базового курса"); } @@ -150,7 +150,7 @@ private async Task AddFromTemplateAsync(CourseTemplate courseTemplate, L using var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled); var course = courseTemplate.ToCourse(); - course.MentorIds = mentorId; + course.Mentors = new List { new CourseMentor { UserId = mentorId } }; course.InviteCode = Guid.NewGuid().ToString(); var courseId = await _coursesRepository.AddAsync(course); @@ -178,7 +178,7 @@ private async Task AddFromTemplateAsync(CourseTemplate courseTemplate, L { CourseId = courseId, CourseName = course.Name, - MentorIds = course.MentorIds, + MentorIds = string.Join("/", course.Mentors.Select(m => m.UserId)), StudentId = student.StudentId }); } @@ -222,11 +222,17 @@ public async Task AddStudentAsync(long courseId, string studentId) }; await _courseMatesRepository.AddAsync(courseMate); + + var mentorIds = await _context.CourseMentors + .Where(m => m.CourseId == courseId) + .Select(m => m.UserId) + .ToArrayAsync(); + _eventBus.Publish(new NewCourseMateEvent { CourseId = courseId, CourseName = course.Name, - MentorIds = course.MentorIds, + MentorIds = string.Join("/", mentorIds), StudentId = studentId, IsAccepted = false }); @@ -244,11 +250,16 @@ public async Task AcceptCourseMateAsync(long courseId, string studentId) await _courseMatesRepository.UpdateAsync(cm.Id, cm => new CourseMate { IsAccepted = true }); + var mentorIds = await _context.CourseMentors + .Where(m => m.CourseId == courseId) + .Select(m => m.UserId) + .ToArrayAsync(); + _eventBus.Publish(new LecturerAcceptToCourseEvent { CourseId = courseId, CourseName = course.Name, - MentorIds = course.MentorIds, + MentorIds = string.Join("/", mentorIds), StudentId = studentId }); @@ -265,11 +276,16 @@ public async Task RejectCourseMateAsync(long courseId, string studentId) await _courseMatesRepository.DeleteAsync(cm.Id); + var mentorIds = await _context.CourseMentors + .Where(m => m.CourseId == courseId) + .Select(m => m.UserId) + .ToArrayAsync(); + _eventBus.Publish(new LecturerRejectToCourseEvent { CourseId = courseId, CourseName = course.Name, - MentorIds = course.MentorIds, + MentorIds = string.Join("/", mentorIds), StudentId = studentId }); return true; @@ -280,10 +296,11 @@ public async Task GetUserCoursesAsync(string userId, string role) var isMentor = role == Roles.LecturerRole || role == Roles.ExpertRole; var courses = isMentor - ? _coursesRepository.FindAll(c => c.MentorIds.Contains(userId)) + ? _coursesRepository.FindAll(c => c.Mentors.Any(m => m.UserId == userId)) : _coursesRepository.FindAll(c => c.CourseMates.Any(cm => cm.IsAccepted && cm.StudentId == userId)); var coursesWithValues = await courses + .Include(c => c.Mentors) .AsSplitQuery() .Include(c => c.CourseMates) .Include(c => c.Homeworks.OrderBy(h => h.Id)).ThenInclude(t => t.Tasks.OrderBy(x => x.Id)) @@ -311,13 +328,18 @@ public async Task AcceptLecturerAsync(long courseId, string lecturerEmail, { var course = await _coursesRepository.GetAsync(courseId); if (course == null) return false; - if (!course.MentorIds.Contains(lecturerId)) + + var alreadyMentor = await _context.CourseMentors + .AnyAsync(m => m.CourseId == courseId && m.UserId == lecturerId); + + if (!alreadyMentor) { - var newMentors = course.MentorIds + "/" + lecturerId; - await _coursesRepository.UpdateAsync(courseId, с => new Course + await _context.CourseMentors.AddAsync(new CourseMentor { - MentorIds = newMentors, + CourseId = courseId, + UserId = lecturerId }); + await _context.SaveChangesAsync(); _eventBus.Publish(new LecturerInvitedToCourseEvent { @@ -335,8 +357,10 @@ public async Task AcceptLecturerAsync(long courseId, string lecturerEmail, public async Task GetCourseLecturers(long courseId) { - var mentorIds = await _coursesRepository.GetMentorIdsAsync(courseId); - return mentorIds.Split('/'); + return await _context.CourseMentors + .Where(m => m.CourseId == courseId) + .Select(m => m.UserId) + .ToArrayAsync(); } public async Task HasStudent(long courseId, string studentId)