Skip to content

Commit

Permalink
Added DataLayer and ServiceLayer
Browse files Browse the repository at this point in the history
  • Loading branch information
JonPSmith committed Mar 12, 2018
1 parent f5d8c39 commit 10f09cd
Show file tree
Hide file tree
Showing 41 changed files with 1,498 additions and 1 deletion.
16 changes: 16 additions & 0 deletions DataLayer/DataLayer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\GenericLibsBase\GenericLibsBase.csproj" />
</ItemGroup>

</Project>
22 changes: 22 additions & 0 deletions DataLayer/Dtos/OrderBooksDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT licence. See License.txt in the project root for license information.

using System;
using DataLayer.EfClasses;

namespace DataLayer.Dtos
{
public struct OrderBooksDto
{
public int BookId { get; }
public Book ChosenBook { get; }
public short numBooks { get; }

public OrderBooksDto(int bookId, Book chosenBook, short numBooks) : this()
{
BookId = bookId;
ChosenBook = chosenBook ?? throw new ArgumentNullException(nameof(chosenBook));
this.numBooks = numBooks;
}
}
}
35 changes: 35 additions & 0 deletions DataLayer/EfClasses/Author.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT licence. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace DataLayer.EfClasses
{
public class Author
{
public const int NameLength = 100;

//used by EF Core
private Author() { }

public Author(string name)
{
Name = name;
}

public int AuthorId { get; private set; }

[Required]
[MaxLength(NameLength)]
public string Name { get; private set; }

//------------------------------
//Relationships

public ICollection<BookAuthor>
BooksLink
{ get; set; }
}

}
122 changes: 122 additions & 0 deletions DataLayer/EfClasses/Book.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT licence. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using GenericLibsBase;
using Microsoft.EntityFrameworkCore;

namespace DataLayer.EfClasses
{
public class Book
{
public const int PromotionalTextLength = 200;
private HashSet<BookAuthor> _authorsLink;

//-----------------------------------------------
//relationships

//Use uninitialised backing fields - this means we can detect if the collection was loaded
private HashSet<Review> _reviews;

//-----------------------------------------------
//ctors

private Book() { }

public Book(string title, string description, DateTime publishedOn,
string publisher, decimal price, string imageUrl, ICollection<Author> authors)
{
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentNullException(nameof(title));

Title = title;
Description = description;
PublishedOn = publishedOn;
Publisher = publisher;
ActualPrice = price;
OrgPrice = price;
ImageUrl = imageUrl;
_reviews = new HashSet<Review>(); //We add an empty list on create. I allows reviews to be added when building test data

if (authors == null || !authors.Any())
throw new ArgumentException("You must have at least one Author for a book", nameof(authors));
byte order = 0;
_authorsLink = new HashSet<BookAuthor>(authors.Select(a => new BookAuthor(this, a, order++)));
}

public int BookId { get; private set; }
public string Title { get; private set; }
public string Description { get; private set; }
public DateTime PublishedOn { get; private set; }
public string Publisher { get; private set; }
public decimal OrgPrice { get; private set; }
public decimal ActualPrice { get; private set; }

[MaxLength(PromotionalTextLength)]
public string PromotionalText { get; private set; }

public string ImageUrl { get; private set; }

public IEnumerable<Review> Reviews => _reviews?.ToList();
public IEnumerable<BookAuthor> AuthorsLink => _authorsLink?.ToList();

public void UpdatePublishedOn(DateTime newDate)
{
PublishedOn = newDate;
}

public void AddReview(int numStars, string comment, string voterName,
DbContext context = null)
{
if (_reviews != null)
{
_reviews.Add(new Review(numStars, comment, voterName));
}
else if (context == null)
{
throw new ArgumentNullException(nameof(context),
"You must provide a context if the Reviews collection isn't valid.");
}
else if (context.Entry(this).IsKeySet)
{
context.Add(new Review(numStars, comment, voterName, BookId));
}
else
{
throw new InvalidOperationException("Could not add a new review.");
}
}

public void RemoveReview(Review review)
{
if (_reviews == null)
throw new NullReferenceException("You must use .Include(p => p.Reviews) before calling this method.");

_reviews.Remove(review);
}

public IStatusGeneric AddPromotion(decimal newPrice, string promotionalText)
{
var status = new StatusGenericHandler();
if (string.IsNullOrWhiteSpace(promotionalText))
{
status.AddError("You must provide some text to go with the promotion.", nameof(PromotionalText));
return status;
}

ActualPrice = newPrice;
PromotionalText = promotionalText;
return status;
}

public void RemovePromotion()
{
ActualPrice = OrgPrice;
PromotionalText = null;
}
}

}
26 changes: 26 additions & 0 deletions DataLayer/EfClasses/BookAuthor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT licence. See License.txt in the project root for license information.
namespace DataLayer.EfClasses
{
public class BookAuthor
{
private BookAuthor() { }

internal BookAuthor(Book book, Author author, byte order)
{
Book = book;
Author = author;
Order = order;
}

public int BookId { get; private set; }
public int AuthorId { get; private set; }
public byte Order { get; private set; }

//-----------------------------
//Relationships

public Book Book { get; private set; }
public Author Author { get; private set; }
}
}
64 changes: 64 additions & 0 deletions DataLayer/EfClasses/LineItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT licence. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace DataLayer.EfClasses
{
public class LineItem : IValidatableObject
{
internal LineItem(short numBooks, Book chosenBook, byte lineNum)
{
NumBooks = numBooks;
ChosenBook = chosenBook ?? throw new ArgumentNullException(nameof(chosenBook));
BookPrice = chosenBook.ActualPrice;
LineNum = lineNum;
}

/// <summary>
/// Used by EF Core
/// </summary>
private LineItem() { }

public int LineItemId { get; private set; }

[Range(1,5, ErrorMessage =
"This order is over the limit of 5 books.")]
public byte LineNum { get; private set; }

public short NumBooks { get; private set; }

/// <summary>
/// This holds a copy of the book price. We do this in case the price of the book changes,
/// e.g. if the price was discounted in the future the order is still correct.
/// </summary>
public decimal BookPrice { get; private set; }

// relationships

public int OrderId { get; private set; }
public int BookId { get; private set; }

public Book ChosenBook { get; private set; }

/// <summary>
/// Extra validation rules: These are checked by using the SaveChangesWithValidation method when saving to the database
/// </summary>
/// <param name="validationContext"></param>
/// <returns></returns>
IEnumerable<ValidationResult> IValidatableObject.Validate
(ValidationContext validationContext)
{
if (BookPrice < 0)
yield return new ValidationResult($"Sorry, the book '{ChosenBook.Title}' is not for sale.");

if (NumBooks > 100)
yield return new ValidationResult("If you want to order a 100 or more books"+
" please phone us on 01234-5678-90",
new[] { nameof(NumBooks) });
}
}

}
87 changes: 87 additions & 0 deletions DataLayer/EfClasses/Order.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT licence. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using DataLayer.Dtos;
using GenericLibsBase;

namespace DataLayer.EfClasses
{
public class Order
{
private HashSet<LineItem> _lineItems;


private Order() { }

public int OrderId { get; private set; }
public DateTime DateOrderedUtc { get; private set; }
public DateTime ExpectedDeliveryDate { get; private set; }
public bool HasBeenDelivered { get; private set; }
public string CustomerName { get; private set; }

// relationships

public IEnumerable<LineItem> LineItems => _lineItems?.ToList();

public string OrderNumber => $"SO{OrderId:D6}";

public static IStatusGeneric<Order> CreateOrderFactory(
string customerName, DateTime expectedDeliveryDate,
IEnumerable<OrderBooksDto> bookOrders)
{
var status = new StatusGenericHandler<Order>();
var order = new Order
{
CustomerName = customerName,
ExpectedDeliveryDate = expectedDeliveryDate,
DateOrderedUtc = DateTime.UtcNow,
HasBeenDelivered = expectedDeliveryDate < DateTime.Today
};

byte lineNum = 1;
order._lineItems = new HashSet<LineItem>(bookOrders
.Select(x => new LineItem(x.numBooks, x.ChosenBook, lineNum++)));
if (!order._lineItems.Any())
status.AddError("No items in your basket.");

status.Result = order;
return status;
}

public IStatusGeneric ChangeDeliveryDate(string userId, DateTime newDeliveryDate)
{
var status = new StatusGenericHandler();
if (CustomerName != userId)
{
status.AddError("I'm sorry, but that order does not belong to you");
//Log a security issue
return status;
}

if (HasBeenDelivered)
{
status.AddError("I'm sorry, but that order has been delivered.");
return status;
}

if (newDeliveryDate < DateTime.Today.AddDays(1))
{
status.AddError("I'm sorry, we cannot get the order to you that quickly. Please choose a new date.", "NewDeliveryDate");
return status;
}

if (newDeliveryDate.DayOfWeek == DayOfWeek.Sunday)
{
status.AddError("I'm sorry, we don't deliver on Sunday. Please choose a new date.", "NewDeliveryDate");
return status;
}

//All Ok
ExpectedDeliveryDate = newDeliveryDate;
return status;
}
}
}
Loading

0 comments on commit 10f09cd

Please sign in to comment.