Commit 6ed7a977 authored by Scott DePouw's avatar Scott DePouw

Sample code for Repository Update Experiment.

parent 36d11177
...@@ -3,6 +3,17 @@ using CleanArchitecture.Core.SharedKernel; ...@@ -3,6 +3,17 @@ using CleanArchitecture.Core.SharedKernel;
namespace CleanArchitecture.Core.Entities namespace CleanArchitecture.Core.Entities
{ {
public class Bar : BaseEntity
{
public int Number { get; set; }
}
public class Foo : BaseEntity
{
public Bar Bar { get; set; }
public string Name { get; set; }
}
public class ToDoItem : BaseEntity public class ToDoItem : BaseEntity
{ {
public string Title { get; set; } public string Title { get; set; }
......
...@@ -8,7 +8,8 @@ namespace CleanArchitecture.Core.Interfaces ...@@ -8,7 +8,8 @@ namespace CleanArchitecture.Core.Interfaces
T GetById<T>(int id) where T : BaseEntity; T GetById<T>(int id) where T : BaseEntity;
List<T> List<T>() where T : BaseEntity; List<T> List<T>() where T : BaseEntity;
T Add<T>(T entity) where T : BaseEntity; T Add<T>(T entity) where T : BaseEntity;
void Update<T>(T entity) where T : BaseEntity; void UpdateUsingOriginalMethod<T>(T entity) where T : BaseEntity;
void UpdateUsingDbContextUpdate<T>(T entity) where T : BaseEntity;
void Delete<T>(T entity) where T : BaseEntity; void Delete<T>(T entity) where T : BaseEntity;
} }
} }
using CleanArchitecture.Core.Interfaces; using CleanArchitecture.Core.Entities;
using CleanArchitecture.Core.Interfaces;
using CleanArchitecture.Core.SharedKernel;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Linq; using System.Linq;
using CleanArchitecture.Core.Entities;
using CleanArchitecture.Core.SharedKernel;
namespace CleanArchitecture.Infrastructure.Data namespace CleanArchitecture.Infrastructure.Data
{ {
...@@ -17,6 +17,8 @@ namespace CleanArchitecture.Infrastructure.Data ...@@ -17,6 +17,8 @@ namespace CleanArchitecture.Infrastructure.Data
} }
public DbSet<ToDoItem> ToDoItems { get; set; } public DbSet<ToDoItem> ToDoItems { get; set; }
public DbSet<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
public override int SaveChanges() public override int SaveChanges()
{ {
......
...@@ -17,7 +17,7 @@ namespace CleanArchitecture.Infrastructure.Data ...@@ -17,7 +17,7 @@ namespace CleanArchitecture.Infrastructure.Data
public T GetById<T>(int id) where T : BaseEntity public T GetById<T>(int id) where T : BaseEntity
{ {
return _dbContext.Set<T>().SingleOrDefault(e => e.Id == id); return _dbContext.Set<T>().Include("Bar").SingleOrDefault(e => e.Id == id);
} }
public List<T> List<T>() where T : BaseEntity public List<T> List<T>() where T : BaseEntity
...@@ -39,10 +39,16 @@ namespace CleanArchitecture.Infrastructure.Data ...@@ -39,10 +39,16 @@ namespace CleanArchitecture.Infrastructure.Data
_dbContext.SaveChanges(); _dbContext.SaveChanges();
} }
public void Update<T>(T entity) where T : BaseEntity public void UpdateUsingOriginalMethod<T>(T entity) where T : BaseEntity
{ {
_dbContext.Entry(entity).State = EntityState.Modified; _dbContext.Entry(entity).State = EntityState.Modified;
_dbContext.SaveChanges(); _dbContext.SaveChanges();
} }
public void UpdateUsingDbContextUpdate<T>(T entity) where T : BaseEntity
{
_dbContext.Update(entity);
_dbContext.SaveChanges();
}
} }
} }
// <auto-generated />
using System;
using CleanArchitecture.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace CleanArchitecture.Infrastructure.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20190516105250_Foo")]
partial class Foo
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.11-servicing-32099")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("CleanArchitecture.Core.Entities.Bar", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("Number");
b.HasKey("Id");
b.ToTable("Bars");
});
modelBuilder.Entity("CleanArchitecture.Core.Entities.Foo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int?>("BarId");
b.Property<string>("Name");
b.HasKey("Id");
b.HasIndex("BarId");
b.ToTable("Foos");
});
modelBuilder.Entity("CleanArchitecture.Core.Entities.ToDoItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Description");
b.Property<bool>("IsDone");
b.Property<string>("Title");
b.HasKey("Id");
b.ToTable("ToDoItems");
});
modelBuilder.Entity("CleanArchitecture.Core.Entities.Foo", b =>
{
b.HasOne("CleanArchitecture.Core.Entities.Bar", "Bar")
.WithMany()
.HasForeignKey("BarId");
});
#pragma warning restore 612, 618
}
}
}
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace CleanArchitecture.Infrastructure.Migrations
{
public partial class Foo : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Bars",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Number = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Bars", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ToDoItems",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Title = table.Column<string>(nullable: true),
Description = table.Column<string>(nullable: true),
IsDone = table.Column<bool>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ToDoItems", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Foos",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
BarId = table.Column<int>(nullable: true),
Name = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Foos", x => x.Id);
table.ForeignKey(
name: "FK_Foos_Bars_BarId",
column: x => x.BarId,
principalTable: "Bars",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Foos_BarId",
table: "Foos",
column: "BarId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Foos");
migrationBuilder.DropTable(
name: "ToDoItems");
migrationBuilder.DropTable(
name: "Bars");
}
}
}
// <auto-generated />
using System;
using CleanArchitecture.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace CleanArchitecture.Infrastructure.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.11-servicing-32099")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("CleanArchitecture.Core.Entities.Bar", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("Number");
b.HasKey("Id");
b.ToTable("Bars");
});
modelBuilder.Entity("CleanArchitecture.Core.Entities.Foo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int?>("BarId");
b.Property<string>("Name");
b.HasKey("Id");
b.HasIndex("BarId");
b.ToTable("Foos");
});
modelBuilder.Entity("CleanArchitecture.Core.Entities.ToDoItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Description");
b.Property<bool>("IsDone");
b.Property<string>("Title");
b.HasKey("Id");
b.ToTable("ToDoItems");
});
modelBuilder.Entity("CleanArchitecture.Core.Entities.Foo", b =>
{
b.HasOne("CleanArchitecture.Core.Entities.Bar", "Bar")
.WithMany()
.HasForeignKey("BarId");
});
#pragma warning restore 612, 618
}
}
}
...@@ -51,7 +51,7 @@ namespace CleanArchitecture.Web.Api ...@@ -51,7 +51,7 @@ namespace CleanArchitecture.Web.Api
{ {
var toDoItem = _repository.GetById<ToDoItem>(id); var toDoItem = _repository.GetById<ToDoItem>(id);
toDoItem.MarkComplete(); toDoItem.MarkComplete();
_repository.Update(toDoItem); _repository.UpdateUsingOriginalMethod(toDoItem);
return Ok(ToDoItemDTO.FromToDoItem(toDoItem)); return Ok(ToDoItemDTO.FromToDoItem(toDoItem));
} }
......
using Microsoft.AspNetCore.Mvc; using CleanArchitecture.Core.Entities;
using CleanArchitecture.Core.Interfaces;
using Microsoft.AspNetCore.Mvc;
using System.Threading;
namespace CleanArchitecture.Web.Controllers namespace CleanArchitecture.Web.Controllers
{ {
public class HomeController : Controller public class HomeController : Controller
{ {
private readonly IRepository _repository;
public HomeController(IRepository repository)
{
_repository = repository;
}
public IActionResult Index() public IActionResult Index()
{ {
return View(); Foo foo = _repository.GetById<Foo>(1);
Foo originalFoo = new Foo
{
Name = foo.Name,
Bar = new Bar { Number = foo.Bar.Number }
};
if (foo == null)
{
throw new AbandonedMutexException("Gotta make a foo first!");
} }
public IActionResult Error() if (foo.Name == "Scott")
{ {
return View(); return View(foo);
} }
foo.Name = "Scott";
foo.Bar.Number++;
// Choose which Update method to call here.
_repository.UpdateUsingOriginalMethod(foo); // Original code, setting entity's state to EntityState.Modified.
// _repository.UpdateUsingDbContextUpdate(foo); // New code, calling dbContext.Update(entity).
return View(originalFoo);
}
public IActionResult Error() => View();
} }
} }
...@@ -16,8 +16,7 @@ namespace CleanArchitecture.Web.Controllers ...@@ -16,8 +16,7 @@ namespace CleanArchitecture.Web.Controllers
public IActionResult Index() public IActionResult Index()
{ {
var items = _repository.List<ToDoItem>(); return View(_repository.GetById<Foo>(1));
return View(items);
} }
public IActionResult Populate() public IActionResult Populate()
......
using Microsoft.AspNetCore; using CleanArchitecture.Infrastructure.Data;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace CleanArchitecture.Web namespace CleanArchitecture.Web
{ {
...@@ -7,7 +10,23 @@ namespace CleanArchitecture.Web ...@@ -7,7 +10,23 @@ namespace CleanArchitecture.Web
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
CreateWebHostBuilder(args).Build().Run(); IWebHost host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var dbContext = services.GetRequiredService<AppDbContext>();
SeedData.PopulateTestData(dbContext);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex}");
}
}
host.Run();
} }
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
......
using CleanArchitecture.Core.Entities; using CleanArchitecture.Core.Entities;
using CleanArchitecture.Infrastructure.Data; using CleanArchitecture.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace CleanArchitecture.Web namespace CleanArchitecture.Web
{ {
...@@ -7,6 +9,14 @@ namespace CleanArchitecture.Web ...@@ -7,6 +9,14 @@ namespace CleanArchitecture.Web
{ {
public static void PopulateTestData(AppDbContext dbContext) public static void PopulateTestData(AppDbContext dbContext)
{ {
if (dbContext.ToDoItems.Any())
{
Foo single = dbContext.Foos.Include("Bar").Single();
single.Name = "Steve";
single.Bar.Number = 5;
dbContext.SaveChanges();
return;
}
var toDos = dbContext.ToDoItems; var toDos = dbContext.ToDoItems;
foreach (var item in toDos) foreach (var item in toDos)
{ {
...@@ -23,6 +33,8 @@ namespace CleanArchitecture.Web ...@@ -23,6 +33,8 @@ namespace CleanArchitecture.Web
Title = "Test Item 2", Title = "Test Item 2",
Description = "Test Description Two" Description = "Test Description Two"
}); });
dbContext.Foos.Add(new Foo { Name = "Steve", Bar = new Bar { Number = 5 } });
dbContext.SaveChanges(); dbContext.SaveChanges();
} }
......
@{ @model CleanArchitecture.Core.Entities.Foo
@{
ViewData["Title"] = "Clean DDD Architecture Sample"; ViewData["Title"] = "Clean DDD Architecture Sample";
} }
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<h2>To Do Items (MVC)</h2> <h2>Update Experiment</h2>
<ul> <ul>
<li><a asp-area="" asp-controller="ToDo" asp-action="Populate">Load Sample To Do Items</a> (API call returning just a number)</li> @if (Model.Name == "Scott")
<li><a asp-area="" asp-controller="ToDo" asp-action="Index">List To Do Items</a></li> {
<li><a href="/swagger">API Documentation (Swagger)</a></li> <li>Experiment already run. To see again, restart application.</li>
</ul> }
<h2>To Do Items (Razor Pages)</h2> else
<ul> {
<li><a asp-page="/ToDoRazorPage/Populate">Load Sample To Do Items</a></li> <li>Model with nested entity original state: (Foo.Name - @Model.Name | Foo.Bar.Number: @Model.Bar.Number)</li>
<li><a asp-page="/ToDoRazorPage/Index">List To Do Items</a></li> <li><a asp-area="" asp-controller="ToDo" asp-action="Index">Click here to see if update worked</a></li>
}
</ul> </ul>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
......
@{ @{
ViewData["Title"] = "ToDo List"; ViewData["Title"] = "ToDo List";
} }
@model IEnumerable<CleanArchitecture.Core.Entities.ToDoItem> @model CleanArchitecture.Core.Entities.Foo
<h2>To Do Items (MVC View)</h2> <h2>Update Experiment</h2>
<ul> <ul>
@foreach (var item in Model) <li>Updated Entity State. If update worked, then Name should go from Steve to Scott, and if nested update worked, then Bar.Number should have increased from 5 to 6.</li>
{ <li>(Foo.Name - @Model.Name | Foo.Bar.Number: @Model.Bar.Number)</li>
<li>@item.Title<br/>@item.Description</li>
}
</ul> </ul>
...@@ -30,7 +30,7 @@ namespace CleanArchitecture.Tests.Integration.Data ...@@ -30,7 +30,7 @@ namespace CleanArchitecture.Tests.Integration.Data
newItem.Title = newTitle; newItem.Title = newTitle;
// Update the item // Update the item
repository.Update(newItem); repository.UpdateUsingOriginalMethod(newItem);
var updatedItem = repository.List<ToDoItem>() var updatedItem = repository.List<ToDoItem>()
.FirstOrDefault(i => i.Title == newTitle); .FirstOrDefault(i => i.Title == newTitle);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment