ajout EF Core

This commit is contained in:
Julien LEICHER 2021-12-15 10:22:55 +01:00
parent a9ad4a39c1
commit b799d3a2e9
No known key found for this signature in database
GPG Key ID: BE0761B6A007EB96
15 changed files with 457 additions and 27 deletions

2
.gitignore vendored
View File

@ -452,3 +452,5 @@ $RECYCLE.BIN/
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
hackernet.db*

View File

@ -2,7 +2,7 @@ using HackerNet.Infrastructure.AspNet;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHackerNetServices();
builder.Services.AddHackerNetServicesEntityFramework();
builder.Services.AddControllers();
builder.Services.AddOpenApiDocument(d =>
{
@ -11,6 +11,8 @@ builder.Services.AddOpenApiDocument(d =>
var app = builder.Build();
app.MigrateDatabase();
app.UseOpenApi();
app.UseSwaggerUi3();

View File

@ -5,7 +5,8 @@ var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
//ServiceCollectionExtensions.AddHackerNetServices(builder.Services);
builder.Services
.AddHackerNetServices()
//.AddHackerNetServicesMemory()
.AddHackerNetServicesEntityFramework()
.AddControllersWithViews(o =>
{
//o.Filters.Add<CustomExceptionFilter>();
@ -13,6 +14,8 @@ builder.Services
var app = builder.Build();
app.MigrateDatabase();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{

View File

@ -0,0 +1,49 @@
using HackerNet.Application;
using HackerNet.Domain;
using HackerNet.Infrastructure.Repositories.EntityFramework;
using HackerNet.Infrastructure.Repositories.Memory;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace HackerNet.Infrastructure.AspNet;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddHackerNetServicesMemory(this IServiceCollection services)
{
var link = new Link("https://localhost:7050/", "Youhouuu");
var comment = new Comment(link.Id, "Wow!");
var linksRepository = new MemoryLinkRepository(link);
var commentsRepository = new MemoryCommentRepository(comment);
services.AddSingleton<ILinkRepository>(linksRepository);
services.AddSingleton<ICommentRepository>(commentsRepository);
services.AddSingleton<IReadStore>(new MemoryReadStore(linksRepository, commentsRepository));
services.AddSingleton<LinkService>();
return services;
}
public static IServiceCollection AddHackerNetServicesEntityFramework(this IServiceCollection services)
{
services.AddDbContext<HackerContext>(options
=> options.UseSqlite("Data Source=hackernet.db"));
services.AddScoped<ILinkRepository, EFLinkRepository>();
services.AddScoped<ICommentRepository, EFCommentRepository>();
services.AddScoped<IReadStore, EFReadStore>();
services.AddScoped<LinkService>();
return services;
}
public static void MigrateDatabase(this IApplicationBuilder app)
{
using var scope = app.ApplicationServices.CreateScope();
using var ctx = scope.ServiceProvider.GetRequiredService<HackerContext>();
ctx.Database.Migrate();
}
}

View File

@ -1,25 +0,0 @@
using HackerNet.Application;
using HackerNet.Domain;
using HackerNet.Infrastructure.Repositories.Memory;
using Microsoft.Extensions.DependencyInjection;
namespace HackerNet.Infrastructure.AspNet;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddHackerNetServices(this IServiceCollection services)
{
var link = new Link("https://localhost:7050/", "Youhouuu");
var comment = new Comment(link.Id, "Wow!");
var linksRepository = new MemoryLinkRepository(link);
var commentsRepository = new MemoryCommentRepository(comment);
services.AddSingleton<ILinkRepository>(linksRepository);
services.AddSingleton<ICommentRepository>(commentsRepository);
services.AddSingleton<IReadStore>(new MemoryReadStore(linksRepository, commentsRepository));
services.AddSingleton<LinkService>();
return services;
}
}

View File

@ -5,6 +5,14 @@
<ProjectReference Include="..\HackerNet.Application\HackerNet.Application.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>

View File

@ -0,0 +1,79 @@
// <auto-generated />
using System;
using HackerNet.Infrastructure.Repositories.EntityFramework;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace HackerNet.Infrastructure.Migrations
{
[DbContext(typeof(HackerContext))]
[Migration("20211215082223_CreateLinkAndComment")]
partial class CreateLinkAndComment
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
modelBuilder.Entity("HackerNet.Domain.Comment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<Guid>("LinkId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LinkId");
b.ToTable("Comments");
});
modelBuilder.Entity("HackerNet.Domain.Link", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Url")
.IsRequired()
.HasMaxLength(250)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Links");
});
modelBuilder.Entity("HackerNet.Domain.Comment", b =>
{
b.HasOne("HackerNet.Domain.Link", null)
.WithMany()
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,61 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace HackerNet.Infrastructure.Migrations
{
public partial class CreateLinkAndComment : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Links",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
Url = table.Column<string>(type: "TEXT", maxLength: 250, nullable: false),
Description = table.Column<string>(type: "TEXT", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Links", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Comments",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
LinkId = table.Column<Guid>(type: "TEXT", nullable: false),
Content = table.Column<string>(type: "TEXT", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Comments", x => x.Id);
table.ForeignKey(
name: "FK_Comments_Links_LinkId",
column: x => x.LinkId,
principalTable: "Links",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Comments_LinkId",
table: "Comments",
column: "LinkId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Comments");
migrationBuilder.DropTable(
name: "Links");
}
}
}

View File

@ -0,0 +1,77 @@
// <auto-generated />
using System;
using HackerNet.Infrastructure.Repositories.EntityFramework;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace HackerNet.Infrastructure.Migrations
{
[DbContext(typeof(HackerContext))]
partial class HackerContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
modelBuilder.Entity("HackerNet.Domain.Comment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<Guid>("LinkId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LinkId");
b.ToTable("Comments", (string)null);
});
modelBuilder.Entity("HackerNet.Domain.Link", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Url")
.IsRequired()
.HasMaxLength(250)
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Links", (string)null);
});
modelBuilder.Entity("HackerNet.Domain.Comment", b =>
{
b.HasOne("HackerNet.Domain.Link", null)
.WithMany()
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,19 @@
using HackerNet.Domain;
namespace HackerNet.Infrastructure.Repositories.EntityFramework;
public class EFCommentRepository : ICommentRepository
{
private readonly HackerContext _context;
public EFCommentRepository(HackerContext context)
{
_context = context;
}
public void Add(Comment comment)
{
_context.Comments.Add(comment);
_context.SaveChanges();
}
}

View File

@ -0,0 +1,19 @@
using HackerNet.Domain;
namespace HackerNet.Infrastructure.Repositories.EntityFramework;
public class EFLinkRepository : ILinkRepository
{
private readonly HackerContext _context;
public EFLinkRepository(HackerContext context)
{
_context = context;
}
public void Add(Link link)
{
_context.Links.Add(link);
_context.SaveChanges();
}
}

View File

@ -0,0 +1,42 @@
using HackerNet.Application;
namespace HackerNet.Infrastructure.Repositories.EntityFramework;
public class EFReadStore : IReadStore
{
private readonly HackerContext _context;
public EFReadStore(HackerContext context)
{
_context = context;
}
public LinkComment[] GetLinkComments(Guid linkId)
=> _context.Comments
.OrderByDescending(c => c.CreatedAt)
.Select(c => new LinkComment
{
Content = c.Content
}).ToArray();
public LinkHomePage GetLinkDetail(Guid id)
=> GetLinks(id).Single();
public LinkHomePage[] GetPublishedLinks()
=> GetLinks().ToArray();
private IQueryable<LinkHomePage> GetLinks(Guid? id = null)
{
return _context.Links
.Where(l => !id.HasValue || l.Id == id)
.OrderByDescending(l => l.CreatedAt)
.Select(l => new LinkHomePage
{
Id = l.Id,
Url = l.Url,
Description = l.Description,
CommentsCount = _context.Comments
.Count(c => c.LinkId == l.Id),
});
}
}

View File

@ -0,0 +1,20 @@
using HackerNet.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace HackerNet.Infrastructure.Repositories.EntityFramework.EntityTypeConfigurations;
public class CommentEntityTypeConfiguration : IEntityTypeConfiguration<Comment>
{
public void Configure(EntityTypeBuilder<Comment> builder)
{
builder.HasKey(o => o.Id);
builder.HasOne<Link>()
.WithMany()
.HasForeignKey(o => o.LinkId)
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
builder.Property(o => o.Content).IsRequired();
builder.Property(o => o.CreatedAt).IsRequired();
}
}

View File

@ -0,0 +1,16 @@
using HackerNet.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace HackerNet.Infrastructure.Repositories.EntityFramework.EntityTypeConfigurations;
public class LinkEntityTypeConfiguration : IEntityTypeConfiguration<Link>
{
public void Configure(EntityTypeBuilder<Link> builder)
{
builder.HasKey(o => o.Id);
builder.Property(o => o.Url).IsRequired().HasMaxLength(250);
builder.Property(o => o.Description).IsRequired();
builder.Property(o => o.CreatedAt).IsRequired();
}
}

View File

@ -0,0 +1,58 @@
using HackerNet.Application;
using HackerNet.Domain;
using HackerNet.Infrastructure.Repositories.EntityFramework.EntityTypeConfigurations;
using Microsoft.EntityFrameworkCore;
namespace HackerNet.Infrastructure.Repositories.EntityFramework;
public class HackerContext : DbContext
{
#pragma warning disable CS8618
public DbSet<Link> Links { get; set; }
public DbSet<Comment> Comments { get; set; }
#pragma warning restore CS8618
public HackerContext() : base()
{
}
public HackerContext(DbContextOptions<HackerContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlite("Data Source=:memory:");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// var link = modelBuilder.Entity<Link>();
// link.HasKey(o => o.Id);
// link.Property(o => o.Url).IsRequired().HasMaxLength(250);
// link.Property(o => o.Description).IsRequired();
// link.Property(o => o.CreatedAt).IsRequired();
// var comment = modelBuilder.Entity<Comment>();
// comment.HasKey(o => o.Id);
// comment.HasOne<Link>()
// .WithMany()
// .HasForeignKey(o => o.LinkId)
// .IsRequired()
// .OnDelete(DeleteBehavior.Cascade);
// comment.Property(o => o.Content).IsRequired();
// comment.Property(o => o.CreatedAt).IsRequired();
//modelBuilder.ApplyConfiguration(new LinkEntityTypeConfiguration());
//modelBuilder.ApplyConfiguration(new CommentEntityTypeConfiguration());
modelBuilder.ApplyConfigurationsFromAssembly(typeof(HackerContext).Assembly);
}
}