EF - artemovsergey/ASP GitHub Wiki

Initial DbSet

public DbSet<User> Users => Set<User>();

Converters

public class DateTimeToCharConverter : ValueConverter<DateTime, string>
{
    public DateTimeToCharConverter() : base(
        dateTime => dateTime.ToString("yyyyMMdd", CultureInfo.InvariantCulture),
        stringValue => DateTime.ParseExact(stringValue, "yyyyMMdd", CultureInfo.InvariantCulture)
    )
    {
    }
}

Configuration

 protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new GameMapping());
        // modelBuilder.ApplyConfiguration(new UserMapping());
        
        base.OnModelCreating(modelBuilder);
    }
public class GameMapping : IEntityTypeConfiguration<Game>
{
    public void Configure(EntityTypeBuilder<Game> builder)
    {
        builder.ToTable("Games").HasKey(g => g.Id);
        builder.Property(g => g.CreatedAt)
                .HasColumnType("timestamp with time zone");
        //.HasConversion(new DateTimeToCharConverter());

        builder.Property(g => g.Status).HasConversion<string>();
        builder.Property(g => g.Result).HasConversion<string>();
        builder.Property(g => g.CurrentMove).HasConversion<string>();
        builder.Property(g => g.Board).HasConversion(
            v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
            v => JsonSerializer.Deserialize<string?[][]>(v, (JsonSerializerOptions?)null)!);

        builder.Property(g => g.Board)
            .HasConversion(
                v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
                v => JsonSerializer.Deserialize<string?[][]>(v, (JsonSerializerOptions?)null)!)
            .Metadata.SetValueComparer(
                new ValueComparer<string?[][]>(
                    (c1, c2) => c1!.SequenceEqual(c2!), // Сравниваем массивы поэлементно
                    c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), // Вычисляем хеш-код
                    c => c.Select(x => x.ToArray()).ToArray())); // Создаем глубокую копию для snapshot
        
        
        // builder.HasOne(g => g.User)
        //        .WithMany(user => user.Games)
        //        .HasPrincipalKey(g =>g.Id)
        //        .HasForeignKey(u => u.UserId);

        // Seed
         builder.HasData(
             new Game
             {
                 Id = new Guid("b8aa36a5-9094-4dbd-80a5-444fcb92d3a4"), // Укажите явный Id (если есть)
                 Board = new string?[][] 
                 {
                     new string?[] { "X", "O", null },
                     new string?[] { null, "X", "O" },
                     new string?[] { "O", null, "X" }
                 },
                 CreatedAt = new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc)
                 
             }
         );
    }
}

Migration

if (app.Environment.IsProduction())
{
    using var scope = app.Services.CreateScope();
    var dbContext = scope.ServiceProvider.GetRequiredService<TicTacToeContext>();
    dbContext.Database.EnsureDeleted();
    dbContext.Database.Migrate();
}

FluentAPI

OnModelCreating

 protected override void OnModelCreating(ModelBuilder modelBuilder)
 {
     // Configure indexes and relationships
     ConfigureEntityRelationships(modelBuilder);

     // Seed initial data
     SeedInitialData(modelBuilder);

     // Configure property conversions for User entity
     ConfigureUserPropertyConversions(modelBuilder);
 }

 // Configure indexes and relationships
 private void ConfigureEntityRelationships(ModelBuilder modelBuilder)
 {
     // Ensure Username is unique
     modelBuilder.Entity<User>().HasIndex(u => u.Username).IsUnique();

     // Configure one-to-many relationship between User and Record entities
     modelBuilder.Entity<Record>()
         .HasOne(p => p.User)
         .WithMany(u => u.Records)
         .HasForeignKey(p => p.UserId)
         .OnDelete(DeleteBehavior.Cascade);

     // Auto-include navigation property Records when querying User
     modelBuilder.Entity<User>()
         .Navigation(u => u.Records).AutoInclude();
 }

 // Seed initial data
 private void SeedInitialData(ModelBuilder modelBuilder)
 {
     var user = User.CreateUser(new Username("SuperAdmin2077CP"),
         new Password(HashPassword("qwerty28042002")),
         new Email("[email protected]", true),
         Role.Admin,
         "ConfirmToken");

     var record = new Record
     {
         Id = Guid.NewGuid(),
         Title = "My day",
         Url = new Uri("https://www.youtube.com/"),
         DateCreated = DateTime.Now,
         DeadLine = DateTime.Now.AddMonths(1),
         Likes = 183,
         DisLikes = 13,
         IsPrivate = false,
         UserId = user.Id,
     };

     // Add initial data to User and Record entities
     modelBuilder.Entity<User>().HasData(user);
     modelBuilder.Entity<Record>().HasData(record);
 }

 // Hash a password
 private string HashPassword(string password)
 {
     var hashedBytes = SHA256.HashData(Encoding.UTF8.GetBytes(password));
     var hash = BitConverter.ToString(hashedBytes).Replace("-", "").ToLower();
     return hash;
 }

 // Configure property conversions for User entity
 private void ConfigureUserPropertyConversions(ModelBuilder modelBuilder)
 {
     // Configure conversion for Username property
     modelBuilder.Entity<User>()
         .Property(u => u.Username)
         .HasConversion(
             u => u.Value,
             u => new Username(u));

     // Configure conversion for Password property
     modelBuilder.Entity<User>()
         .Property(u => u.Password)
         .HasConversion(
             p => p.Value,
             p => new Password(p));

     // Configure conversion for Email property
     modelBuilder.Entity<User>()
         .Property(u => u.Email)
         .HasConversion(
             e => e.Value,
             e => new Email(e, true));
 }

Migration

Add-Migraion Init -Project "RusRoads.Application" -StartupProject "RusRoads.Application"
Update-database -Project "RusRoads.Application" -StartupProject "RusRoads.Application"

Тестовые данные

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    //seed categories
    modelBuilder.Entity<Country>().HasData(new Country { CountryId = 1, Name = "Belgium" });
    modelBuilder.Entity<Country>().HasData(new Country { CountryId = 2, Name = "Germany" });
    modelBuilder.Entity<Country>().HasData(new Country { CountryId = 3, Name = "Netherlands" });
    modelBuilder.Entity<Country>().HasData(new Country { CountryId = 4, Name = "USA" });
    modelBuilder.Entity<Country>().HasData(new Country { CountryId = 5, Name = "Japan" });
    modelBuilder.Entity<Country>().HasData(new Country { CountryId = 6, Name = "China" });
    modelBuilder.Entity<Country>().HasData(new Country { CountryId = 7, Name = "UK" });
    modelBuilder.Entity<Country>().HasData(new Country { CountryId = 8, Name = "France" });
    modelBuilder.Entity<Country>().HasData(new Country { CountryId = 9, Name = "Brazil" });

    modelBuilder.Entity<JobCategory>().HasData(new JobCategory(){JobCategoryId = 1, JobCategoryName = "Pie research"});
    modelBuilder.Entity<JobCategory>().HasData(new JobCategory(){JobCategoryId = 2, JobCategoryName = "Sales"});
    modelBuilder.Entity<JobCategory>().HasData(new JobCategory(){JobCategoryId = 3, JobCategoryName = "Management"});
    modelBuilder.Entity<JobCategory>().HasData(new JobCategory(){JobCategoryId = 4, JobCategoryName = "Store staff"});
    modelBuilder.Entity<JobCategory>().HasData(new JobCategory(){JobCategoryId = 5, JobCategoryName = "Finance"});
    modelBuilder.Entity<JobCategory>().HasData(new JobCategory(){JobCategoryId = 6, JobCategoryName = "QA"});
    modelBuilder.Entity<JobCategory>().HasData(new JobCategory(){JobCategoryId = 7, JobCategoryName = "IT"});
    modelBuilder.Entity<JobCategory>().HasData(new JobCategory(){JobCategoryId = 8, JobCategoryName = "Cleaning"});
    modelBuilder.Entity<JobCategory>().HasData(new JobCategory(){JobCategoryId = 9, JobCategoryName = "Bakery"});

    modelBuilder.Entity<Employee>().HasData(new Employee
    {
        EmployeeId = 1,
        CountryId = 1,
        MaritalStatus = MaritalStatus.Single,
        BirthDate = new DateTime(1979, 1, 16),
        City = "Brussels",
        Email = "[email protected]",
        FirstName = "Bethany",
        LastName = "Smith",
        Gender = Gender.Female,
        PhoneNumber = "324777888773",
        Smoker = false,
        Street = "Grote Markt 1",
        Zip = "1000",
        JobCategoryId = 1,
        Comment = "Lorem Ipsum",
        ExitDate = null,
        JoinedDate = new DateTime(2015, 3, 1),
        Latitude = 50.8503, 
        Longitude = 4.3517
    });
}

Генерация контроллеров и конечных точек

Если класс контекста лежит в другом проекте, то рядом с классом контекста создаем класс

public class ExampleContextFactory : IDesignTimeDbContextFactory<ExampleContext>
{
    public ExampleContext CreateDbContext(string[] args)
    {
        var optionsBuilder = new DbContextOptionsBuilder<ExampleContext>();
        optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Database=Example;Username=postgres;Password=root");

        return new ExampleContext(optionsBuilder.Options);
    }
}

AsNoTracking

API GetBooks возвращает коллекцию объектов Book. Ни одна из этих сущностей не изменяется. EF Core по умолчанию записывает все, что происходит с сущностями, которые он возвращает из запросов или добавляет в контекст. Эта функция, называемая отслеживанием изменений, является основной частью того, что делает EF Core таким мощным. Например, если код изменяет свойство Title объекта Book, EF Core записывает это изменение, а при вызове SaveChanges преобразует его в SQL-оператор UPDATE. Когда вы запрашиваете данные и не хотите вносить изменения, отключите отслеживание изменений, чтобы предотвратить случайные изменения, а также сэкономить память и процессор. На уровне запроса можно отключить отслеживание изменений с помощью метода расширения AsNoTracking.

IAsyncEnumerable.

В отличие от IEnumerable, который возвращает синхронный перечислитель. Синхронные перечислители позволяют избежать блокировки потоков, сначала получая все записи, а затем перечисляя их. Если вы хотите отправлять записи обратно в ответ по мере их получения из базы данных, не блокируя поток, IEnumerable не подойдет. IAsyncEnumerable позволяет действию начать отправку данных ответа, как только запрос базы данных начнет получать записи из базы данных. Без IAsyncEnumerable действие должно ждать отправки ответа, пока не будут получены все записи из базы данных и не будет готово полное содержимое ответа. Хотя это действие не имеет особых преимуществ для нашего примера, оно полезно для сервисов, которые работают с большим количеством данных (или медленными запросами).

⚠️ **GitHub.com Fallback** ⚠️