Basics setup, going to change how repos work
This commit is contained in:
69
DBConnection/AppDbContext.cs
Normal file
69
DBConnection/AppDbContext.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.Reflection;
|
||||
using DBConnection.Interfaces;
|
||||
using DBConnection.ModelBuilders;
|
||||
using DBConnection.Models;
|
||||
using DBConnection.Seeders;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DBConnection;
|
||||
|
||||
public class AppDbContext : DbContext
|
||||
{
|
||||
public DbSet<Novel> Novels { get; set; }
|
||||
public DbSet<Chapter> Chapters { get; set; }
|
||||
public DbSet<Author> Authors { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
public DbSet<Tag> Tags { get; set; }
|
||||
public DbSet<UserNovel> UserNovels { get; set; }
|
||||
|
||||
private readonly IEnumerable<ISeeder> _seeders =
|
||||
from t in Assembly.GetExecutingAssembly().GetTypes()
|
||||
where t.IsClass && t.Namespace.Contains(nameof(DBConnection.Seeders)) && typeof(ISeeder).IsAssignableFrom(t)
|
||||
select (ISeeder) Activator.CreateInstance(t);
|
||||
|
||||
private static readonly IEnumerable<IModelBuilder> ModelBuilders =
|
||||
from t in Assembly.GetExecutingAssembly().GetTypes()
|
||||
where t.IsClass && t.Namespace.Contains(nameof(DBConnection.ModelBuilders)) && typeof(IModelBuilder).IsAssignableFrom(t)
|
||||
select (IModelBuilder) Activator.CreateInstance(t);
|
||||
|
||||
public AppDbContext(DbContextOptions options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
UpdateAuditInfo();
|
||||
return base.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private void UpdateAuditInfo()
|
||||
{
|
||||
var entries = ChangeTracker.Entries().Where(x =>
|
||||
x.Entity is BaseEntity && (x.State == EntityState.Added || x.State == EntityState.Modified));
|
||||
foreach(var entry in entries) {
|
||||
if (entry.State == EntityState.Added)
|
||||
{
|
||||
((BaseEntity)entry.Entity).DateCreated = DateTime.Now;
|
||||
}
|
||||
((BaseEntity)entry.Entity).DateModified = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
foreach (var builder in ModelBuilders)
|
||||
{
|
||||
builder.BuildModel(modelBuilder);
|
||||
}
|
||||
foreach (var seeder in _seeders)
|
||||
{
|
||||
seeder.SeedData(modelBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
}
|
||||
}
|
||||
24
DBConnection/DBConnection.csproj
Normal file
24
DBConnection/DBConnection.csproj
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.7" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Interfaces" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
9
DBConnection/Enums/NovelStatus.cs
Normal file
9
DBConnection/Enums/NovelStatus.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DBConnection.Enums;
|
||||
|
||||
public enum NovelStatus
|
||||
{
|
||||
InProgress,
|
||||
Completed,
|
||||
Hiatus,
|
||||
Unknown
|
||||
}
|
||||
40
DBConnection/Extensions/BuilderExtensions.cs
Normal file
40
DBConnection/Extensions/BuilderExtensions.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Reflection;
|
||||
using DBConnection.Repositories.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DBConnection.Extensions;
|
||||
|
||||
public static class BuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds dbcontext and all repositories in repository namespace
|
||||
/// </summary>
|
||||
/// <param name="collection">service collection</param>
|
||||
/// <param name="config">configuration</param>
|
||||
public static void AddDbServices(this IServiceCollection collection, IConfiguration config)
|
||||
{
|
||||
string dbConnectionString = config.GetConnectionString("DefaultConnection");
|
||||
collection.AddDbContext<AppDbContext>(opt =>
|
||||
{
|
||||
opt.UseNpgsql(dbConnectionString);
|
||||
});
|
||||
Type[] repositories = Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(t => t.IsClass && !t.IsAbstract && t.Namespace.Contains(nameof(DBConnection.Repositories)) && typeof(IRepository).IsAssignableFrom(t)).ToArray();
|
||||
foreach (var repo in repositories)
|
||||
{
|
||||
var repoInterface = repo.GetInterfaces()
|
||||
.FirstOrDefault(repoInterface => typeof(IRepository).IsAssignableFrom(repoInterface) && repoInterface != typeof(IRepository));
|
||||
if (repoInterface != null)
|
||||
{
|
||||
collection.AddScoped(repoInterface, repo);
|
||||
}
|
||||
else
|
||||
{
|
||||
collection.AddScoped(repo);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
DBConnection/ModelBuilders/IModelBuilder.cs
Normal file
11
DBConnection/ModelBuilders/IModelBuilder.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DBConnection.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// ModelBuilders are used for customizing DB models with properties not settable via annotations
|
||||
/// </summary>
|
||||
public interface IModelBuilder
|
||||
{
|
||||
public ModelBuilder BuildModel(ModelBuilder builder);
|
||||
}
|
||||
15
DBConnection/ModelBuilders/UserNovelBuilder.cs
Normal file
15
DBConnection/ModelBuilders/UserNovelBuilder.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using DBConnection.Interfaces;
|
||||
using DBConnection.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DBConnection.ModelBuilders;
|
||||
|
||||
public class UserNovelBuilder : IModelBuilder
|
||||
{
|
||||
public ModelBuilder BuildModel(ModelBuilder builder)
|
||||
{
|
||||
var userNovelModelBuilder = builder.Entity<UserNovel>();
|
||||
userNovelModelBuilder.HasKey(un => new {un.NovelUrl, un.UserId});
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
10
DBConnection/Models/Author.cs
Normal file
10
DBConnection/Models/Author.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DBConnection.Models;
|
||||
|
||||
public class Author : BaseEntity
|
||||
{
|
||||
[Key]
|
||||
public string Url { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
7
DBConnection/Models/BaseEntity.cs
Normal file
7
DBConnection/Models/BaseEntity.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DBConnection.Models;
|
||||
|
||||
public abstract class BaseEntity
|
||||
{
|
||||
public DateTime DateCreated { get; set; }
|
||||
public DateTime DateModified { get; set; }
|
||||
}
|
||||
15
DBConnection/Models/Chapter.cs
Normal file
15
DBConnection/Models/Chapter.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DBConnection.Models;
|
||||
|
||||
public class Chapter : BaseEntity
|
||||
{
|
||||
[Key]
|
||||
public int ChapterNumber { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string? Content { get; set; }
|
||||
public string? RawContent { get; set; }
|
||||
public string Url { get; set; }
|
||||
public DateTime DatePosted { get; set; }
|
||||
public DateTime DateUpdated { get; set; }
|
||||
}
|
||||
15
DBConnection/Models/Novel.cs
Normal file
15
DBConnection/Models/Novel.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DBConnection.Models;
|
||||
|
||||
public class Novel : BaseEntity
|
||||
{
|
||||
[Key]
|
||||
public string Url { get; set; }
|
||||
public string Title { get; set; }
|
||||
public Author Author { get; set; }
|
||||
public List<Tag> Tags { get; set; }
|
||||
public List<Chapter> Chapters { get; set; }
|
||||
public DateTime LastUpdated { get; set; }
|
||||
public DateTime DatePosted { get; set; }
|
||||
}
|
||||
12
DBConnection/Models/Tag.cs
Normal file
12
DBConnection/Models/Tag.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace DBConnection.Models;
|
||||
|
||||
public class Tag : BaseEntity
|
||||
{
|
||||
[Key]
|
||||
public string TagValue { get; set; }
|
||||
[JsonIgnore]
|
||||
public List<Novel> Novels { get; set; }
|
||||
}
|
||||
11
DBConnection/Models/User.cs
Normal file
11
DBConnection/Models/User.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DBConnection.Models;
|
||||
|
||||
public class User : BaseEntity
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
public string Email { get; set; }
|
||||
public List<UserNovel> WatchedNovels { get; set; }
|
||||
}
|
||||
14
DBConnection/Models/UserNovel.cs
Normal file
14
DBConnection/Models/UserNovel.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace DBConnection.Models;
|
||||
|
||||
public class UserNovel
|
||||
{
|
||||
[JsonIgnore]
|
||||
public int UserId { get; set; }
|
||||
public string NovelUrl { get; set; }
|
||||
public Novel Novel { get; set; }
|
||||
public User User { get; set; }
|
||||
public int LastChapterRead { get; set; }
|
||||
}
|
||||
59
DBConnection/Repositories/BaseRepository.cs
Normal file
59
DBConnection/Repositories/BaseRepository.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Reflection;
|
||||
using DBConnection.Models;
|
||||
using DBConnection.Repositories.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NuGet.Configuration;
|
||||
|
||||
namespace DBConnection.Repositories;
|
||||
|
||||
public abstract class BaseRepository<TEntityType> : IRepository<TEntityType> where TEntityType : BaseEntity
|
||||
{
|
||||
protected readonly AppDbContext DbContext;
|
||||
|
||||
private object?[]? GetPrimaryKey(TEntityType entity)
|
||||
{
|
||||
var keyProperties = DbContext.Model.FindEntityType(typeof(TEntityType))?.FindPrimaryKey()?.Properties.Select(p => p.Name);
|
||||
return keyProperties?.Select(p => entity.GetType().GetProperty(p)?.GetValue(entity, null)).ToArray();
|
||||
}
|
||||
|
||||
protected abstract IQueryable<TEntityType> GetAllIncludedQueryable();
|
||||
|
||||
public BaseRepository(AppDbContext dbContext)
|
||||
{
|
||||
DbContext = dbContext;
|
||||
}
|
||||
|
||||
public virtual TEntityType Delete(TEntityType entity)
|
||||
{
|
||||
DbContext.Set<TEntityType>().Remove(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual async Task<TEntityType> Upsert(TEntityType entity)
|
||||
{
|
||||
bool exists = await DbContext.Set<TEntityType>().ContainsAsync(entity);
|
||||
if (!exists)
|
||||
{
|
||||
DbContext.Set<TEntityType>().Add(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dbEntry = await Get(entity);
|
||||
DbContext.Entry(dbEntry).CurrentValues.SetValues(entity);
|
||||
}
|
||||
|
||||
await DbContext.SaveChangesAsync();
|
||||
return entity;
|
||||
}
|
||||
|
||||
public virtual async Task<TEntityType?> Get(TEntityType entity)
|
||||
{
|
||||
var keyValues = GetPrimaryKey(entity);
|
||||
return await Get(keyValues);
|
||||
}
|
||||
|
||||
public virtual async Task<TEntityType?> Get(params object?[]? keyValues)
|
||||
{
|
||||
return await DbContext.Set<TEntityType>().Include(j => j.DateCreated).FindAsync(keyValues);
|
||||
}
|
||||
}
|
||||
8
DBConnection/Repositories/Interfaces/INovelRepository.cs
Normal file
8
DBConnection/Repositories/Interfaces/INovelRepository.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using DBConnection.Models;
|
||||
|
||||
namespace DBConnection.Repositories.Interfaces;
|
||||
|
||||
public interface INovelRepository : IRepository<Novel>
|
||||
{
|
||||
|
||||
}
|
||||
14
DBConnection/Repositories/Interfaces/IRepository.cs
Normal file
14
DBConnection/Repositories/Interfaces/IRepository.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace DBConnection.Repositories.Interfaces;
|
||||
|
||||
public interface IRepository
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public interface IRepository<TEntityType> : IRepository where TEntityType : class
|
||||
{
|
||||
TEntityType Delete(TEntityType entity);
|
||||
Task<TEntityType> Upsert(TEntityType entity);
|
||||
Task<TEntityType?> Get(TEntityType entity);
|
||||
Task<TEntityType?> Get(params object?[]? keyValues);
|
||||
}
|
||||
22
DBConnection/Repositories/NovelRepository.cs
Normal file
22
DBConnection/Repositories/NovelRepository.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using DBConnection.Models;
|
||||
using DBConnection.Repositories.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DBConnection.Repositories;
|
||||
|
||||
public class NovelRepository : BaseRepository<Novel>, INovelRepository
|
||||
{
|
||||
|
||||
public NovelRepository(AppDbContext dbContext) : base(dbContext)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IQueryable<Novel> GetAllIncludedQueryable()
|
||||
{
|
||||
return DbContext.Novels
|
||||
.Include(i => i.Author)
|
||||
.Include(i => i.Chapters)
|
||||
.Include(i => i.Tags);
|
||||
}
|
||||
|
||||
}
|
||||
12
DBConnection/Seeders/ISeeder.cs
Normal file
12
DBConnection/Seeders/ISeeder.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DBConnection.Seeders;
|
||||
|
||||
/// <summary>
|
||||
/// Used for seeding the database with pre-existing data
|
||||
/// </summary>
|
||||
public interface ISeeder
|
||||
{
|
||||
public static string SeedDataDirectory = @"Data/SeedData/";
|
||||
public ModelBuilder SeedData(ModelBuilder builder);
|
||||
}
|
||||
Reference in New Issue
Block a user