feat: dbcontext pooling & lazy loading

This commit is contained in:
Timothy Schenk 2023-11-16 12:06:36 +01:00
parent 5abe7f7564
commit 316881266b
10 changed files with 433 additions and 22 deletions

View file

@ -20,7 +20,10 @@ public class Account
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Column(TypeName = "varchar(20)")] public string Username { get; set; }
[Column(TypeName = "varchar(20)")]
[MaxLength(20)]
public string Username { get; set; }
[Column(TypeName = "bytea")] public byte[] Password { get; set; }
[EmailAddress] public string Email { get; set; }

View file

@ -1,10 +1,12 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Wonderking.Game.Data.Character;
using Wonderking.Packets.Outgoing.Data;
namespace Server.DB.Documents;
[Index(nameof(Name), IsUnique = true), Index(nameof(Id), IsUnique = true)]
public class Character
{
public virtual Account Account { get; set; }
@ -14,7 +16,11 @@ public class Character
public Guid Id { get; set; }
public ushort MapId { get; set; }
[Column(TypeName = "varchar(20)")]
[MaxLength(20)]
public string Name { get; set; }
public short LastXCoordinate { get; set; }
public short LastYCoordinate { get; set; }
public PvPLevel PvPLevel { get; set; }

View file

@ -9,7 +9,7 @@ public class GuildMember
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public Character Character { get; set; }
public Guild Guild { get; set; }
public virtual Character Character { get; set; }
public virtual Guild Guild { get; set; }
public GuildRank Rank { get; set; }
}

View file

@ -3,7 +3,7 @@ namespace Server.DB.Documents;
public class InventoryItem
{
public Guid CharacterId { get; set; }
public Character Character { get; set; }
public virtual Character Character { get; set; }
public Guid Id { get; set; }
public ushort ItemId { get; set; }
public ushort Count { get; set; }

View file

@ -0,0 +1,333 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Server.DB;
#nullable disable
namespace Server.DB.Migrations
{
[DbContext(typeof(WonderkingContext))]
[Migration("20231116110504_DBPoolingAndLazyLoadingSupport")]
partial class DBPoolingAndLazyLoadingSupport
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true)
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Server.DB.Documents.Account", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Email")
.HasColumnType("text");
b.Property<byte[]>("Password")
.HasColumnType("bytea");
b.Property<byte>("PermissionLevel")
.HasColumnType("smallint");
b.Property<byte[]>("Salt")
.HasColumnType("bytea");
b.Property<string>("Username")
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.HasKey("Id");
b.HasIndex("Id")
.IsUnique();
b.HasIndex("Username")
.IsUnique();
b.ToTable("Accounts");
});
modelBuilder.Entity("Server.DB.Documents.Character", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("AccountId")
.HasColumnType("uuid");
b.Property<long>("Experience")
.HasColumnType("bigint");
b.Property<byte>("Gender")
.HasColumnType("smallint");
b.Property<Guid?>("GuildId")
.HasColumnType("uuid");
b.Property<int>("Health")
.HasColumnType("integer");
b.Property<short>("LastXCoordinate")
.HasColumnType("smallint");
b.Property<short>("LastYCoordinate")
.HasColumnType("smallint");
b.Property<byte>("Level")
.HasColumnType("smallint");
b.Property<int>("Mana")
.HasColumnType("integer");
b.Property<int>("MapId")
.HasColumnType("integer");
b.Property<string>("Name")
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.Property<byte>("PvPLevel")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("AccountId");
b.HasIndex("GuildId");
b.HasIndex("Id")
.IsUnique();
b.HasIndex("Name")
.IsUnique();
b.ToTable("Characters");
});
modelBuilder.Entity("Server.DB.Documents.Guild", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("Notice")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Guild");
});
modelBuilder.Entity("Server.DB.Documents.GuildMember", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("CharacterId")
.HasColumnType("uuid");
b.Property<Guid?>("GuildId")
.HasColumnType("uuid");
b.Property<byte>("Rank")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("CharacterId");
b.HasIndex("GuildId");
b.ToTable("GuildMember");
});
modelBuilder.Entity("Server.DB.Documents.InventoryItem", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<byte>("AddOption")
.HasColumnType("smallint");
b.Property<byte>("AddOption2")
.HasColumnType("smallint");
b.Property<byte>("AddOption3")
.HasColumnType("smallint");
b.Property<Guid>("CharacterId")
.HasColumnType("uuid");
b.Property<int>("Count")
.HasColumnType("integer");
b.Property<byte>("InventoryTab")
.HasColumnType("smallint");
b.Property<int>("ItemId")
.HasColumnType("integer");
b.Property<byte>("Level")
.HasColumnType("smallint");
b.Property<short>("Option")
.HasColumnType("smallint");
b.Property<short>("Option2")
.HasColumnType("smallint");
b.Property<short>("Option3")
.HasColumnType("smallint");
b.Property<byte>("Rarity")
.HasColumnType("smallint");
b.Property<byte>("Slot")
.HasColumnType("smallint");
b.HasKey("Id");
b.HasIndex("CharacterId");
b.ToTable("InventoryItem");
});
modelBuilder.Entity("Server.DB.Documents.Character", b =>
{
b.HasOne("Server.DB.Documents.Account", "Account")
.WithMany("Characters")
.HasForeignKey("AccountId");
b.HasOne("Server.DB.Documents.Guild", "Guild")
.WithMany()
.HasForeignKey("GuildId");
b.OwnsOne("Wonderking.Packets.Outgoing.Data.BaseStats", "BaseStats", b1 =>
{
b1.Property<Guid>("CharacterId")
.HasColumnType("uuid");
b1.Property<short>("Dexterity")
.HasColumnType("smallint");
b1.Property<short>("Intelligence")
.HasColumnType("smallint");
b1.Property<short>("Luck")
.HasColumnType("smallint");
b1.Property<short>("Strength")
.HasColumnType("smallint");
b1.Property<short>("Vitality")
.HasColumnType("smallint");
b1.Property<short>("Wisdom")
.HasColumnType("smallint");
b1.HasKey("CharacterId");
b1.ToTable("Characters");
b1.WithOwner()
.HasForeignKey("CharacterId");
});
b.OwnsOne("Wonderking.Packets.Outgoing.Data.JobData", "JobData", b1 =>
{
b1.Property<Guid>("CharacterId")
.HasColumnType("uuid");
b1.Property<byte>("FirstJob")
.HasColumnType("smallint");
b1.Property<byte>("FourthJob")
.HasColumnType("smallint");
b1.Property<byte>("SecondJob")
.HasColumnType("smallint");
b1.Property<byte>("ThirdJob")
.HasColumnType("smallint");
b1.HasKey("CharacterId");
b1.ToTable("Characters");
b1.WithOwner()
.HasForeignKey("CharacterId");
});
b.Navigation("Account");
b.Navigation("BaseStats");
b.Navigation("Guild");
b.Navigation("JobData");
});
modelBuilder.Entity("Server.DB.Documents.GuildMember", b =>
{
b.HasOne("Server.DB.Documents.Character", "Character")
.WithMany()
.HasForeignKey("CharacterId");
b.HasOne("Server.DB.Documents.Guild", "Guild")
.WithMany("GuildMembers")
.HasForeignKey("GuildId");
b.Navigation("Character");
b.Navigation("Guild");
});
modelBuilder.Entity("Server.DB.Documents.InventoryItem", b =>
{
b.HasOne("Server.DB.Documents.Character", "Character")
.WithMany("InventoryItems")
.HasForeignKey("CharacterId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Character");
});
modelBuilder.Entity("Server.DB.Documents.Account", b =>
{
b.Navigation("Characters");
});
modelBuilder.Entity("Server.DB.Documents.Character", b =>
{
b.Navigation("InventoryItems");
});
modelBuilder.Entity("Server.DB.Documents.Guild", b =>
{
b.Navigation("GuildMembers");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,57 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Server.DB.Migrations;
/// <inheritdoc />
public partial class DBPoolingAndLazyLoadingSupport : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Characters",
type: "varchar(20)",
maxLength: 20,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.CreateIndex(
name: "IX_Characters_Id",
table: "Characters",
column: "Id",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Characters_Name",
table: "Characters",
column: "Name",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Characters_Id",
table: "Characters");
migrationBuilder.DropIndex(
name: "IX_Characters_Name",
table: "Characters");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Characters",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(20)",
oldMaxLength: 20,
oldNullable: true);
}
}

View file

@ -18,6 +18,9 @@ namespace Server.DB.Migrations
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.0")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true)
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
@ -41,6 +44,7 @@ namespace Server.DB.Migrations
.HasColumnType("bytea");
b.Property<string>("Username")
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.HasKey("Id");
@ -91,7 +95,8 @@ namespace Server.DB.Migrations
.HasColumnType("integer");
b.Property<string>("Name")
.HasColumnType("text");
.HasMaxLength(20)
.HasColumnType("varchar(20)");
b.Property<byte>("PvPLevel")
.HasColumnType("smallint");
@ -102,6 +107,12 @@ namespace Server.DB.Migrations
b.HasIndex("GuildId");
b.HasIndex("Id")
.IsUnique();
b.HasIndex("Name")
.IsUnique();
b.ToTable("Characters");
});

View file

@ -1,27 +1,16 @@
using JetBrains.Annotations;
namespace Server.DB;
using Documents;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
public class WonderkingContext : DbContext
{
private readonly IConfiguration _configuration;
private readonly ILoggerFactory _loggerFactory;
public WonderkingContext(ILoggerFactory loggerFactory, IConfiguration configuration)
public WonderkingContext([NotNull] DbContextOptions options) : base(options)
{
this._loggerFactory = loggerFactory;
this._configuration = configuration;
}
public DbSet<Account> Accounts { get; set; }
public DbSet<Character> Characters { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder
.UseNpgsql(
$"Host={this._configuration["DB:Host"]};Username={this._configuration["DB:Username"]};Password={this._configuration["DB:Password"]};Database={this._configuration["DB:Database"]};Port={this._configuration["DB:Port"]}")
.EnableSensitiveDataLogging().UseLoggerFactory(this._loggerFactory);
}

View file

@ -17,10 +17,21 @@ builder.Configuration.AddJsonFile("settings.json", true, true)
.AddJsonFile($"settings.{builder.Environment.EnvironmentName}.json", true)
.AddEnvironmentVariables().Build();
builder.Services.AddLogging();
builder.Logging.AddFile("Logs/Server-{Date}.log", LogLevel.Trace);
builder.Logging.AddFile("Logs/Server-{Date}.json.log", LogLevel.Trace, isJson: true);
var loggerFactory = LoggerFactory.Create(loggingBuilder =>
{
loggingBuilder.AddFile("Logs/Server-{Date}.log", LogLevel.Trace);
loggingBuilder.AddFile("Logs/Server-{Date}.json.log", LogLevel.Trace, isJson: true);
loggingBuilder.AddConsole();
});
builder.Services.AddEntityFrameworkNpgsql();
builder.Services.AddDbContext<WonderkingContext>();
builder.Services.AddDbContextPool<WonderkingContext>(o =>
{
using var configuration = builder.Configuration;
o.UseNpgsql(
$"Host={configuration["DB:Host"]};Username={configuration["DB:Username"]};Password={configuration["DB:Password"]};Database={configuration["DB:Database"]};Port={configuration["DB:Port"]}")
.EnableSensitiveDataLogging().UseLazyLoadingProxies().UseLoggerFactory(loggerFactory);
});
builder.Services.AddSingleton<ILoggerFactory>(loggerFactory);
builder.Services.AddSingleton<PacketDistributorService>();
builder.Services.AddSingleton<ItemObjectPoolService>();
builder.Services.AddHostedService<ItemObjectPoolService>();

View file

@ -66,6 +66,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="8.0.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>