This commit is contained in:
gamer147
2024-09-05 08:32:54 -04:00
parent 8d62c9f238
commit ee7e276036
45 changed files with 1506 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
using System.Reflection;
using DCGEngine.Database.Models;
using Microsoft.EntityFrameworkCore;
namespace DCGEngine.Database.Configuration;
public class DCGEDatabaseConfiguration
{
/// <summary>
/// The default name of the appsettings section where these are configured.
/// </summary>
public const string DefaultSectionName = "DCGEDatabaseConfiguration";
#region Appsettings
// TODO
#endregion
#region Manual Configuration
/// <summary>
/// Assemblies to be searched for classes implementing <see cref="BaseEntity{TKey}"/> to be added as <see cref="DbSet{TEntity}"/>s. Should be set in code, not in appsettings.
/// </summary>
public List<Assembly> DbSetSearchAssemblies { get; set; }
#endregion
}

View File

@@ -0,0 +1,72 @@
using DCGEngine.Database.Configuration;
using DCGEngine.Database.Interfaces;
using DCGEngine.Database.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DCGEngine.Database;
public class DCGEDbContext : DbContext
{
private readonly DCGEDatabaseConfiguration _configuration;
private readonly ILogger _logger;
public DCGEDbContext(IOptions<DCGEDatabaseConfiguration> configuration, ILogger<DCGEDbContext> logger, DbContextOptions options) : base(options)
{
_logger = logger;
_configuration = configuration.Value;
}
/// <inheritdoc/>
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
foreach (var entityEntry in ChangeTracker.Entries())
{
if (entityEntry.Entity is ITimeTrackedEntity timeTrackedEntity)
{
if (entityEntry.State is EntityState.Added && timeTrackedEntity.DateCreated is null)
{
timeTrackedEntity.DateCreated = DateTime.UtcNow;
}
if (entityEntry.State is EntityState.Modified or EntityState.Added)
{
timeTrackedEntity.DateUpdated = DateTime.UtcNow;
}
}
}
return await base.SaveChangesAsync(cancellationToken);
}
/// <inheritdoc/>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
_configuration.DbSetSearchAssemblies.ForEach(assembly =>
{
foreach (var typeInfo in assembly.DefinedTypes.Where(type => type.IsAssignableTo(typeof(IDbTrackedEntity))))
{
modelBuilder.Entity(typeInfo.AsType());
}
});
base.OnModelCreating(modelBuilder);
}
public void UpdateDatabase()
{
IEnumerable<string> pendingMigrations = Database.GetPendingMigrations();
if (!pendingMigrations.Any())
{
return;
}
foreach (string migration in pendingMigrations)
{
_logger.LogInformation("Found pending migration with name {migrationName}.", migration);
}
_logger.LogInformation("Attempting to apply pending migrations...");
Database.Migrate();
_logger.LogInformation("Migrations applied.");
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="Attributes\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,11 @@
using Microsoft.EntityFrameworkCore;
namespace DCGEngine.Database.Interfaces;
/// <summary>
/// Indicates a class should have a <see cref="DbSet{TEntity}"/> created and be tracked by the <see cref="DbContext"/>.
/// </summary>
public interface IDbTrackedEntity
{
}

View File

@@ -0,0 +1,14 @@
namespace DCGEngine.Database.Interfaces;
public interface ITimeTrackedEntity
{
/// <summary>
/// The <see cref="DateTime"/> this entity was first added to the database.
/// </summary>
public DateTime? DateCreated { get; set; }
/// <summary>
/// The <see cref="DateTime"/> this entity was last updated.
/// </summary>
public DateTime? DateUpdated { get; set; }
}

View File

@@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
using DCGEngine.Database.Interfaces;
namespace DCGEngine.Database.Models;
public class BaseEntity<TKey> : ITimeTrackedEntity, IDbTrackedEntity
{
[Key]
public virtual TKey Id { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateUpdated { get; set; }
}

View File

@@ -0,0 +1,27 @@
namespace DCGEngine.Database.Models;
/// <summary>
/// A card within the system.
/// </summary>
public abstract class CardEntry : BaseEntity<long>
{
/// <summary>
/// The name of this card used internally, to separate it from any localization key stored.
/// </summary>
public virtual string InternalName { get; set; }
/// <summary>
/// The offensive power of this card.
/// </summary>
public virtual int? Attack { get; set; }
/// <summary>
/// The defensive power of this card.
/// </summary>
public virtual int? Defense { get; set; }
/// <summary>
/// How much of the primary resource (ie mana, energy) does this card cost to play in a match.
/// </summary>
public virtual int? PrimaryResourceCost { get; set; }
}

View File

@@ -0,0 +1,17 @@
namespace DCGEngine.Database.Models;
/// <summary>
/// A deck consisting of multiple <see cref="CardEntry"/> stored in the DB.
/// </summary>
public abstract class DeckEntry : BaseEntity<long>
{
/// <summary>
/// How this deck is referred to internally.
/// </summary>
public virtual string InternalName { get; set; }
/// <summary>
/// The cards present in this deck.
/// </summary>
public virtual List<CardEntry> Cards { get; set; }
}