From 2c9d5e978303d511dfe9b318601e9f6fae20d207 Mon Sep 17 00:00:00 2001 From: Alexis DRAI Date: Sun, 12 Feb 2023 15:11:48 +0100 Subject: [PATCH] :bug: :fire: :recycle: :skull: :coffin: Implement One-to-Many and set-up SQLite --- Tests/Controllers/CatsControllerTest.cs | 18 ++-- cat_cafe/Controllers/BarsController.cs | 100 +++++++----------- cat_cafe/Controllers/CatsController.cs | 47 +++----- cat_cafe/Controllers/CustomersController.cs | 65 ++++-------- cat_cafe/Dto/BarDto.cs | 11 +- cat_cafe/Dto/CatDto.cs | 4 +- cat_cafe/Dto/CustomerDto.cs | 7 +- cat_cafe/Entities/Bar.cs | 35 +++--- cat_cafe/Entities/Cat.cs | 3 +- cat_cafe/Entities/Customer.cs | 5 +- cat_cafe/Mappers/BarMapper.cs | 23 ++-- cat_cafe/Mappers/CustomerMapper.cs | 15 ++- cat_cafe/Program.cs | 25 +++-- .../{CatContext.cs => CatCafeContext.cs} | 7 ++ cat_cafe/cat_cafe.csproj | 10 +- 15 files changed, 157 insertions(+), 218 deletions(-) rename cat_cafe/Repositories/{CatContext.cs => CatCafeContext.cs} (63%) diff --git a/Tests/Controllers/CatsControllerTest.cs b/Tests/Controllers/CatsControllerTest.cs index c42f046..0944b4a 100644 --- a/Tests/Controllers/CatsControllerTest.cs +++ b/Tests/Controllers/CatsControllerTest.cs @@ -3,12 +3,12 @@ using cat_cafe.Dto; using cat_cafe.Entities; using cat_cafe.Mappers; using cat_cafe.Repositories; +using cat_cafe.WeSo; +using FluentAssertions; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using FluentAssertions; -using cat_cafe.WeSo; namespace cat_cafe.Controllers.Tests { @@ -101,7 +101,7 @@ namespace cat_cafe.Controllers.Tests actualResult!.Value.Should().BeEquivalentTo(aliceDto); } - /* + [TestMethod()] public async Task PutCatTest() { @@ -111,12 +111,12 @@ namespace cat_cafe.Controllers.Tests // Act var responseType = await controller.PutCat(bob.Id, robert); - // System.InvalidOperationException: - // The instance of entity type 'Cat' cannot be tracked because another instance - // with the same key value for {'Id'} is already being tracked. + // System.InvalidOperationException: + // The instance of entity type 'Cat' cannot be tracked because another instance + // with the same key value for {'Id'} is already being tracked. - // ... this simple update should work out of the box, - // DbContext is already 'scoped' by default instead of singleton + // ... this simple update should work out of the box, + // DbContext is already 'scoped' by default instead of singleton // Assert responseType.Should().BeOfType(); @@ -125,7 +125,7 @@ namespace cat_cafe.Controllers.Tests var actualResult = actual.Result as OkObjectResult; actualResult!.Value.Should().BeEquivalentTo(robert); } - */ + [TestMethod()] public async Task PostCatTest() diff --git a/cat_cafe/Controllers/BarsController.cs b/cat_cafe/Controllers/BarsController.cs index 263b8fb..22dc28f 100644 --- a/cat_cafe/Controllers/BarsController.cs +++ b/cat_cafe/Controllers/BarsController.cs @@ -1,29 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; +using AutoMapper; +using cat_cafe.Dto; using cat_cafe.Entities; using cat_cafe.Repositories; -using AutoMapper; -using cat_cafe.Dto; -using System.Collections; -using System.Xml.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; namespace cat_cafe.Controllers { [Route("api/v{version:apiVersion}/[controller]")] [ApiController] - [ApiVersion("2.0")] + [ApiVersion("1.0")] public class BarsController : ControllerBase { private readonly CatCafeContext _context; private readonly IMapper _mapper; - private readonly ILogger _logger; + private readonly ILogger _logger; - public BarsController(CatCafeContext context,IMapper mapper, ILogger logger) + public BarsController(CatCafeContext context, IMapper mapper, ILogger logger) { _context = context; _mapper = mapper; @@ -32,97 +25,83 @@ namespace cat_cafe.Controllers // GET: api/v1/Bars [HttpGet] - [MapToApiVersion("1.0")] public async Task>> GetBars() { - var bars = _context.Bars - .Include(a => a.cats) - .Select(a => new Bar - { - Id = a.Id, - Name = a.Name, - cats = a.cats.Select(p => new Cat { Name = p.Name, Age = p.Age, Id= p.Id}).ToList() - }) - .ToList(); - return _mapper.Map>(bars); + var bars = await _context.Bars + .Include(b => b.Cats) + .ToListAsync(); + + return Ok(_mapper.Map>(bars)); } // GET: api/v1/Bars/5 [HttpGet("{id}")] - [MapToApiVersion("1.0")] public async Task> GetBar(long id) { - var bar = _context.Bars.Include(p => p.cats) - .Select(a => new Bar - { - Id = a.Id, - Name = a.Name, - cats = a.cats.Select(p => new Cat { Name = p.Name, Age = p.Age, Id = p.Id }).ToList() - - }).FirstOrDefaultAsync(p => p.Id == id); + var bar = await _context.Bars + .Include(b => b.Cats) + .SingleOrDefaultAsync(b => b.Id == id); if (bar == null) { return NotFound(); } - return _mapper.Map(bar.Result); + return Ok(_mapper.Map(bar)); } // PUT: api/v1/Bars/5 // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754 [HttpPut("{id}")] - [MapToApiVersion("1.0")] public async Task PutBar(long id, BarDto barDto) { if (id != barDto.Id) { return BadRequest(); } - Bar bar = _mapper.Map(barDto); - _context.Entry(bar).State = EntityState.Modified; - try - { - await _context.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException) + var bar = await _context.Bars + .Include(b => b.Cats) + .SingleOrDefaultAsync(b => b.Id == id); + + if (bar == null) { - if (!BarExists(id)) - { - return NotFound(); - } - else - { - throw; - } + return NotFound(); } + _mapper.Map(barDto, bar); + bar.Cats = await _context.Cats + .Where(c => barDto.CatIds.Contains(c.Id)) + .ToListAsync(); + + await _context.SaveChangesAsync(); return NoContent(); } // POST: api/v1/Bars // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754 [HttpPost] - [MapToApiVersion("1.0")] - public async Task> PostBar(BarDto barDto) + public async Task> CreateBar(BarDto barDto) { - // Bar bar = _mapper.Map(barDto); var bar = _mapper.Map(barDto); - + bar.Cats = await _context.Cats + .Where(c => barDto.CatIds.Contains(c.Id)) + .ToListAsync(); _context.Bars.Add(bar); await _context.SaveChangesAsync(); - return CreatedAtAction("GetBar", new { id = barDto.Id }, _mapper.Map(bar)); + return CreatedAtAction(nameof(GetBar), new { id = bar.Id }, _mapper.Map(bar)); } // DELETE: api/v1/Bars/5 [HttpDelete("{id}")] - [MapToApiVersion("1.0")] public async Task DeleteBar(long id) { - var bar = await _context.Bars.FindAsync(id); + var bar = await _context.Bars + .Include(b => b.Cats) + .SingleOrDefaultAsync(b => b.Id == id); + if (bar == null) { return NotFound(); @@ -133,10 +112,5 @@ namespace cat_cafe.Controllers return NoContent(); } - - private bool BarExists(long id) - { - return _context.Bars.Any(e => e.Id == id); - } } } diff --git a/cat_cafe/Controllers/CatsController.cs b/cat_cafe/Controllers/CatsController.cs index 178270b..734dbc5 100644 --- a/cat_cafe/Controllers/CatsController.cs +++ b/cat_cafe/Controllers/CatsController.cs @@ -1,18 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; +using AutoMapper; +using cat_cafe.Dto; using cat_cafe.Entities; using cat_cafe.Repositories; -using AutoMapper; -using cat_cafe.Dto; -using Serilog; -using Newtonsoft.Json; -using Microsoft.Extensions.Logging.Abstractions; using cat_cafe.WeSo; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; namespace cat_cafe.Controllers { @@ -28,8 +20,8 @@ namespace cat_cafe.Controllers private readonly WebSocketHandler _webSocketHandler; public CatsController( - CatCafeContext context, - IMapper mapper, + CatCafeContext context, + IMapper mapper, ILogger logger, WebSocketHandler webSocketHandler ) @@ -65,7 +57,6 @@ namespace cat_cafe.Controllers // GET: api/v1/Cats/5 [HttpGet("{id}")] - [MapToApiVersion("1.0")] public async Task> GetCat(long id) { var cat = await _context.Cats.FindAsync(id); @@ -81,7 +72,6 @@ namespace cat_cafe.Controllers // PUT: api/v1/Cats/5 // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754 [HttpPut("{id}")] - [MapToApiVersion("1.0")] public async Task PutCat(long id, CatDto catDto) { if (id != catDto.Id) @@ -89,32 +79,24 @@ namespace cat_cafe.Controllers return BadRequest(); } - Cat cat = _mapper.Map(catDto); - _context.Entry(cat).State = EntityState.Modified; + var cat = await _context.Cats + .SingleOrDefaultAsync(c => c.Id == id); - try - { - await _context.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException) + if (cat == null) { - if (!CatExists(id)) - { - return NotFound(); - } - else - { - throw; - } + return NotFound(); } + _mapper.Map(catDto, cat); + + await _context.SaveChangesAsync(); + return NoContent(); } // POST: api/v1/Cats // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754 [HttpPost] - [MapToApiVersion("1.0")] public async Task> PostCat(CatDto catDto) { Cat cat = _mapper.Map(catDto); @@ -128,7 +110,6 @@ namespace cat_cafe.Controllers // DELETE: api/v1/Cats/5 [HttpDelete("{id}")] - [MapToApiVersion("1.0")] public async Task DeleteCat(long id) { var cat = await _context.Cats.FindAsync(id); diff --git a/cat_cafe/Controllers/CustomersController.cs b/cat_cafe/Controllers/CustomersController.cs index d695dd6..89ff88e 100644 --- a/cat_cafe/Controllers/CustomersController.cs +++ b/cat_cafe/Controllers/CustomersController.cs @@ -1,29 +1,24 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; +using AutoMapper; +using cat_cafe.Dto; using cat_cafe.Entities; using cat_cafe.Repositories; -using cat_cafe.Dto; -using AutoMapper; -using Serilog; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; +using Serilog; namespace cat_cafe.Controllers { [Route("api/v{version:apiVersion}/[controller]")] [ApiController] - [ApiVersion("2.0")] + [ApiVersion("1.0")] public class CustomersController : ControllerBase { private readonly CatCafeContext _context; private readonly IMapper _mapper; private readonly ILogger _logger; - public CustomersController(CatCafeContext context,IMapper mapper,ILogger logger) + public CustomersController(CatCafeContext context, IMapper mapper, ILogger logger) { _context = context; _mapper = mapper; @@ -32,7 +27,6 @@ namespace cat_cafe.Controllers // GET: api/v1/Customers [HttpGet] - [MapToApiVersion("1.0")] public async Task>> GetCustomers() { Log.Information(this.Request.Method + " => get All customers"); @@ -48,10 +42,9 @@ namespace cat_cafe.Controllers // GET: api/v1/Customers/5 [HttpGet("{id}")] - [MapToApiVersion("1.0")] public async Task> GetCustomer(long id) { - Log.Information(this.Request.Method + " => get by ID {@id}",id); + Log.Information(this.Request.Method + " => get by ID {@id}", id); var customer = await _context.Customers.FindAsync(id); if (customer == null) @@ -70,49 +63,34 @@ namespace cat_cafe.Controllers // PUT: api/v1/Customers/5 // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754 [HttpPut("{id}")] - [MapToApiVersion("1.0")] public async Task PutCustomer(long id, CustomerDto customerDto) { Log.Information(this.Request.Method + " => put by ID {@id}", id); if (id != customerDto.Id) { - Log.Information(this.Request.Method + " => " + BadRequest().StatusCode.ToString()+" IDs not matching"); + Log.Information(this.Request.Method + " => " + BadRequest().StatusCode.ToString() + " IDs not matching"); return BadRequest(); } - Customer customer = _mapper.Map(customerDto); - - _context.Entry(customer).State = EntityState.Modified; + var customer = await _context.Customers + .SingleOrDefaultAsync(c => c.Id == id); - try - { - await _context.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException e) + if (customer == null) { - if (!CustomerExists(id)) - { - Log.Information(this.Request.Method + " => " + NotFound().StatusCode.ToString()); - return NotFound(); - } - else - { - Log.Error(this.Request.Method + " => " + e.Message); - throw; - } + return NotFound(); } - Log.Information(this.Request.Method + " => " - + this.Response.StatusCode.ToString() + " " - + customer.GetType().ToString() + " " - + JsonConvert.SerializeObject(customer).ToString()); - return Ok(); + + _mapper.Map(customerDto, customer); + + await _context.SaveChangesAsync(); + + return NoContent(); } // POST: api/v1/Customers // To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754 [HttpPost] - [MapToApiVersion("1.0")] - public async Task> PostCustomer(CustomerDto customerDto) + public async Task> PostCustomer(CustomerDto customerDto) { Log.Information(this.Request.Method + " => post customer"); @@ -125,12 +103,11 @@ namespace cat_cafe.Controllers + customer.GetType().ToString() + " " + JsonConvert.SerializeObject(customer).ToString()); - return CreatedAtAction("GetCustomer", new { id = customer.Id }, _mapper.Map( customer)); + return CreatedAtAction("GetCustomer", new { id = customer.Id }, _mapper.Map(customer)); } // DELETE: api/v1/Customers/5 [HttpDelete("{id}")] - [MapToApiVersion("1.0")] public async Task DeleteCustomer(long id) { Log.Information(this.Request.Method + " => delete by ID {@id}", id); diff --git a/cat_cafe/Dto/BarDto.cs b/cat_cafe/Dto/BarDto.cs index 85173dd..f08e1a0 100644 --- a/cat_cafe/Dto/BarDto.cs +++ b/cat_cafe/Dto/BarDto.cs @@ -1,13 +1,10 @@ -using System; -using cat_cafe.Entities; - -namespace cat_cafe.Dto +namespace cat_cafe.Dto { - public class BarDto - { + public class BarDto + { public long Id { get; set; } public string? Name { get; set; } - public List cats { get; set; } = new List(); + public List CatIds { get; set; } = new(); } } diff --git a/cat_cafe/Dto/CatDto.cs b/cat_cafe/Dto/CatDto.cs index 86cf631..e864bcf 100644 --- a/cat_cafe/Dto/CatDto.cs +++ b/cat_cafe/Dto/CatDto.cs @@ -1,9 +1,9 @@ -using System; -namespace cat_cafe.Dto +namespace cat_cafe.Dto { public class CatDto { public long Id { get; set; } public string? Name { get; set; } + public long? BarId { get; set; } } } diff --git a/cat_cafe/Dto/CustomerDto.cs b/cat_cafe/Dto/CustomerDto.cs index 181224b..7a029e7 100644 --- a/cat_cafe/Dto/CustomerDto.cs +++ b/cat_cafe/Dto/CustomerDto.cs @@ -1,10 +1,9 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace cat_cafe.Dto { - public class CustomerDto - { + public class CustomerDto + { public long Id { get; set; } [Required] public string FullName { get; set; } diff --git a/cat_cafe/Entities/Bar.cs b/cat_cafe/Entities/Bar.cs index 1345acd..501c748 100644 --- a/cat_cafe/Entities/Bar.cs +++ b/cat_cafe/Entities/Bar.cs @@ -1,24 +1,19 @@ -using System; -namespace cat_cafe.Entities +namespace cat_cafe.Entities { - public class Bar - { - - - public long Id { get; set; } - public string? Name { get; set; } - public List cats { get; set; } = new List(); + public class Bar + { + public long Id { get; set; } + public string? Name { get; set; } + public List Cats { get; set; } = new(); - - public void addCat(Cat c) - { - cats.Add(c); - } - public void removeCat(Cat c) - { - cats.Remove(c); - } - - } + public void AddCat(Cat c) + { + Cats.Add(c); + } + public void RemoveCat(Cat c) + { + Cats.Remove(c); + } + } } diff --git a/cat_cafe/Entities/Cat.cs b/cat_cafe/Entities/Cat.cs index d16b49d..6d6ac23 100644 --- a/cat_cafe/Entities/Cat.cs +++ b/cat_cafe/Entities/Cat.cs @@ -8,7 +8,8 @@ namespace cat_cafe.Entities public string? Name { get; set; } [Required] public int Age { get; set; } = 0; - + public long? BarId { get; set; } + public Bar? Bar { get; set; } public string Meow() { return "meow"; } } } diff --git a/cat_cafe/Entities/Customer.cs b/cat_cafe/Entities/Customer.cs index 3c264ab..b21580e 100644 --- a/cat_cafe/Entities/Customer.cs +++ b/cat_cafe/Entities/Customer.cs @@ -1,11 +1,10 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace cat_cafe.Entities { public class Customer { - + public long Id { get; set; } [Required] public string? FullName { get; set; } diff --git a/cat_cafe/Mappers/BarMapper.cs b/cat_cafe/Mappers/BarMapper.cs index cb89d24..86016f5 100644 --- a/cat_cafe/Mappers/BarMapper.cs +++ b/cat_cafe/Mappers/BarMapper.cs @@ -1,18 +1,17 @@ -using System; -using AutoMapper; +using AutoMapper; using cat_cafe.Dto; using cat_cafe.Entities; namespace cat_cafe.Mappers { - public class BarMapper:Profile - { - public BarMapper() - { - - // var mapper = config.CreateMapper(); - CreateMap().ReverseMap(); - } - } -} + public class BarMapper : Profile + { + public BarMapper() + { + CreateMap() + .ForMember(dest => dest.CatIds, opt => opt.MapFrom(src => src.Cats.Select(c => c.Id))); + CreateMap(); + } + } +} diff --git a/cat_cafe/Mappers/CustomerMapper.cs b/cat_cafe/Mappers/CustomerMapper.cs index d0f38ea..801863b 100644 --- a/cat_cafe/Mappers/CustomerMapper.cs +++ b/cat_cafe/Mappers/CustomerMapper.cs @@ -2,15 +2,14 @@ using cat_cafe.Dto; using cat_cafe.Entities; - namespace cat_cafe.Mappers { - public class CustomerMapper:Profile - { - public CustomerMapper() - { - CreateMap().ReverseMap(); - } - } + public class CustomerMapper : Profile + { + public CustomerMapper() + { + CreateMap().ReverseMap(); + } + } } diff --git a/cat_cafe/Program.cs b/cat_cafe/Program.cs index 927f514..bbb6902 100644 --- a/cat_cafe/Program.cs +++ b/cat_cafe/Program.cs @@ -1,8 +1,8 @@ -using Microsoft.EntityFrameworkCore; using cat_cafe.Repositories; +using cat_cafe.WeSo; +using Microsoft.EntityFrameworkCore; using Serilog; using System.Net.WebSockets; -using cat_cafe.WeSo; var builder = WebApplication.CreateBuilder(args); @@ -15,22 +15,32 @@ List _sockets = new(); builder.Services.AddSingleton>(x => _sockets); builder.Services.AddSingleton(); builder.Services.AddControllers(); -builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase("CatCafe")); +builder.Services.AddDbContext(opt => opt.UseSqlite("Data Source=cat_cafe.db")); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddAutoMapper(typeof(Program)); builder.Services.AddControllersWithViews(); -builder.Services.AddApiVersioning(o => { o.ReportApiVersions = true; }); +builder.Services.AddApiVersioning(opt => { opt.ReportApiVersions = true; }); builder.Services.AddVersionedApiExplorer( - options => + opt => { - options.GroupNameFormat = "'v'VVV"; - options.SubstituteApiVersionInUrl = true; + opt.GroupNameFormat = "'v'VVV"; + opt.SubstituteApiVersionInUrl = true; } ); var app = builder.Build(); +using (var serviceScope = app.Services.CreateScope()) +{ + var context = serviceScope.ServiceProvider.GetRequiredService(); + context.Database.EnsureCreated(); + if (context.Database.GetPendingMigrations().Any()) + { + context.Database.Migrate(); + } +} + app.UseHttpLogging(); // Configure the HTTP request pipeline. @@ -48,7 +58,6 @@ app.MapControllers(); app.UseWebSockets(); - app.Use(async (context, next) => { if (context.Request.Path == "/ws") diff --git a/cat_cafe/Repositories/CatContext.cs b/cat_cafe/Repositories/CatCafeContext.cs similarity index 63% rename from cat_cafe/Repositories/CatContext.cs rename to cat_cafe/Repositories/CatCafeContext.cs index 1ee329a..ea7dab0 100644 --- a/cat_cafe/Repositories/CatContext.cs +++ b/cat_cafe/Repositories/CatCafeContext.cs @@ -14,5 +14,12 @@ namespace cat_cafe.Repositories public DbSet Bars { get; set; } = null!; public DbSet Customers { get; set; } = null!; + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasOne(c => c.Bar) + .WithMany(b => b.Cats) + .HasForeignKey(c => c.BarId); + } } } diff --git a/cat_cafe/cat_cafe.csproj b/cat_cafe/cat_cafe.csproj index 17549b0..43478bb 100644 --- a/cat_cafe/cat_cafe.csproj +++ b/cat_cafe/cat_cafe.csproj @@ -4,16 +4,18 @@ net6.0 enable enable + $(MSBuildProjectDirectory) - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive -- 2.36.3