Initial efcore migration and updates to make sure upserting novels (mostly) works. still need to do chapter handling

This commit is contained in:
2022-07-14 23:12:12 -04:00
parent 5402923e9f
commit 5337e7ccb8
25 changed files with 962 additions and 64 deletions

View File

@@ -18,14 +18,14 @@ public static class BuilderExtensions
string dbConnectionString = config.GetConnectionString("DefaultConnection");
collection.AddDbContext<AppDbContext>(opt =>
{
opt.UseNpgsql(dbConnectionString);
opt.UseSqlite(dbConnectionString);
});
Type[] repositories = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && (t.Namespace?.Contains(nameof(DBConnection.Repositories)) ?? false) && typeof(IRepository).IsAssignableFrom(t)).ToArray();
foreach (var repo in repositories)
{
var repoInterface = repo.GetInterfaces()
.FirstOrDefault(repoInterface => typeof(IRepository).IsAssignableFrom(repoInterface) && repoInterface != typeof(IRepository));
.FirstOrDefault(repoInterface => typeof(IRepository).IsAssignableFrom(repoInterface) && repoInterface != typeof(IRepository) && !repoInterface.IsGenericType);
if (repoInterface != null)
{
collection.AddScoped(repoInterface, repo);

View File

@@ -0,0 +1,250 @@
// <auto-generated />
using System;
using DBConnection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace DBConnection.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20220715030913_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.7");
modelBuilder.Entity("DBConnection.Models.Author", b =>
{
b.Property<string>("Url")
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Url");
b.ToTable("Authors");
});
modelBuilder.Entity("DBConnection.Models.Chapter", b =>
{
b.Property<string>("Url")
.HasColumnType("TEXT");
b.Property<int>("ChapterNumber")
.HasColumnType("INTEGER");
b.Property<string>("Content")
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.Property<DateTime>("DatePosted")
.HasColumnType("TEXT");
b.Property<DateTime>("DateUpdated")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("NovelUrl")
.HasColumnType("TEXT");
b.Property<string>("RawContent")
.HasColumnType("TEXT");
b.HasKey("Url");
b.HasIndex("NovelUrl");
b.ToTable("Chapters");
});
modelBuilder.Entity("DBConnection.Models.Novel", b =>
{
b.Property<string>("Url")
.HasColumnType("TEXT");
b.Property<string>("AuthorUrl")
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.Property<DateTime>("DatePosted")
.HasColumnType("TEXT");
b.Property<DateTime>("LastUpdated")
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Url");
b.HasIndex("AuthorUrl");
b.ToTable("Novels");
});
modelBuilder.Entity("DBConnection.Models.Tag", b =>
{
b.Property<string>("TagValue")
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.HasKey("TagValue");
b.ToTable("Tags");
});
modelBuilder.Entity("DBConnection.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("DBConnection.Models.UserNovel", b =>
{
b.Property<string>("NovelUrl")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.Property<int>("LastChapterRead")
.HasColumnType("INTEGER");
b.HasKey("NovelUrl", "UserId");
b.HasIndex("UserId");
b.ToTable("UserNovels");
});
modelBuilder.Entity("NovelTag", b =>
{
b.Property<string>("NovelsUrl")
.HasColumnType("TEXT");
b.Property<string>("TagsTagValue")
.HasColumnType("TEXT");
b.HasKey("NovelsUrl", "TagsTagValue");
b.HasIndex("TagsTagValue");
b.ToTable("NovelTag");
});
modelBuilder.Entity("DBConnection.Models.Chapter", b =>
{
b.HasOne("DBConnection.Models.Novel", null)
.WithMany("Chapters")
.HasForeignKey("NovelUrl");
});
modelBuilder.Entity("DBConnection.Models.Novel", b =>
{
b.HasOne("DBConnection.Models.Author", "Author")
.WithMany("Novels")
.HasForeignKey("AuthorUrl");
b.Navigation("Author");
});
modelBuilder.Entity("DBConnection.Models.UserNovel", b =>
{
b.HasOne("DBConnection.Models.Novel", "Novel")
.WithMany()
.HasForeignKey("NovelUrl")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("DBConnection.Models.User", "User")
.WithMany("WatchedNovels")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Novel");
b.Navigation("User");
});
modelBuilder.Entity("NovelTag", b =>
{
b.HasOne("DBConnection.Models.Novel", null)
.WithMany()
.HasForeignKey("NovelsUrl")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("DBConnection.Models.Tag", null)
.WithMany()
.HasForeignKey("TagsTagValue")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("DBConnection.Models.Author", b =>
{
b.Navigation("Novels");
});
modelBuilder.Entity("DBConnection.Models.Novel", b =>
{
b.Navigation("Chapters");
});
modelBuilder.Entity("DBConnection.Models.User", b =>
{
b.Navigation("WatchedNovels");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,195 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DBConnection.Migrations
{
public partial class Initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Authors",
columns: table => new
{
Url = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false),
DateModified = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Authors", x => x.Url);
});
migrationBuilder.CreateTable(
name: "Tags",
columns: table => new
{
TagValue = table.Column<string>(type: "TEXT", nullable: false),
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false),
DateModified = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Tags", x => x.TagValue);
});
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Email = table.Column<string>(type: "TEXT", nullable: false),
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false),
DateModified = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Novels",
columns: table => new
{
Url = table.Column<string>(type: "TEXT", nullable: false),
Title = table.Column<string>(type: "TEXT", nullable: false),
AuthorUrl = table.Column<string>(type: "TEXT", nullable: true),
LastUpdated = table.Column<DateTime>(type: "TEXT", nullable: false),
DatePosted = table.Column<DateTime>(type: "TEXT", nullable: false),
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false),
DateModified = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Novels", x => x.Url);
table.ForeignKey(
name: "FK_Novels_Authors_AuthorUrl",
column: x => x.AuthorUrl,
principalTable: "Authors",
principalColumn: "Url");
});
migrationBuilder.CreateTable(
name: "Chapters",
columns: table => new
{
Url = table.Column<string>(type: "TEXT", nullable: false),
ChapterNumber = table.Column<int>(type: "INTEGER", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
Content = table.Column<string>(type: "TEXT", nullable: true),
RawContent = table.Column<string>(type: "TEXT", nullable: true),
DatePosted = table.Column<DateTime>(type: "TEXT", nullable: false),
DateUpdated = table.Column<DateTime>(type: "TEXT", nullable: false),
NovelUrl = table.Column<string>(type: "TEXT", nullable: true),
DateCreated = table.Column<DateTime>(type: "TEXT", nullable: false),
DateModified = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Chapters", x => x.Url);
table.ForeignKey(
name: "FK_Chapters_Novels_NovelUrl",
column: x => x.NovelUrl,
principalTable: "Novels",
principalColumn: "Url");
});
migrationBuilder.CreateTable(
name: "NovelTag",
columns: table => new
{
NovelsUrl = table.Column<string>(type: "TEXT", nullable: false),
TagsTagValue = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_NovelTag", x => new { x.NovelsUrl, x.TagsTagValue });
table.ForeignKey(
name: "FK_NovelTag_Novels_NovelsUrl",
column: x => x.NovelsUrl,
principalTable: "Novels",
principalColumn: "Url",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_NovelTag_Tags_TagsTagValue",
column: x => x.TagsTagValue,
principalTable: "Tags",
principalColumn: "TagValue",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "UserNovels",
columns: table => new
{
UserId = table.Column<int>(type: "INTEGER", nullable: false),
NovelUrl = table.Column<string>(type: "TEXT", nullable: false),
LastChapterRead = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserNovels", x => new { x.NovelUrl, x.UserId });
table.ForeignKey(
name: "FK_UserNovels_Novels_NovelUrl",
column: x => x.NovelUrl,
principalTable: "Novels",
principalColumn: "Url",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_UserNovels_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Chapters_NovelUrl",
table: "Chapters",
column: "NovelUrl");
migrationBuilder.CreateIndex(
name: "IX_Novels_AuthorUrl",
table: "Novels",
column: "AuthorUrl");
migrationBuilder.CreateIndex(
name: "IX_NovelTag_TagsTagValue",
table: "NovelTag",
column: "TagsTagValue");
migrationBuilder.CreateIndex(
name: "IX_UserNovels_UserId",
table: "UserNovels",
column: "UserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Chapters");
migrationBuilder.DropTable(
name: "NovelTag");
migrationBuilder.DropTable(
name: "UserNovels");
migrationBuilder.DropTable(
name: "Tags");
migrationBuilder.DropTable(
name: "Novels");
migrationBuilder.DropTable(
name: "Users");
migrationBuilder.DropTable(
name: "Authors");
}
}
}

View File

@@ -0,0 +1,248 @@
// <auto-generated />
using System;
using DBConnection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace DBConnection.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.7");
modelBuilder.Entity("DBConnection.Models.Author", b =>
{
b.Property<string>("Url")
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Url");
b.ToTable("Authors");
});
modelBuilder.Entity("DBConnection.Models.Chapter", b =>
{
b.Property<string>("Url")
.HasColumnType("TEXT");
b.Property<int>("ChapterNumber")
.HasColumnType("INTEGER");
b.Property<string>("Content")
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.Property<DateTime>("DatePosted")
.HasColumnType("TEXT");
b.Property<DateTime>("DateUpdated")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("NovelUrl")
.HasColumnType("TEXT");
b.Property<string>("RawContent")
.HasColumnType("TEXT");
b.HasKey("Url");
b.HasIndex("NovelUrl");
b.ToTable("Chapters");
});
modelBuilder.Entity("DBConnection.Models.Novel", b =>
{
b.Property<string>("Url")
.HasColumnType("TEXT");
b.Property<string>("AuthorUrl")
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.Property<DateTime>("DatePosted")
.HasColumnType("TEXT");
b.Property<DateTime>("LastUpdated")
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Url");
b.HasIndex("AuthorUrl");
b.ToTable("Novels");
});
modelBuilder.Entity("DBConnection.Models.Tag", b =>
{
b.Property<string>("TagValue")
.HasColumnType("TEXT");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.HasKey("TagValue");
b.ToTable("Tags");
});
modelBuilder.Entity("DBConnection.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("DateCreated")
.HasColumnType("TEXT");
b.Property<DateTime>("DateModified")
.HasColumnType("TEXT");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("DBConnection.Models.UserNovel", b =>
{
b.Property<string>("NovelUrl")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.Property<int>("LastChapterRead")
.HasColumnType("INTEGER");
b.HasKey("NovelUrl", "UserId");
b.HasIndex("UserId");
b.ToTable("UserNovels");
});
modelBuilder.Entity("NovelTag", b =>
{
b.Property<string>("NovelsUrl")
.HasColumnType("TEXT");
b.Property<string>("TagsTagValue")
.HasColumnType("TEXT");
b.HasKey("NovelsUrl", "TagsTagValue");
b.HasIndex("TagsTagValue");
b.ToTable("NovelTag");
});
modelBuilder.Entity("DBConnection.Models.Chapter", b =>
{
b.HasOne("DBConnection.Models.Novel", null)
.WithMany("Chapters")
.HasForeignKey("NovelUrl");
});
modelBuilder.Entity("DBConnection.Models.Novel", b =>
{
b.HasOne("DBConnection.Models.Author", "Author")
.WithMany("Novels")
.HasForeignKey("AuthorUrl");
b.Navigation("Author");
});
modelBuilder.Entity("DBConnection.Models.UserNovel", b =>
{
b.HasOne("DBConnection.Models.Novel", "Novel")
.WithMany()
.HasForeignKey("NovelUrl")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("DBConnection.Models.User", "User")
.WithMany("WatchedNovels")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Novel");
b.Navigation("User");
});
modelBuilder.Entity("NovelTag", b =>
{
b.HasOne("DBConnection.Models.Novel", null)
.WithMany()
.HasForeignKey("NovelsUrl")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("DBConnection.Models.Tag", null)
.WithMany()
.HasForeignKey("TagsTagValue")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("DBConnection.Models.Author", b =>
{
b.Navigation("Novels");
});
modelBuilder.Entity("DBConnection.Models.Novel", b =>
{
b.Navigation("Chapters");
});
modelBuilder.Entity("DBConnection.Models.User", b =>
{
b.Navigation("WatchedNovels");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
namespace DBConnection.Models;
@@ -7,5 +8,6 @@ public class Author : BaseEntity
[Key]
public string Url { get; set; }
public string Name { get; set; }
[JsonIgnore]
public List<Novel> Novels { get; set; }
}

View File

@@ -4,11 +4,11 @@ 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; }
[Key]
public string Url { get; set; }
public DateTime DatePosted { get; set; }
public DateTime DateUpdated { get; set; }

View File

@@ -0,0 +1,17 @@
using DBConnection.Models;
using DBConnection.Repositories.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace DBConnection.Repositories;
public class AuthorRepository : BaseRepository<Author>, IAuthorRepository
{
public AuthorRepository(AppDbContext dbContext) : base(dbContext)
{
}
protected override IQueryable<Author> GetAllIncludedQueryable()
{
return DbContext.Authors.Include(i => i.Novels);
}
}

View File

@@ -13,7 +13,8 @@ public abstract class BaseRepository<TEntityType> : IRepository<TEntityType> whe
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();
var ret = keyProperties?.Select(p => entity.GetType().GetProperty(p)?.GetValue(entity, null)).ToArray();
return ret;
}
protected abstract IQueryable<TEntityType> GetAllIncludedQueryable();
@@ -48,7 +49,7 @@ public abstract class BaseRepository<TEntityType> : IRepository<TEntityType> whe
public virtual async Task<TEntityType?> GetIncluded(TEntityType entity)
{
return await GetIncluded(dbEntity => GetPrimaryKey(dbEntity) == GetPrimaryKey(entity));
return await GetIncluded(dbEntity => GetPrimaryKey(dbEntity).SequenceEqual(GetPrimaryKey(entity)));
}
public virtual async Task<TEntityType?> GetIncluded(Func<TEntityType, bool> predicate)

View File

@@ -0,0 +1,8 @@
using DBConnection.Models;
namespace DBConnection.Repositories.Interfaces;
public interface IAuthorRepository : IRepository<Author>
{
}

View File

@@ -0,0 +1,8 @@
using DBConnection.Models;
namespace DBConnection.Repositories.Interfaces;
public interface ITagRepository : IRepository<Tag>
{
}

View File

@@ -6,9 +6,32 @@ namespace DBConnection.Repositories;
public class NovelRepository : BaseRepository<Novel>, INovelRepository
{
public NovelRepository(AppDbContext dbContext) : base(dbContext)
private readonly IAuthorRepository _authorRepository;
private readonly ITagRepository _tagRepository;
public NovelRepository(AppDbContext dbContext, IAuthorRepository authorRepository, ITagRepository tagRepository) : base(dbContext)
{
_authorRepository = authorRepository;
_tagRepository = tagRepository;
}
public override async Task<Novel> Upsert(Novel entity)
{
var dbEntity = await GetIncluded(entity) ?? entity;
dbEntity.Author = await _authorRepository.GetIncluded(entity.Author) ?? entity.Author;
List<Tag> newTags = new List<Tag>();
foreach (var tag in dbEntity.Tags)
{
newTags.Add(await _tagRepository.GetIncluded(tag) ?? tag);
}
dbEntity.Tags.Clear();
dbEntity.Tags = newTags;
if (DbContext.Entry(dbEntity).State == EntityState.Detached)
{
DbContext.Add(dbEntity);
}
await DbContext.SaveChangesAsync();
return dbEntity;
}
protected override IQueryable<Novel> GetAllIncludedQueryable()

View File

@@ -0,0 +1,17 @@
using DBConnection.Models;
using DBConnection.Repositories.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace DBConnection.Repositories;
public class TagRepository : BaseRepository<Tag>, ITagRepository
{
public TagRepository(AppDbContext dbContext) : base(dbContext)
{
}
protected override IQueryable<Tag> GetAllIncludedQueryable()
{
return DbContext.Tags.Include(i => i.Novels);
}
}