├── After
├── Entities
│ ├── User.cs
│ ├── Entity.cs
│ ├── Category.cs
│ ├── Entities.csproj
│ ├── ProductCategory.cs
│ ├── AuditableEntity.cs
│ └── Product.cs
├── Handlers
│ ├── SaveChangesPostProcessor
│ │ ├── IChangeDataRequest.cs
│ │ └── SaveChangesRequestPostProcessor.cs
│ ├── Products
│ │ ├── Commands
│ │ │ ├── DeleteProduct
│ │ │ │ ├── DeleteProductCommand.cs
│ │ │ │ └── DeleteProductCommandHandler.cs
│ │ │ ├── Dto
│ │ │ │ └── ProductDto.cs
│ │ │ ├── UpdateProduct
│ │ │ │ ├── UpdateProductCommand.cs
│ │ │ │ └── UpdateProductCommandHandler.cs
│ │ │ └── CreateProduct
│ │ │ │ ├── CreateProductCommand.cs
│ │ │ │ └── CreateProductCommandHandler.cs
│ │ ├── Queries
│ │ │ ├── GetAvailableProducts
│ │ │ │ ├── GetAvailableProductsQuery.cs
│ │ │ │ └── GetAvailableProductsQueryHandler.cs
│ │ │ └── GetProductsByName
│ │ │ │ ├── GetProductsByNameQuery.cs
│ │ │ │ └── GetProductsByNameQueryHandler.cs
│ │ └── DbContextExtensions.cs
│ ├── Categories
│ │ └── Queries
│ │ │ ├── GetDigitalCategories
│ │ │ ├── GetDigitalCategoriesQuery.cs
│ │ │ └── GetDigitalCategoriesQueryHandler.cs
│ │ │ └── GetCategoriesByName
│ │ │ ├── GetCategoriesByNameQuery.cs
│ │ │ └── GetCategoriesByNameQueryHandler.cs
│ └── Handlers.csproj
├── WebHost
│ ├── appsettings.Development.json
│ ├── Utils
│ │ └── AutoMapperProfile.cs
│ ├── appsettings.json
│ ├── Program.cs
│ ├── Services
│ │ └── CurrentUserService.cs
│ ├── Controllers
│ │ ├── CategoriesController.cs
│ │ └── ProductsController.cs
│ ├── WebHost.csproj
│ └── Startup.cs
├── Infrastructure.Interfaces
│ ├── Services
│ │ └── ICurrentUserService.cs
│ ├── DataAccess
│ │ ├── IDbContextPostProcessor.cs
│ │ └── IDbContext.cs
│ └── Infrastructure.Interfaces.csproj
├── Tests
│ ├── Tests.csproj
│ └── NoRepositoryTests.cs
└── DataAccess.MsSql
│ ├── AppDbContextPostProcessor.cs
│ ├── DataAccess.MsSql.csproj
│ ├── AppDbContext.cs
│ └── Migrations
│ ├── 20200621074859_Initial.cs
│ ├── AppDbContextModelSnapshot.cs
│ └── 20200621074859_Initial.Designer.cs
├── Before
├── Entities
│ ├── User.cs
│ ├── Entity.cs
│ ├── Entities.csproj
│ ├── Category.cs
│ ├── ProductCategory.cs
│ ├── AuditableEntity.cs
│ └── Product.cs
├── WebHost
│ ├── appsettings.Development.json
│ ├── Utils
│ │ └── AutoMapperProfile.cs
│ ├── appsettings.json
│ ├── Program.cs
│ ├── Services
│ │ └── CurrentUserService.cs
│ ├── Controllers
│ │ ├── CategoriesController.cs
│ │ └── ProductsController.cs
│ ├── WebHost.csproj
│ └── Startup.cs
├── Infrastructure.Interfaces
│ ├── Services
│ │ └── ICurrentUserService.cs
│ ├── Infrastructure.Interfaces.csproj
│ ├── QueryableHelpers
│ │ ├── IQueryableExecutor.cs
│ │ └── QueryableHelper.cs
│ ├── DataAccess.Repository
│ │ ├── IRepository.cs
│ │ ├── IRepositoryUnitOfWork.cs
│ │ └── IProductRepository.cs
│ └── DataAccess.NoRepository
│ │ └── INoRepositoryUnitOfWork.cs
├── Handlers
│ ├── Products
│ │ ├── Commands
│ │ │ ├── DeleteProduct
│ │ │ │ ├── DeleteProductCommand.cs
│ │ │ │ └── DeleteProductCommandHandler.cs
│ │ │ └── UpdateProduct
│ │ │ │ ├── UpdateProductCommand.cs
│ │ │ │ ├── ProductDto.cs
│ │ │ │ └── UpdateProductCommandHandler.cs
│ │ └── Queries
│ │ │ ├── GetAvailableProducts
│ │ │ ├── GetAvailableProductsQuery.cs
│ │ │ └── GetAvailableProductsQueryHandler.cs
│ │ │ └── GetProductsByName
│ │ │ ├── GetProductsByNameQuery.cs
│ │ │ └── GetProductsByNameQueryHandler.cs
│ ├── Categories
│ │ └── Queries
│ │ │ ├── GetDigitalCategories
│ │ │ ├── GetDigitalCategoriesQuery.cs
│ │ │ └── GetDigitalCategoriesQueryHandler.cs
│ │ │ └── GetCategoriesByName
│ │ │ ├── GetCategoriesByNameQuery.cs
│ │ │ └── GetCategoriesByNameQueryHandler.cs
│ └── Handlers.csproj
├── Tests
│ ├── InMemoryQueryableExecutor.cs
│ ├── Tests.csproj
│ └── RepositoryTests.cs
└── DataAccess.MsSql
│ ├── DataAccess.NoRepository
│ ├── EfQueryableExecutor.cs
│ └── NoRepositoryUnitOfWork.cs
│ ├── AppDbContext.cs
│ ├── DataAccess.MsSql.csproj
│ ├── DataAccess.Repository
│ ├── AuditableRepository.cs
│ ├── Repository.cs
│ ├── RepositoryUnitOfWork.cs
│ └── ProductRepository.cs
│ └── Migrations
│ ├── 20200621075833_Initial.cs
│ ├── AppDbContextModelSnapshot.cs
│ └── 20200621075833_Initial.Designer.cs
├── README.md
├── .gitignore
└── DataAccessWithoutRepositoryAndUnitOfWork.sln
/After/Entities/User.cs:
--------------------------------------------------------------------------------
1 | namespace Entities
2 | {
3 | public class User : Entity
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Before/Entities/User.cs:
--------------------------------------------------------------------------------
1 | namespace Entities
2 | {
3 | public class User : Entity
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DataAccessWithoutRepositoryAndUnitOfWork
2 | Sample project which displayed how to design DataAccess without Repository and UnitOfWork
3 |
--------------------------------------------------------------------------------
/After/Entities/Entity.cs:
--------------------------------------------------------------------------------
1 | namespace Entities
2 | {
3 | public abstract class Entity
4 | {
5 | public int Id { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Before/Entities/Entity.cs:
--------------------------------------------------------------------------------
1 | namespace Entities
2 | {
3 | public abstract class Entity
4 | {
5 | public int Id { get; set; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/After/Handlers/SaveChangesPostProcessor/IChangeDataRequest.cs:
--------------------------------------------------------------------------------
1 | namespace Handlers.SaveChangesPostProcessor
2 | {
3 | public interface IChangeDataRequest
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Before/Entities/Entities.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/After/WebHost/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Before/WebHost/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/After/Entities/Category.cs:
--------------------------------------------------------------------------------
1 | namespace Entities
2 | {
3 | public class Category : Entity
4 | {
5 | public string Name { get; set; }
6 |
7 | public bool IsDigital { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Before/Entities/Category.cs:
--------------------------------------------------------------------------------
1 | namespace Entities
2 | {
3 | public class Category : Entity
4 | {
5 | public string Name { get; set; }
6 |
7 | public bool IsDigital { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/After/Infrastructure.Interfaces/Services/ICurrentUserService.cs:
--------------------------------------------------------------------------------
1 | namespace Infrastructure.Interfaces.Services
2 | {
3 | public interface ICurrentUserService
4 | {
5 | int? UserId { get; }
6 |
7 | bool IsAuthenticated { get; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Before/Infrastructure.Interfaces/Services/ICurrentUserService.cs:
--------------------------------------------------------------------------------
1 | namespace Infrastructure.Interfaces.Services
2 | {
3 | public interface ICurrentUserService
4 | {
5 | int? UserId { get; }
6 |
7 | bool IsAuthenticated { get; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Commands/DeleteProduct/DeleteProductCommand.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 |
3 | namespace Handlers.Products.Commands.DeleteProduct
4 | {
5 | public class DeleteProductCommand : IRequest
6 | {
7 | public int ProductId { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Before/Handlers/Products/Commands/DeleteProduct/DeleteProductCommand.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 |
3 | namespace Handlers.Products.Commands.DeleteProduct
4 | {
5 | public class DeleteProductCommand : IRequest
6 | {
7 | public int ProductId { get; set; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/After/Entities/Entities.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Commands/Dto/ProductDto.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Handlers.Products.Commands.Dto
4 | {
5 | public class ProductDto
6 | {
7 | public string Name { get; set; }
8 | public List CategoryIds { get; set; } = new List();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/After/Entities/ProductCategory.cs:
--------------------------------------------------------------------------------
1 | namespace Entities
2 | {
3 | public class ProductCategory
4 | {
5 | public int ProductId { get; set; }
6 | public int CategoryId { get; set; }
7 |
8 | public Product Product { get; set; }
9 | public Category Category { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/After/Infrastructure.Interfaces/DataAccess/IDbContextPostProcessor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore.ChangeTracking;
2 |
3 | namespace Infrastructure.Interfaces.DataAccess
4 | {
5 | public interface IDbContextPostProcessor : IDbContext
6 | {
7 | ChangeTracker ChangeTracker { get; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Before/Entities/ProductCategory.cs:
--------------------------------------------------------------------------------
1 | namespace Entities
2 | {
3 | public class ProductCategory
4 | {
5 | public int ProductId { get; set; }
6 | public int CategoryId { get; set; }
7 |
8 | public Product Product { get; set; }
9 | public Category Category { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Queries/GetAvailableProducts/GetAvailableProductsQuery.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using MediatR;
3 | using System.Collections.Generic;
4 |
5 | namespace Handlers.Products.Queries.GetAvailableProducts
6 | {
7 | public class GetAvailableProductsQuery : IRequest>
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Before/Handlers/Products/Commands/UpdateProduct/UpdateProductCommand.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 |
3 | namespace Handlers.Products.Commands.UpdateProduct
4 | {
5 | public class UpdateProductCommand : IRequest
6 | {
7 | public int ProductId { get; set; }
8 | public ProductDto ProductDto { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Before/Handlers/Products/Commands/UpdateProduct/ProductDto.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Handlers.Products.Commands.UpdateProduct
4 | {
5 | public class ProductDto
6 | {
7 | public string Name { get; set; }
8 | public List CategoryIds { get; set; } = new List();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Before/Handlers/Products/Queries/GetAvailableProducts/GetAvailableProductsQuery.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using MediatR;
3 | using System.Collections.Generic;
4 |
5 | namespace Handlers.Products.Queries.GetAvailableProducts
6 | {
7 | public class GetAvailableProductsQuery : IRequest>
8 | {
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Before/Infrastructure.Interfaces/Infrastructure.Interfaces.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/After/Handlers/Categories/Queries/GetDigitalCategories/GetDigitalCategoriesQuery.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using MediatR;
3 | using System.Collections.Generic;
4 |
5 | namespace Handlers.Categories.Queries.GetDigitalCategories
6 | {
7 | public class GetDigitalCategoriesQuery : IRequest>
8 | {
9 |
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Before/Handlers/Categories/Queries/GetDigitalCategories/GetDigitalCategoriesQuery.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using MediatR;
3 | using System.Collections.Generic;
4 |
5 | namespace Handlers.Categories.Queries.GetDigitalCategories
6 | {
7 | public class GetDigitalCategoriesQuery : IRequest>
8 | {
9 |
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/After/WebHost/Utils/AutoMapperProfile.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using Entities;
3 | using Handlers.Products.Commands.Dto;
4 |
5 | namespace WebHost
6 | {
7 | public class AutoMapperProfile : Profile
8 | {
9 | public AutoMapperProfile()
10 | {
11 | CreateMap();
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/After/Entities/AuditableEntity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Entities
4 | {
5 | public abstract class AuditableEntity : Entity
6 | {
7 | public DateTime CreatedAt { get; set; }
8 | public int CreatedBy { get; set; }
9 |
10 | public DateTime? ModifiedAt { get; set; }
11 | public int? ModifiedBy { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Before/Entities/AuditableEntity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Entities
4 | {
5 | public abstract class AuditableEntity : Entity
6 | {
7 | public DateTime CreatedAt { get; set; }
8 | public int CreatedBy { get; set; }
9 |
10 | public DateTime? ModifiedAt { get; set; }
11 | public int? ModifiedBy { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Before/Handlers/Products/Queries/GetProductsByName/GetProductsByNameQuery.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using System.Collections.Generic;
3 | using Entities;
4 |
5 | namespace Handlers.Products.Queries.GetProductsByName
6 | {
7 | public class GetProductsByNameQuery : IRequest>
8 | {
9 | public string Name { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Commands/UpdateProduct/UpdateProductCommand.cs:
--------------------------------------------------------------------------------
1 | using Handlers.Products.Commands.Dto;
2 | using MediatR;
3 |
4 | namespace Handlers.Products.Commands.UpdateProduct
5 | {
6 | public class UpdateProductCommand : IRequest
7 | {
8 | public int ProductId { get; set; }
9 | public ProductDto ProductDto { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Before/WebHost/Utils/AutoMapperProfile.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using Entities;
3 | using Handlers.Products.Commands.UpdateProduct;
4 |
5 | namespace WebHost
6 | {
7 | public class AutoMapperProfile : Profile
8 | {
9 | public AutoMapperProfile()
10 | {
11 | CreateMap();
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/After/Handlers/Categories/Queries/GetCategoriesByName/GetCategoriesByNameQuery.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using MediatR;
3 | using System.Collections.Generic;
4 |
5 | namespace Handlers.Categories.Queries.GetCategoriesByName
6 | {
7 | public class GetCategoriesByNameQuery : IRequest>
8 | {
9 | public string CategoryName { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Before/Handlers/Categories/Queries/GetCategoriesByName/GetCategoriesByNameQuery.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using MediatR;
3 | using System.Collections.Generic;
4 |
5 | namespace Handlers.Categories.Queries.GetCategoriesByName
6 | {
7 | public class GetCategoriesByNameQuery : IRequest>
8 | {
9 | public string CategoryName { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Before/Infrastructure.Interfaces/QueryableHelpers/IQueryableExecutor.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 |
5 | namespace Infrastructure.Interfaces.QueryableHelpers
6 | {
7 | public interface IQueryableExecutor
8 | {
9 | Task> ToListAsync(IQueryable query);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/After/WebHost/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "MsSqlConnection": "Data Source=.;Initial Catalog=NoRepository2;Integrated Security=True"
4 | },
5 | "Logging": {
6 | "LogLevel": {
7 | "Default": "Information",
8 | "Microsoft": "Warning",
9 | "Microsoft.Hosting.Lifetime": "Information"
10 | }
11 | },
12 | "AllowedHosts": "*"
13 | }
14 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Commands/CreateProduct/CreateProductCommand.cs:
--------------------------------------------------------------------------------
1 | using Handlers.Products.Commands.Dto;
2 | using Handlers.SaveChangesPostProcessor;
3 | using MediatR;
4 |
5 | namespace Handlers.Products.Commands.CreateProduct
6 | {
7 | public class CreateProductCommand : IRequest, IChangeDataRequest
8 | {
9 | public ProductDto ProductDto { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Before/WebHost/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "MsSqlConnection": "Data Source=.;Initial Catalog=NoRepository_Before;Integrated Security=True"
4 | },
5 | "Logging": {
6 | "LogLevel": {
7 | "Default": "Information",
8 | "Microsoft": "Warning",
9 | "Microsoft.Hosting.Lifetime": "Information"
10 | }
11 | },
12 | "AllowedHosts": "*"
13 | }
14 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Queries/GetProductsByName/GetProductsByNameQuery.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using System.Collections.Generic;
3 | using Entities;
4 | using Handlers.SaveChangesPostProcessor;
5 |
6 | namespace Handlers.Products.Queries.GetProductsByName
7 | {
8 | public class GetProductsByNameQuery : IRequest>
9 | {
10 | public string Name { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Before/Entities/Product.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Entities
4 | {
5 | public class Product : AuditableEntity
6 | {
7 | public string Name { get; set; }
8 | public int Quantity { get; set; }
9 | public bool IsAvailable { get; set; }
10 |
11 | public ICollection ProductCategories { get; protected set; } = new HashSet();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/After/Infrastructure.Interfaces/Infrastructure.Interfaces.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Before/Tests/InMemoryQueryableExecutor.cs:
--------------------------------------------------------------------------------
1 | using Infrastructure.Interfaces.QueryableHelpers;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace Tests
7 | {
8 | public class InMemoryQueryableExecutor : IQueryableExecutor
9 | {
10 | public async Task> ToListAsync(IQueryable query)
11 | {
12 | return query.ToList();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Before/Infrastructure.Interfaces/DataAccess.Repository/IRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 |
4 | namespace Infrastructure.Interfaces.DataAccess
5 | {
6 | public interface IRepository
7 | {
8 | ValueTask GetAsync(int id);
9 | void Add(TEntity entity);
10 | void Update(TEntity entity);
11 | void Remove(TEntity entity);
12 | void RemoveRange(IEnumerable entities);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Before/Infrastructure.Interfaces/DataAccess.Repository/IRepositoryUnitOfWork.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using System.Threading.Tasks;
3 |
4 | namespace Infrastructure.Interfaces.DataAccess
5 | {
6 | public interface IRepositoryUnitOfWork
7 | {
8 | IProductRepository ProductRepository { get; }
9 | IRepository ProductCategoryRepository { get; }
10 | Task SaveChangesAsync();
11 |
12 | //IQueryable Products { get; } // anti-pattern
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Before/Infrastructure.Interfaces/QueryableHelpers/QueryableHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 |
5 | namespace Infrastructure.Interfaces.QueryableHelpers
6 | {
7 | public static class QueryableHelper
8 | {
9 | public static IQueryableExecutor QueryableExecutor { get; set; }
10 |
11 | public static Task> ToListAsync(this IQueryable query)
12 | {
13 | return QueryableExecutor.ToListAsync(query);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Before/Infrastructure.Interfaces/DataAccess.NoRepository/INoRepositoryUnitOfWork.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 |
5 | namespace Infrastructure.Interfaces.DataAccess.NoRepository
6 | {
7 | public interface INoRepositoryUnitOfWork
8 | {
9 | IQueryable Products { get; }
10 |
11 | IQueryable Categories { get; }
12 |
13 | void Add(TEntity entity);
14 |
15 | void Remove(TEntity entity);
16 |
17 | Task SaveChangesAsync();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Before/Handlers/Handlers.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/DataAccess.NoRepository/EfQueryableExecutor.cs:
--------------------------------------------------------------------------------
1 | using Infrastructure.Interfaces.QueryableHelpers;
2 | using Microsoft.EntityFrameworkCore;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 |
7 | namespace DataAccess.MsSql.DataAccess.NoRepository
8 | {
9 | public class EfQueryableExecutor : IQueryableExecutor
10 | {
11 | public Task> ToListAsync(IQueryable query)
12 | {
13 | return EntityFrameworkQueryableExtensions.ToListAsync(query);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Before/WebHost/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.Hosting;
3 |
4 | namespace WebHost
5 | {
6 | public class Program
7 | {
8 | public static void Main(string[] args)
9 | {
10 | CreateHostBuilder(args).Build().Run();
11 | }
12 |
13 | public static IHostBuilder CreateHostBuilder(string[] args) =>
14 | Host.CreateDefaultBuilder(args)
15 | .ConfigureWebHostDefaults(webBuilder =>
16 | {
17 | webBuilder.UseStartup();
18 | });
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/After/Handlers/Handlers.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Before/Infrastructure.Interfaces/DataAccess.Repository/IProductRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using Entities;
4 |
5 | namespace Infrastructure.Interfaces.DataAccess
6 | {
7 | public interface IProductRepository : IRepository
8 | {
9 | Task GetWithCategoriesAsync(int id);
10 |
11 | Task> GetProductsByNameAsync(string name);
12 |
13 | Task> GetAvailableProductsAsync();
14 |
15 | //IQueryable Products { get; } // anti-pattern
16 | //IQueryable AvailableProducts { get; } // anti-pattern
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/After/WebHost/Program.cs:
--------------------------------------------------------------------------------
1 | using Autofac.Extensions.DependencyInjection;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Hosting;
4 |
5 | namespace WebHost
6 | {
7 | public class Program
8 | {
9 | public static void Main(string[] args)
10 | {
11 | CreateHostBuilder(args).Build().Run();
12 | }
13 |
14 | public static IHostBuilder CreateHostBuilder(string[] args) =>
15 | Host.CreateDefaultBuilder(args)
16 | .UseServiceProviderFactory(new AutofacServiceProviderFactory())
17 | .ConfigureWebHostDefaults(webBuilder =>
18 | {
19 | webBuilder.UseStartup();
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/After/Infrastructure.Interfaces/DataAccess/IDbContext.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Microsoft.EntityFrameworkCore;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Microsoft.EntityFrameworkCore.ChangeTracking;
6 |
7 | namespace Infrastructure.Interfaces.DataAccess
8 | {
9 | public interface IDbContext
10 | {
11 | DbSet Products { get; set; }
12 |
13 | DbSet Categories { get; set; }
14 |
15 | DbSet ProductCategories { get; set; }
16 |
17 | DbSet Users { get; set; }
18 |
19 | Task SaveChangesAsync(CancellationToken cancellationToken = default);
20 | EntityEntry Entry(TEntity result) where TEntity : class;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/After/WebHost/Services/CurrentUserService.cs:
--------------------------------------------------------------------------------
1 | using Infrastructure.Interfaces.Services;
2 | using Microsoft.AspNetCore.Http;
3 |
4 | namespace WebHost.Services
5 | {
6 | public class CurrentUserService : ICurrentUserService
7 | {
8 | public CurrentUserService(IHttpContextAccessor httpContextAccessor)
9 | {
10 | IsAuthenticated = httpContextAccessor?.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
11 | UserId = 1;
12 | //if (IsAuthenticated)
13 | //{
14 | // UserId = int.Parse(httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
15 | //}
16 | }
17 | public int? UserId { get; }
18 |
19 | public bool IsAuthenticated { get; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Before/WebHost/Services/CurrentUserService.cs:
--------------------------------------------------------------------------------
1 | using Infrastructure.Interfaces.Services;
2 | using Microsoft.AspNetCore.Http;
3 | using System.Security.Claims;
4 |
5 | namespace WebHost.Services
6 | {
7 | public class CurrentUserService : ICurrentUserService
8 | {
9 | public CurrentUserService(IHttpContextAccessor httpContextAccessor)
10 | {
11 | IsAuthenticated = httpContextAccessor?.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
12 | UserId = 1;
13 | //if (IsAuthenticated)
14 | //{
15 | // UserId = int.Parse(httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
16 | //}
17 | }
18 | public int? UserId { get; }
19 |
20 | public bool IsAuthenticated { get; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Before/Tests/Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/AppDbContext.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Microsoft.EntityFrameworkCore;
3 |
4 | namespace DataAccess.MsSql
5 | {
6 | public class AppDbContext : DbContext
7 | {
8 | public AppDbContext(DbContextOptions options)
9 | : base(options)
10 | {
11 | }
12 | public DbSet Products { get; set; }
13 |
14 | public DbSet Categories { get; set; }
15 |
16 | public DbSet ProductCategories { get; set; }
17 |
18 | public DbSet Users { get; set; }
19 |
20 | protected override void OnModelCreating(ModelBuilder builder)
21 | {
22 | base.OnModelCreating(builder);
23 |
24 | builder.Entity()
25 | .HasKey(x => new { x.ProductId, x.CategoryId });
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Before/Handlers/Products/Queries/GetProductsByName/GetProductsByNameQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Entities;
6 | using Infrastructure.Interfaces.DataAccess;
7 |
8 | namespace Handlers.Products.Queries.GetProductsByName
9 | {
10 | public class GetProductsByNameQueryHandler : IRequestHandler>
11 | {
12 | private readonly IRepositoryUnitOfWork _uow;
13 |
14 | public GetProductsByNameQueryHandler(IRepositoryUnitOfWork uow)
15 | {
16 | _uow = uow;
17 | }
18 | public async Task> Handle(GetProductsByNameQuery request, CancellationToken cancellationToken)
19 | {
20 | return await _uow.ProductRepository.GetProductsByNameAsync(request.Name);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/After/Handlers/Products/DbContextExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 | using Entities;
4 | using Infrastructure.Interfaces.DataAccess;
5 | using Microsoft.EntityFrameworkCore;
6 |
7 | namespace Handlers.Products
8 | {
9 | public static class DbContextExtensions
10 | {
11 | public static Task SingleOrDefaultFullAsync(this IQueryable dbSet, int id)
12 | {
13 | return dbSet
14 | .Include(x => x.ProductCategories)
15 | .SingleOrDefaultAsync(x => x.Id == id);
16 | }
17 |
18 | public static async Task FindFullProductAsync(this IDbContext dbContext, int id)
19 | {
20 | var result = await dbContext.Products.FindAsync(id);
21 | await dbContext.Entry(result).Collection(x => x.ProductCategories).LoadAsync();
22 | return result;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Before/Handlers/Products/Queries/GetAvailableProducts/GetAvailableProductsQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using MediatR;
4 | using System.Collections.Generic;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace Handlers.Products.Queries.GetAvailableProducts
9 | {
10 | public class GetAvailableProductsQueryHandler : IRequestHandler>
11 | {
12 | private readonly IRepositoryUnitOfWork _uow;
13 |
14 | public GetAvailableProductsQueryHandler(IRepositoryUnitOfWork uow)
15 | {
16 | _uow = uow;
17 | }
18 | public async Task> Handle(GetAvailableProductsQuery request, CancellationToken cancellationToken)
19 | {
20 | return await _uow.ProductRepository.GetAvailableProductsAsync();
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/After/Tests/Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Commands/DeleteProduct/DeleteProductCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using MediatR;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace Handlers.Products.Commands.DeleteProduct
8 | {
9 | public class DeleteProductCommandHandler : AsyncRequestHandler
10 | {
11 | private IDbContext _dbContext;
12 | public DeleteProductCommandHandler(IDbContext dbContext)
13 | {
14 | _dbContext = dbContext;
15 | }
16 |
17 | protected override async Task Handle(DeleteProductCommand request, CancellationToken cancellationToken)
18 | {
19 | _dbContext.Products.Remove(new Product { Id = request.ProductId });
20 |
21 | //cascade delete for product categories
22 |
23 | await _dbContext.SaveChangesAsync();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/After/DataAccess.MsSql/AppDbContextPostProcessor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using Entities;
4 |
5 | namespace DataAccess.MsSql
6 | {
7 |
8 | public class AppDbContextPostProcessor : DbContext, IDbContextPostProcessor
9 | {
10 | public AppDbContextPostProcessor(DbContextOptions options)
11 | : base(options)
12 | {
13 |
14 | }
15 | public DbSet Products { get; set; }
16 |
17 | public DbSet Categories { get; set; }
18 |
19 | public DbSet ProductCategories { get; set; }
20 |
21 | public DbSet Users { get; set; }
22 |
23 | protected override void OnModelCreating(ModelBuilder builder)
24 | {
25 | base.OnModelCreating(builder);
26 |
27 | builder.Entity()
28 | .HasKey(x => new { x.ProductId, x.CategoryId });
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Before/Handlers/Products/Commands/DeleteProduct/DeleteProductCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using Infrastructure.Interfaces.DataAccess;
2 | using MediatR;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace Handlers.Products.Commands.DeleteProduct
7 | {
8 | public class DeleteProductCommandHandler : AsyncRequestHandler
9 | {
10 | private IRepositoryUnitOfWork _uow;
11 | public DeleteProductCommandHandler(IRepositoryUnitOfWork uow)
12 | {
13 | _uow = uow;
14 | }
15 |
16 | protected override async Task Handle(DeleteProductCommand request, CancellationToken cancellationToken)
17 | {
18 | var product = await _uow.ProductRepository.GetWithCategoriesAsync(request.ProductId);
19 |
20 | _uow.ProductCategoryRepository.RemoveRange(product.ProductCategories);
21 | _uow.ProductRepository.Remove(product);
22 |
23 | await _uow.SaveChangesAsync();
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Queries/GetAvailableProducts/GetAvailableProductsQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using MediatR;
4 | using Microsoft.EntityFrameworkCore;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace Handlers.Products.Queries.GetAvailableProducts
11 | {
12 | public class GetAvailableProductsQueryHandler : IRequestHandler>
13 | {
14 | private readonly IDbContext _dbContext;
15 |
16 | public GetAvailableProductsQueryHandler(IDbContext dbContext)
17 | {
18 | _dbContext = dbContext;
19 | }
20 | public Task> Handle(GetAvailableProductsQuery request, CancellationToken cancellationToken)
21 | {
22 | return _dbContext.Products
23 | .AsNoTracking()
24 | .Where(Product.AvailableSpec)
25 | .ToListAsync();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Queries/GetProductsByName/GetProductsByNameQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using MediatR;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Entities;
6 | using Infrastructure.Interfaces.DataAccess;
7 | using System.Linq;
8 | using Microsoft.EntityFrameworkCore;
9 |
10 | namespace Handlers.Products.Queries.GetProductsByName
11 | {
12 | public class GetProductsByNameQueryHandler : IRequestHandler>
13 | {
14 | private readonly IDbContext _dbContext;
15 |
16 | public GetProductsByNameQueryHandler(IDbContext dbContext)
17 | {
18 | _dbContext = dbContext;
19 | }
20 | public Task> Handle(GetProductsByNameQuery request, CancellationToken cancellationToken)
21 | {
22 | return _dbContext.Products
23 | .AsNoTracking()
24 | .Where(Product.AvailableSpec && Product.ByNameSpec(request.Name))
25 | .ToListAsync();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Before/Handlers/Categories/Queries/GetCategoriesByName/GetCategoriesByNameQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess.NoRepository;
3 | using Infrastructure.Interfaces.QueryableHelpers;
4 | using MediatR;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace Handlers.Categories.Queries.GetCategoriesByName
11 | {
12 | public class GetCategoriesByNameQueryHandler : IRequestHandler>
13 | {
14 | private readonly INoRepositoryUnitOfWork _uow;
15 |
16 | public GetCategoriesByNameQueryHandler(INoRepositoryUnitOfWork uow)
17 | {
18 | _uow = uow;
19 | }
20 | public Task> Handle(GetCategoriesByNameQuery request, CancellationToken cancellationToken)
21 | {
22 | return _uow.Categories
23 | .Where(x => x.Name.Contains(request.CategoryName))
24 | .ToListAsync(); // QueryableHelper
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/After/DataAccess.MsSql/DataAccess.MsSql.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 | all
10 | runtime; build; native; contentfiles; analyzers; buildtransitive
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/After/Handlers/Categories/Queries/GetCategoriesByName/GetCategoriesByNameQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using MediatR;
4 | using Microsoft.EntityFrameworkCore;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace Handlers.Categories.Queries.GetCategoriesByName
11 | {
12 | public class GetCategoriesByNameQueryHandler : IRequestHandler>
13 | {
14 | private readonly IDbContext _dbContext;
15 |
16 | public GetCategoriesByNameQueryHandler(IDbContext dbContext)
17 | {
18 | _dbContext = dbContext;
19 | }
20 | public Task> Handle(GetCategoriesByNameQuery request, CancellationToken cancellationToken)
21 | {
22 | return _dbContext.Categories
23 | .AsNoTracking()
24 | .Where(x => x.Name.Contains(request.CategoryName))
25 | .ToListAsync(); // EF Core
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/DataAccess.MsSql.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 | all
10 | runtime; build; native; contentfiles; analyzers; buildtransitive
11 |
12 |
13 |
14 | all
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/After/Handlers/Categories/Queries/GetDigitalCategories/GetDigitalCategoriesQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using MediatR;
4 | using Microsoft.EntityFrameworkCore;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace Handlers.Categories.Queries.GetDigitalCategories
11 | {
12 | public class GetDigitalCategoriesQueryHandler : IRequestHandler>
13 | {
14 | private readonly IDbContext _dbContext;
15 |
16 | public GetDigitalCategoriesQueryHandler(IDbContext dbContext)
17 | {
18 | _dbContext = dbContext;
19 | }
20 | public Task> Handle(GetDigitalCategoriesQuery request, CancellationToken cancellationToken)
21 | {
22 | return _dbContext.Categories
23 | .AsNoTracking()
24 | .Where(x => x.IsDigital)
25 | .ToListAsync();
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/After/WebHost/Controllers/CategoriesController.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Handlers.Categories.Queries.GetCategoriesByName;
3 | using Handlers.Categories.Queries.GetDigitalCategories;
4 | using MediatR;
5 | using Microsoft.AspNetCore.Mvc;
6 | using System.Collections.Generic;
7 | using System.Threading.Tasks;
8 |
9 | namespace WebHost.Controllers
10 | {
11 | [ApiController]
12 | [Route("[controller]/[action]")]
13 | public class CategoriesController : ControllerBase
14 | {
15 | private IMediator _mediator;
16 |
17 | public CategoriesController(IMediator mediator)
18 | {
19 | _mediator = mediator;
20 | }
21 |
22 | [HttpGet]
23 | public async Task> GetDigitalCategories()
24 | {
25 | return await _mediator.Send(new GetDigitalCategoriesQuery());
26 | }
27 |
28 | [HttpGet]
29 | public async Task> GetCategoriesByName(string name)
30 | {
31 | return await _mediator.Send(new GetCategoriesByNameQuery { CategoryName = name });
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/DataAccess.NoRepository/NoRepositoryUnitOfWork.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess.NoRepository;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace DataAccess.MsSql.DataAccess.NoRepository
7 | {
8 | public class NoRepositoryUnitOfWork : INoRepositoryUnitOfWork
9 | {
10 | private readonly AppDbContext _dbContext;
11 | public NoRepositoryUnitOfWork(AppDbContext dbContext)
12 | {
13 | _dbContext = dbContext;
14 | }
15 |
16 | public IQueryable Products => _dbContext.Products;
17 |
18 | public IQueryable Categories => _dbContext.Categories;
19 |
20 | public void Add(TEntity entity)
21 | {
22 | _dbContext.Add(entity);
23 | }
24 |
25 | public void Remove(TEntity entity)
26 | {
27 | _dbContext.Remove(entity);
28 | }
29 |
30 | public Task SaveChangesAsync()
31 | {
32 | return _dbContext.SaveChangesAsync();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Before/WebHost/Controllers/CategoriesController.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Handlers.Categories.Queries.GetCategoriesByName;
3 | using Handlers.Categories.Queries.GetDigitalCategories;
4 | using MediatR;
5 | using Microsoft.AspNetCore.Mvc;
6 | using System.Collections.Generic;
7 | using System.Threading.Tasks;
8 |
9 | namespace WebHost.Controllers
10 | {
11 | [ApiController]
12 | [Route("[controller]/[action]")]
13 | public class CategoriesController : ControllerBase
14 | {
15 | private IMediator _mediator;
16 |
17 | public CategoriesController(IMediator mediator)
18 | {
19 | _mediator = mediator;
20 | }
21 |
22 | [HttpGet]
23 | public async Task> GetDigitalCategories()
24 | {
25 | return await _mediator.Send(new GetDigitalCategoriesQuery());
26 | }
27 |
28 | [HttpGet]
29 | public async Task> GetCategoriesByName(string name)
30 | {
31 | return await _mediator.Send(new GetCategoriesByNameQuery { CategoryName = name });
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/DataAccess.Repository/AuditableRepository.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.Services;
3 | using System;
4 |
5 | namespace DataAccess.MsSql.DataAccess
6 | {
7 | public class AuditableRepository : Repository
8 | where TEntity : AuditableEntity
9 | {
10 | private readonly ICurrentUserService _currentUserService;
11 |
12 | public AuditableRepository(AppDbContext dbContext, ICurrentUserService currentUserService) : base(dbContext)
13 | {
14 | _currentUserService = currentUserService;
15 | }
16 |
17 | public override void Add(TEntity entity)
18 | {
19 | entity.CreatedAt = DateTime.Now;
20 | entity.CreatedBy = _currentUserService.UserId.Value;
21 |
22 | base.Add(entity);
23 | }
24 |
25 | public override void Update(TEntity entity)
26 | {
27 | entity.ModifiedAt = DateTime.Now;
28 | entity.ModifiedBy = _currentUserService.UserId.Value;
29 |
30 | base.Update(entity);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Before/WebHost/WebHost.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 |
10 |
11 | all
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/DataAccess.Repository/Repository.cs:
--------------------------------------------------------------------------------
1 | using Infrastructure.Interfaces.DataAccess;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 |
5 | namespace DataAccess.MsSql.DataAccess
6 | {
7 | public class Repository : IRepository
8 | where TEntity : class
9 | {
10 | protected readonly AppDbContext DbContext;
11 |
12 | public Repository(AppDbContext dbContext)
13 | {
14 | DbContext = dbContext;
15 | }
16 |
17 | public virtual ValueTask GetAsync(int id)
18 | {
19 | return DbContext.FindAsync(id);
20 | }
21 |
22 | public virtual void Add(TEntity entity)
23 | {
24 | DbContext.Add(entity);
25 | }
26 |
27 | public virtual void Update(TEntity entity)
28 | {
29 | DbContext.Update(entity);
30 | }
31 |
32 | public virtual void Remove(TEntity entity)
33 | {
34 | DbContext.Remove(entity);
35 | }
36 |
37 | public virtual void RemoveRange(IEnumerable entities)
38 | {
39 | DbContext.RemoveRange(entities);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Before/Handlers/Categories/Queries/GetDigitalCategories/GetDigitalCategoriesQueryHandler.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess.NoRepository;
3 | using Infrastructure.Interfaces.QueryableHelpers;
4 | using MediatR;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace Handlers.Categories.Queries.GetDigitalCategories
11 | {
12 | public class GetDigitalCategoriesQueryHandler : IRequestHandler>
13 | {
14 | private readonly INoRepositoryUnitOfWork _uow;
15 | private readonly IQueryableExecutor _queryableExecutor;
16 |
17 | public GetDigitalCategoriesQueryHandler(INoRepositoryUnitOfWork uow, IQueryableExecutor queryableExecutor)
18 | {
19 | _uow = uow;
20 | _queryableExecutor = queryableExecutor;
21 | }
22 | public Task> Handle(GetDigitalCategoriesQuery request, CancellationToken cancellationToken)
23 | {
24 | var query = _uow.Categories
25 | .Where(x => x.IsDigital);
26 |
27 | return _queryableExecutor.ToListAsync(query);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Commands/CreateProduct/CreateProductCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using Entities;
3 | using Infrastructure.Interfaces.DataAccess;
4 | using MediatR;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace Handlers.Products.Commands.CreateProduct
9 | {
10 | public class CreateProductCommandHandler : AsyncRequestHandler
11 | {
12 | private readonly IDbContextPostProcessor _dbContext;
13 | private readonly IMapper _mapper;
14 |
15 | public CreateProductCommandHandler(IDbContextPostProcessor dbContext, IMapper mapper)
16 | {
17 | _dbContext = dbContext;
18 | _mapper = mapper;
19 | }
20 | protected override async Task Handle(CreateProductCommand request, CancellationToken cancellationToken)
21 | {
22 | var product = _mapper.Map(request.ProductDto);
23 |
24 | product.UpdateCategories(request.ProductDto.CategoryIds);
25 |
26 | _dbContext.Products.Add(product);
27 |
28 | //CreatedAt abd CreatedBy properties initialized in PostProcessor
29 |
30 | //No _dbContext.SaveChanges(), it called in PostProcessor
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/After/WebHost/WebHost.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | all
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/DataAccess.Repository/RepositoryUnitOfWork.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using Infrastructure.Interfaces.Services;
4 | using System.Threading.Tasks;
5 |
6 | namespace DataAccess.MsSql.DataAccess
7 | {
8 | public class RepositoryUnitOfWork : IRepositoryUnitOfWork
9 | {
10 | private readonly AppDbContext _dbContext;
11 | private readonly ICurrentUserService _currentUserService;
12 |
13 | public RepositoryUnitOfWork(AppDbContext dbContext, ICurrentUserService currentUserService)
14 | {
15 | _dbContext = dbContext;
16 | _currentUserService = currentUserService;
17 | }
18 |
19 | private IProductRepository _productRepository;
20 | public IProductRepository ProductRepository =>
21 | _productRepository ??= new ProductRepository(_dbContext, _currentUserService);
22 |
23 | private IRepository _productCategoryRepository;
24 | public IRepository ProductCategoryRepository =>
25 | _productCategoryRepository ??= new Repository(_dbContext);
26 |
27 | public Task SaveChangesAsync()
28 | {
29 | return _dbContext.SaveChangesAsync();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/After/Handlers/Products/Commands/UpdateProduct/UpdateProductCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using MediatR;
4 | using Microsoft.EntityFrameworkCore;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | namespace Handlers.Products.Commands.UpdateProduct
9 | {
10 | public class UpdateProductCommandHandler : AsyncRequestHandler
11 | {
12 | private readonly IDbContext _dbContext;
13 | private readonly IMapper _mapper;
14 |
15 | public UpdateProductCommandHandler(IDbContext dbContext, IMapper mapper)
16 | {
17 | _dbContext = dbContext;
18 | _mapper = mapper;
19 | }
20 |
21 | protected override async Task Handle(UpdateProductCommand request, CancellationToken cancellationToken)
22 | {
23 | //var product = await _dbContext.Products
24 | // .Include(x => x.ProductCategories)
25 | // .SingleOrDefaultAsync(x => x.Id == request.ProductId);
26 |
27 | //var product = await _dbContext.Products.SingleOrDefaultFullAsync(request.ProductId);
28 |
29 | var product = await _dbContext.FindFullProductAsync(request.ProductId);
30 |
31 | _mapper.Map(request.ProductDto, product);
32 |
33 | //method Product.UpdateCategories may be used in many handlers if needed
34 | product.UpdateCategories(request.ProductDto.CategoryIds);
35 |
36 | // update ModifiedAt and ModifiedBy in overriden SaveChanges method
37 | await _dbContext.SaveChangesAsync();
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/After/Entities/Product.cs:
--------------------------------------------------------------------------------
1 | using AutoFilter;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Entities
6 | {
7 | public class Product : AuditableEntity
8 | {
9 | public static readonly Spec AvailableSpec =
10 | new Spec(x => x.IsAvailable && x.Quantity > 0);
11 |
12 | public static Spec ByNameSpec(string name)
13 | {
14 | return new Spec(x => x.Name.Contains(name));
15 | }
16 |
17 | public string Name { get; set; }
18 | public int Quantity { get; set; }
19 | public bool IsAvailable { get; set; }
20 |
21 | public ICollection ProductCategories { get; protected set; } = new HashSet();
22 |
23 | public void UpdateCategories(IEnumerable newCategoryIds)
24 | {
25 | var currentCategoryIds = ProductCategories.Select(x => x.CategoryId).ToList();
26 |
27 | //delete not existing categories
28 | foreach (var category in ProductCategories
29 | .Where(x => !newCategoryIds.Contains(x.CategoryId))
30 | .ToList())
31 | {
32 | ProductCategories.Remove(category);
33 | }
34 |
35 | //new categories
36 | foreach (var categoryId in newCategoryIds.Except(currentCategoryIds))
37 | {
38 | ProductCategories.Add(new ProductCategory
39 | {
40 | CategoryId = categoryId,
41 | ProductId = Id
42 | });
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/DataAccess.Repository/ProductRepository.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using Infrastructure.Interfaces.Services;
4 | using Microsoft.EntityFrameworkCore;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 |
9 | namespace DataAccess.MsSql.DataAccess
10 | {
11 | public class ProductRepository : AuditableRepository, IProductRepository
12 | {
13 | public ProductRepository(AppDbContext dbContext, ICurrentUserService currentUserService) : base(dbContext, currentUserService)
14 | {
15 | }
16 |
17 | private IQueryable GetAvailable()
18 | {
19 | return DbContext.Products
20 | .Where(x => x.IsAvailable && x.Quantity > 0);
21 | }
22 |
23 | public async Task> GetAvailableProductsAsync()
24 | {
25 | DbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
26 |
27 | return await GetAvailable().ToListAsync();
28 | }
29 |
30 | public async Task> GetProductsByNameAsync(string name)
31 | {
32 | DbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
33 |
34 | return await GetAvailable()
35 | .Where(x => x.Name.Contains(name))
36 | .ToListAsync();
37 | }
38 |
39 | public Task GetWithCategoriesAsync(int id)
40 | {
41 | return DbContext.Products
42 | .Include(x => x.ProductCategories)
43 | .SingleAsync(x => x.Id == id);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Before/Tests/RepositoryTests.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Handlers.Products.Queries.GetAvailableProducts;
3 | using Infrastructure.Interfaces.DataAccess;
4 | using Infrastructure.Interfaces.QueryableHelpers;
5 | using Moq;
6 | using System.Collections.Generic;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using Xunit;
10 |
11 | namespace Tests
12 | {
13 | public class RepositoryTests
14 | {
15 | static RepositoryTests()
16 | {
17 | QueryableHelper.QueryableExecutor = new InMemoryQueryableExecutor();
18 | }
19 |
20 | [Fact]
21 | public async Task MockOfRepositoryMethod()
22 | {
23 | // Arrange
24 | var mockRepository = new Mock();
25 | IReadOnlyList products = new List()
26 | {
27 | new Product { IsAvailable = true, Quantity = 10, Id = 1, Name = "Product1" },
28 | new Product { IsAvailable = true, Quantity = 1, Id = 2, Name = "Product2" },
29 | };
30 | //it is possible to mock any method of repository or unit of work
31 | mockRepository.Setup(x => x.GetAvailableProductsAsync())
32 | .Returns(Task.FromResult(products));
33 | var mockUoW = new Mock();
34 | mockUoW.Setup(x => x.ProductRepository).Returns(mockRepository.Object);
35 | var handler = new GetAvailableProductsQueryHandler(mockUoW.Object);
36 |
37 | // Act
38 | var res = await handler.Handle(new GetAvailableProductsQuery(), CancellationToken.None);
39 |
40 | // Assert
41 | Assert.All(res, x =>
42 | {
43 | Assert.True(x.IsAvailable);
44 | Assert.NotEqual(0, x.Quantity);
45 | });
46 | }
47 |
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/After/Handlers/SaveChangesPostProcessor/SaveChangesRequestPostProcessor.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using Infrastructure.Interfaces.Services;
4 | using MediatR.Pipeline;
5 | using Microsoft.EntityFrameworkCore;
6 | using System;
7 | using System.Linq;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 |
11 | namespace Handlers.SaveChangesPostProcessor
12 | {
13 |
14 | public class SaveChangesRequestPostProcessor : IRequestPostProcessor
15 | where TRequest : IChangeDataRequest
16 | {
17 | private readonly IDbContextPostProcessor _dbContext;
18 | private readonly ICurrentUserService _currentUserService;
19 |
20 | public SaveChangesRequestPostProcessor(IDbContextPostProcessor dbContext, ICurrentUserService currentUserService)
21 | {
22 | _dbContext = dbContext;
23 | _currentUserService = currentUserService;
24 | }
25 |
26 | public async Task Process(TRequest request, TResponse response, CancellationToken cancellationToken)
27 | {
28 | var now = DateTime.Now;
29 |
30 | _dbContext.ChangeTracker.Entries().ToList()
31 | .ForEach(x =>
32 | {
33 | if (x.State == EntityState.Added)
34 | {
35 | x.Entity.CreatedBy = _currentUserService.UserId.Value;
36 | x.Entity.CreatedAt = now;
37 | }
38 | if (x.State == EntityState.Modified)
39 | {
40 | x.Entity.ModifiedBy = _currentUserService.UserId.Value;
41 | x.Entity.ModifiedAt = now;
42 | }
43 | });
44 |
45 | await _dbContext.SaveChangesAsync(cancellationToken);
46 | }
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/After/DataAccess.MsSql/AppDbContext.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Infrastructure.Interfaces.DataAccess;
3 | using Infrastructure.Interfaces.Services;
4 | using Microsoft.EntityFrameworkCore;
5 | using System;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace DataAccess.MsSql
10 | {
11 | public class AppDbContext : DbContext, IDbContext
12 | {
13 | private readonly ICurrentUserService _currentUserService;
14 | public AppDbContext(DbContextOptions options, ICurrentUserService currentUserService)
15 | : base(options)
16 | {
17 | _currentUserService = currentUserService;
18 | }
19 | public DbSet Products { get; set; }
20 |
21 | public DbSet Categories { get; set; }
22 |
23 | public DbSet ProductCategories { get; set; }
24 |
25 | public DbSet Users { get; set; }
26 |
27 | public override Task SaveChangesAsync(CancellationToken cancellationToken = default)
28 | {
29 | var now = DateTime.Now;
30 | foreach (var entry in ChangeTracker.Entries())
31 | {
32 | switch (entry.State)
33 | {
34 | case EntityState.Added:
35 | entry.Entity.CreatedBy = _currentUserService.UserId.Value;
36 | entry.Entity.CreatedAt = now;
37 | break;
38 |
39 | case EntityState.Modified:
40 | entry.Entity.ModifiedBy = _currentUserService.UserId.Value;
41 | entry.Entity.ModifiedAt = now;
42 | break;
43 | }
44 | }
45 |
46 | return base.SaveChangesAsync(cancellationToken);
47 | }
48 |
49 | protected override void OnModelCreating(ModelBuilder builder)
50 | {
51 | base.OnModelCreating(builder);
52 |
53 | builder.Entity()
54 | .HasKey(x => new { x.ProductId, x.CategoryId });
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Before/WebHost/Controllers/ProductsController.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Handlers.Products.Commands.DeleteProduct;
3 | using Handlers.Products.Commands.UpdateProduct;
4 | using Handlers.Products.Queries.GetAvailableProducts;
5 | using Handlers.Products.Queries.GetProductsByName;
6 | using MediatR;
7 | using Microsoft.AspNetCore.Http;
8 | using Microsoft.AspNetCore.Mvc;
9 | using System.Collections.Generic;
10 | using System.ComponentModel.DataAnnotations;
11 | using System.Threading.Tasks;
12 |
13 | namespace WebHost.Controllers
14 | {
15 | [ApiController]
16 | [Route("[controller]/[action]")]
17 | public class ProductsController : ControllerBase
18 | {
19 | private IMediator _mediator;
20 |
21 | public ProductsController(IMediator mediator)
22 | {
23 | _mediator = mediator;
24 | }
25 |
26 | [HttpGet]
27 | public async Task> GetAvailableProducts()
28 | {
29 | return await _mediator.Send(new GetAvailableProductsQuery());
30 | }
31 |
32 | [HttpGet]
33 | public async Task> GetProductsByName(string name)
34 | {
35 | return await _mediator.Send(new GetProductsByNameQuery { Name = name });
36 | }
37 |
38 | [HttpPut("{id}")]
39 | [ProducesResponseType(StatusCodes.Status200OK)]
40 | [ProducesResponseType(StatusCodes.Status404NotFound)]
41 | public async Task UpdateProduct(int id, [Required]ProductDto dto)
42 | {
43 | await _mediator.Send(new UpdateProductCommand
44 | {
45 | ProductId = id,
46 | ProductDto = dto
47 | });
48 |
49 | return Ok();
50 | }
51 |
52 | [HttpDelete("{id}")]
53 | [ProducesResponseType(StatusCodes.Status200OK)]
54 | [ProducesResponseType(StatusCodes.Status404NotFound)]
55 | public async Task DeleteProduct(int id)
56 | {
57 | await _mediator.Send(new DeleteProductCommand { ProductId = id });
58 |
59 | return Ok();
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Before/Handlers/Products/Commands/UpdateProduct/UpdateProductCommandHandler.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using Entities;
3 | using Infrastructure.Interfaces.DataAccess;
4 | using MediatR;
5 | using System.Linq;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace Handlers.Products.Commands.UpdateProduct
10 | {
11 | public class UpdateProductCommandHandler : AsyncRequestHandler
12 | {
13 | private readonly IRepositoryUnitOfWork _uow;
14 | private readonly IMapper _mapper;
15 |
16 | public UpdateProductCommandHandler(IRepositoryUnitOfWork uow, IMapper mapper)
17 | {
18 | _uow = uow;
19 | _mapper = mapper;
20 | }
21 |
22 | protected override async Task Handle(UpdateProductCommand request, CancellationToken cancellationToken)
23 | {
24 | var product = await _uow.ProductRepository.GetWithCategoriesAsync(request.ProductId);
25 |
26 | _mapper.Map(request.ProductDto, product); //why we use automapper directly but create an abstraction for entity framework?
27 |
28 | var newCategoryIds = request.ProductDto.CategoryIds;
29 | var currentCategoryIds = product.ProductCategories.Select(x => x.CategoryId).ToList();
30 |
31 | //delete not existing in DTO categories
32 | foreach (var category in product.ProductCategories
33 | .Where(x => !newCategoryIds.Contains(x.CategoryId)).ToList())
34 | {
35 | product.ProductCategories.Remove(category);
36 | }
37 |
38 | //new categories
39 | foreach (var categoryId in newCategoryIds.Except(currentCategoryIds))
40 | {
41 | product.ProductCategories.Add(new ProductCategory
42 | {
43 | CategoryId = categoryId,
44 | ProductId = product.Id
45 | });
46 | }
47 |
48 | _uow.ProductRepository.Update(product); // update ModifiedAt and ModifiedBy properties
49 |
50 | await _uow.SaveChangesAsync();
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/After/Tests/NoRepositoryTests.cs:
--------------------------------------------------------------------------------
1 | using DataAccess.MsSql;
2 | using Entities;
3 | using Handlers.Products.Queries.GetAvailableProducts;
4 | using Infrastructure.Interfaces.DataAccess;
5 | using Infrastructure.Interfaces.Services;
6 | using Microsoft.EntityFrameworkCore;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using Xunit;
12 |
13 | namespace Tests
14 | {
15 | public class NoRepositoryTests
16 | {
17 | class CurrentUserServiceServiceStub : ICurrentUserService
18 | {
19 | private readonly int? _userId;
20 | private readonly bool _isAuthentificated;
21 |
22 | public CurrentUserServiceServiceStub(int? userId, bool isAuthentificated)
23 | {
24 | _userId = userId;
25 | _isAuthentificated = isAuthentificated;
26 | }
27 |
28 | public int? UserId => _userId;
29 |
30 | public bool IsAuthenticated => _isAuthentificated;
31 | }
32 |
33 | [Fact]
34 | public async Task CreateInMemoryDatabase()
35 | {
36 | // Arrange
37 | var options = new DbContextOptionsBuilder()
38 | .UseInMemoryDatabase(databaseName: "in_memory_database")
39 | .Options;
40 | var currentUserService = new CurrentUserServiceServiceStub(1, true);
41 | IDbContext dbContext = new AppDbContext(options, currentUserService);
42 | //we have to create in memory database
43 | dbContext.Products.AddRange(new List
44 | {
45 | new Product { IsAvailable = true, Quantity = 10, Id = 1, Name = "Product1" },
46 | new Product { IsAvailable = true, Quantity = 1, Id = 2, Name = "Product2" },
47 | });
48 | await dbContext.SaveChangesAsync();
49 | var handler = new GetAvailableProductsQueryHandler(dbContext);
50 |
51 | // Act
52 | var products = await handler.Handle(new GetAvailableProductsQuery(), CancellationToken.None);
53 |
54 | // Assert
55 | Assert.All(products, x =>
56 | {
57 | Assert.True(x.IsAvailable);
58 | Assert.NotEqual(0, x.Quantity);
59 | });
60 |
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/After/WebHost/Controllers/ProductsController.cs:
--------------------------------------------------------------------------------
1 | using Entities;
2 | using Handlers.Products.Commands.CreateProduct;
3 | using Handlers.Products.Commands.DeleteProduct;
4 | using Handlers.Products.Commands.Dto;
5 | using Handlers.Products.Commands.UpdateProduct;
6 | using Handlers.Products.Queries.GetAvailableProducts;
7 | using Handlers.Products.Queries.GetProductsByName;
8 | using MediatR;
9 | using Microsoft.AspNetCore.Http;
10 | using Microsoft.AspNetCore.Mvc;
11 | using System.Collections.Generic;
12 | using System.ComponentModel.DataAnnotations;
13 | using System.Threading.Tasks;
14 |
15 | namespace WebHost.Controllers
16 | {
17 | [ApiController]
18 | [Route("[controller]/[action]")]
19 | public class ProductsController : ControllerBase
20 | {
21 | private IMediator _mediator;
22 |
23 | public ProductsController(IMediator mediator)
24 | {
25 | _mediator = mediator;
26 | }
27 |
28 | [HttpGet]
29 | public async Task> GetAvailableProducts()
30 | {
31 | return await _mediator.Send(new GetAvailableProductsQuery());
32 | }
33 |
34 | [HttpGet]
35 | public async Task> GetProductsByName(string name)
36 | {
37 | return await _mediator.Send(new GetProductsByNameQuery { Name = name });
38 | }
39 |
40 | [HttpPost]
41 | [ProducesResponseType(StatusCodes.Status200OK)]
42 | public async Task CreateProduct([FromBody][Required]ProductDto dto)
43 | {
44 | await _mediator.Send(new CreateProductCommand { ProductDto = dto });
45 |
46 | return Ok();
47 | }
48 |
49 | [HttpPut("{id}")]
50 | [ProducesResponseType(StatusCodes.Status200OK)]
51 | [ProducesResponseType(StatusCodes.Status404NotFound)]
52 | public async Task UpdateProduct(int id, [FromBody][Required]ProductDto dto)
53 | {
54 | await _mediator.Send(new UpdateProductCommand
55 | {
56 | ProductId = id,
57 | ProductDto = dto
58 | });
59 |
60 | return Ok();
61 | }
62 |
63 | [HttpDelete("{id}")]
64 | [ProducesResponseType(StatusCodes.Status200OK)]
65 | [ProducesResponseType(StatusCodes.Status404NotFound)]
66 | public async Task DeleteProduct(int id)
67 | {
68 | await _mediator.Send(new DeleteProductCommand { ProductId = id });
69 |
70 | return Ok();
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Before/WebHost/Startup.cs:
--------------------------------------------------------------------------------
1 | using AutoMapper;
2 | using DataAccess.MsSql;
3 | using DataAccess.MsSql.DataAccess;
4 | using DataAccess.MsSql.DataAccess.NoRepository;
5 | using Handlers.Products.Queries.GetProductsByName;
6 | using Infrastructure.Interfaces.DataAccess;
7 | using Infrastructure.Interfaces.DataAccess.NoRepository;
8 | using Infrastructure.Interfaces.QueryableHelpers;
9 | using Infrastructure.Interfaces.Services;
10 | using MediatR;
11 | using Microsoft.AspNetCore.Builder;
12 | using Microsoft.AspNetCore.Hosting;
13 | using Microsoft.EntityFrameworkCore;
14 | using Microsoft.Extensions.Configuration;
15 | using Microsoft.Extensions.DependencyInjection;
16 | using Microsoft.Extensions.Hosting;
17 | using System.Reflection;
18 | using WebHost.Services;
19 |
20 | namespace WebHost
21 | {
22 | public class Startup
23 | {
24 | public Startup(IConfiguration configuration)
25 | {
26 | Configuration = configuration;
27 | }
28 |
29 | public IConfiguration Configuration { get; }
30 |
31 | // This method gets called by the runtime. Use this method to add services to the container.
32 | public void ConfigureServices(IServiceCollection services)
33 | {
34 | services.AddControllers();
35 | services.AddHttpContextAccessor();
36 |
37 | services.AddScoped();
38 |
39 | var queryableExecutor = new EfQueryableExecutor();
40 | services.AddSingleton(queryableExecutor);
41 | QueryableHelper.QueryableExecutor = queryableExecutor;
42 |
43 | services.AddScoped();
44 | services.AddScoped();
45 |
46 | services.AddDbContext(options =>
47 | options.UseSqlServer(Configuration.GetConnectionString("MsSqlConnection")));
48 |
49 | services.AddMediatR(typeof(GetProductsByNameQuery).Assembly);
50 |
51 | services.AddAutoMapper(Assembly.GetExecutingAssembly());
52 | }
53 |
54 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
55 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
56 | {
57 | if (env.IsDevelopment())
58 | {
59 | app.UseDeveloperExceptionPage();
60 | }
61 |
62 | app.UseRouting();
63 |
64 | app.UseAuthorization();
65 |
66 | app.UseEndpoints(endpoints =>
67 | {
68 | endpoints.MapControllers();
69 | });
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/After/WebHost/Startup.cs:
--------------------------------------------------------------------------------
1 | using Autofac;
2 | using AutoMapper;
3 | using DataAccess.MsSql;
4 | using Handlers.Products.Queries.GetProductsByName;
5 | using Handlers.SaveChangesPostProcessor;
6 | using Infrastructure.Interfaces.DataAccess;
7 | using Infrastructure.Interfaces.Services;
8 | using MediatR.Extensions.Autofac.DependencyInjection;
9 | using MediatR.Pipeline;
10 | using Microsoft.AspNetCore.Builder;
11 | using Microsoft.AspNetCore.Hosting;
12 | using Microsoft.EntityFrameworkCore;
13 | using Microsoft.Extensions.Configuration;
14 | using Microsoft.Extensions.DependencyInjection;
15 | using Microsoft.Extensions.Hosting;
16 | using System.Reflection;
17 | using WebHost.Services;
18 |
19 | namespace WebHost
20 | {
21 | public class Startup
22 | {
23 | public Startup(IConfiguration configuration)
24 | {
25 | Configuration = configuration;
26 | }
27 |
28 | public IConfiguration Configuration { get; }
29 |
30 | // This method gets called by the runtime. Use this method to add services to the container.
31 | public void ConfigureServices(IServiceCollection services)
32 | {
33 | services.AddControllers();
34 | services.AddHttpContextAccessor();
35 |
36 | services.AddScoped();
37 |
38 | services.AddScoped(factory => factory.GetRequiredService());
39 | services.AddDbContext(options =>
40 | options.UseSqlServer(Configuration.GetConnectionString("MsSqlConnection")));
41 |
42 | services.AddScoped(factory => factory.GetRequiredService());
43 | services.AddDbContext(options =>
44 | options.UseSqlServer(Configuration.GetConnectionString("MsSqlConnection")));
45 |
46 | services.AddAutoMapper(Assembly.GetExecutingAssembly());
47 | }
48 |
49 | public void ConfigureContainer(ContainerBuilder builder)
50 | {
51 | builder.AddMediatR(typeof(GetProductsByNameQuery).Assembly);
52 |
53 | builder.RegisterGeneric(typeof(SaveChangesRequestPostProcessor<,>)).As(typeof(IRequestPostProcessor<,>));
54 | }
55 |
56 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
57 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
58 | {
59 | if (env.IsDevelopment())
60 | {
61 | app.UseDeveloperExceptionPage();
62 | }
63 |
64 | app.UseRouting();
65 |
66 | app.UseAuthorization();
67 |
68 | app.UseEndpoints(endpoints =>
69 | {
70 | endpoints.MapControllers();
71 | });
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/After/DataAccess.MsSql/Migrations/20200621074859_Initial.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace DataAccess.MsSql.Migrations
5 | {
6 | public partial class Initial : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "Categories",
12 | columns: table => new
13 | {
14 | Id = table.Column(nullable: false)
15 | .Annotation("SqlServer:Identity", "1, 1"),
16 | Name = table.Column(nullable: true),
17 | IsDigital = table.Column(nullable: false)
18 | },
19 | constraints: table =>
20 | {
21 | table.PrimaryKey("PK_Categories", x => x.Id);
22 | });
23 |
24 | migrationBuilder.CreateTable(
25 | name: "Products",
26 | columns: table => new
27 | {
28 | Id = table.Column(nullable: false)
29 | .Annotation("SqlServer:Identity", "1, 1"),
30 | CreatedAt = table.Column(nullable: false),
31 | CreatedBy = table.Column(nullable: false),
32 | ModifiedAt = table.Column(nullable: true),
33 | ModifiedBy = table.Column(nullable: true),
34 | Name = table.Column(nullable: true),
35 | Quantity = table.Column(nullable: false),
36 | IsAvailable = table.Column(nullable: false)
37 | },
38 | constraints: table =>
39 | {
40 | table.PrimaryKey("PK_Products", x => x.Id);
41 | });
42 |
43 | migrationBuilder.CreateTable(
44 | name: "Users",
45 | columns: table => new
46 | {
47 | Id = table.Column(nullable: false)
48 | .Annotation("SqlServer:Identity", "1, 1")
49 | },
50 | constraints: table =>
51 | {
52 | table.PrimaryKey("PK_Users", x => x.Id);
53 | });
54 |
55 | migrationBuilder.CreateTable(
56 | name: "ProductCategories",
57 | columns: table => new
58 | {
59 | ProductId = table.Column(nullable: false),
60 | CategoryId = table.Column(nullable: false)
61 | },
62 | constraints: table =>
63 | {
64 | table.PrimaryKey("PK_ProductCategories", x => new { x.ProductId, x.CategoryId });
65 | table.ForeignKey(
66 | name: "FK_ProductCategories_Categories_CategoryId",
67 | column: x => x.CategoryId,
68 | principalTable: "Categories",
69 | principalColumn: "Id",
70 | onDelete: ReferentialAction.Cascade);
71 | table.ForeignKey(
72 | name: "FK_ProductCategories_Products_ProductId",
73 | column: x => x.ProductId,
74 | principalTable: "Products",
75 | principalColumn: "Id",
76 | onDelete: ReferentialAction.Cascade);
77 | });
78 |
79 | migrationBuilder.CreateIndex(
80 | name: "IX_ProductCategories_CategoryId",
81 | table: "ProductCategories",
82 | column: "CategoryId");
83 | }
84 |
85 | protected override void Down(MigrationBuilder migrationBuilder)
86 | {
87 | migrationBuilder.DropTable(
88 | name: "ProductCategories");
89 |
90 | migrationBuilder.DropTable(
91 | name: "Users");
92 |
93 | migrationBuilder.DropTable(
94 | name: "Categories");
95 |
96 | migrationBuilder.DropTable(
97 | name: "Products");
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/Migrations/20200621075833_Initial.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.EntityFrameworkCore.Migrations;
3 |
4 | namespace DataAccess.MsSql.Migrations
5 | {
6 | public partial class Initial : Migration
7 | {
8 | protected override void Up(MigrationBuilder migrationBuilder)
9 | {
10 | migrationBuilder.CreateTable(
11 | name: "Categories",
12 | columns: table => new
13 | {
14 | Id = table.Column(nullable: false)
15 | .Annotation("SqlServer:Identity", "1, 1"),
16 | Name = table.Column(nullable: true),
17 | IsDigital = table.Column(nullable: false)
18 | },
19 | constraints: table =>
20 | {
21 | table.PrimaryKey("PK_Categories", x => x.Id);
22 | });
23 |
24 | migrationBuilder.CreateTable(
25 | name: "Products",
26 | columns: table => new
27 | {
28 | Id = table.Column(nullable: false)
29 | .Annotation("SqlServer:Identity", "1, 1"),
30 | CreatedAt = table.Column(nullable: false),
31 | CreatedBy = table.Column(nullable: false),
32 | ModifiedAt = table.Column(nullable: true),
33 | ModifiedBy = table.Column(nullable: true),
34 | Name = table.Column(nullable: true),
35 | Quantity = table.Column(nullable: false),
36 | IsAvailable = table.Column(nullable: false)
37 | },
38 | constraints: table =>
39 | {
40 | table.PrimaryKey("PK_Products", x => x.Id);
41 | });
42 |
43 | migrationBuilder.CreateTable(
44 | name: "Users",
45 | columns: table => new
46 | {
47 | Id = table.Column(nullable: false)
48 | .Annotation("SqlServer:Identity", "1, 1")
49 | },
50 | constraints: table =>
51 | {
52 | table.PrimaryKey("PK_Users", x => x.Id);
53 | });
54 |
55 | migrationBuilder.CreateTable(
56 | name: "ProductCategories",
57 | columns: table => new
58 | {
59 | ProductId = table.Column(nullable: false),
60 | CategoryId = table.Column(nullable: false)
61 | },
62 | constraints: table =>
63 | {
64 | table.PrimaryKey("PK_ProductCategories", x => new { x.ProductId, x.CategoryId });
65 | table.ForeignKey(
66 | name: "FK_ProductCategories_Categories_CategoryId",
67 | column: x => x.CategoryId,
68 | principalTable: "Categories",
69 | principalColumn: "Id",
70 | onDelete: ReferentialAction.Cascade);
71 | table.ForeignKey(
72 | name: "FK_ProductCategories_Products_ProductId",
73 | column: x => x.ProductId,
74 | principalTable: "Products",
75 | principalColumn: "Id",
76 | onDelete: ReferentialAction.Cascade);
77 | });
78 |
79 | migrationBuilder.CreateIndex(
80 | name: "IX_ProductCategories_CategoryId",
81 | table: "ProductCategories",
82 | column: "CategoryId");
83 | }
84 |
85 | protected override void Down(MigrationBuilder migrationBuilder)
86 | {
87 | migrationBuilder.DropTable(
88 | name: "ProductCategories");
89 |
90 | migrationBuilder.DropTable(
91 | name: "Users");
92 |
93 | migrationBuilder.DropTable(
94 | name: "Categories");
95 |
96 | migrationBuilder.DropTable(
97 | name: "Products");
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/After/DataAccess.MsSql/Migrations/AppDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using DataAccess.MsSql;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace DataAccess.MsSql.Migrations
10 | {
11 | [DbContext(typeof(AppDbContext))]
12 | partial class AppDbContextModelSnapshot : ModelSnapshot
13 | {
14 | protected override void BuildModel(ModelBuilder modelBuilder)
15 | {
16 | #pragma warning disable 612, 618
17 | modelBuilder
18 | .HasAnnotation("ProductVersion", "3.0.0")
19 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
21 |
22 | modelBuilder.Entity("Entities.Category", b =>
23 | {
24 | b.Property("Id")
25 | .ValueGeneratedOnAdd()
26 | .HasColumnType("int")
27 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
28 |
29 | b.Property("IsDigital")
30 | .HasColumnType("bit");
31 |
32 | b.Property("Name")
33 | .HasColumnType("nvarchar(max)");
34 |
35 | b.HasKey("Id");
36 |
37 | b.ToTable("Categories");
38 | });
39 |
40 | modelBuilder.Entity("Entities.Product", b =>
41 | {
42 | b.Property("Id")
43 | .ValueGeneratedOnAdd()
44 | .HasColumnType("int")
45 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
46 |
47 | b.Property("CreatedAt")
48 | .HasColumnType("datetime2");
49 |
50 | b.Property("CreatedBy")
51 | .HasColumnType("int");
52 |
53 | b.Property("IsAvailable")
54 | .HasColumnType("bit");
55 |
56 | b.Property("ModifiedAt")
57 | .HasColumnType("datetime2");
58 |
59 | b.Property("ModifiedBy")
60 | .HasColumnType("int");
61 |
62 | b.Property("Name")
63 | .HasColumnType("nvarchar(max)");
64 |
65 | b.Property("Quantity")
66 | .HasColumnType("int");
67 |
68 | b.HasKey("Id");
69 |
70 | b.ToTable("Products");
71 | });
72 |
73 | modelBuilder.Entity("Entities.ProductCategory", b =>
74 | {
75 | b.Property("ProductId")
76 | .HasColumnType("int");
77 |
78 | b.Property("CategoryId")
79 | .HasColumnType("int");
80 |
81 | b.HasKey("ProductId", "CategoryId");
82 |
83 | b.HasIndex("CategoryId");
84 |
85 | b.ToTable("ProductCategories");
86 | });
87 |
88 | modelBuilder.Entity("Entities.User", b =>
89 | {
90 | b.Property("Id")
91 | .ValueGeneratedOnAdd()
92 | .HasColumnType("int")
93 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
94 |
95 | b.HasKey("Id");
96 |
97 | b.ToTable("Users");
98 | });
99 |
100 | modelBuilder.Entity("Entities.ProductCategory", b =>
101 | {
102 | b.HasOne("Entities.Category", "Category")
103 | .WithMany()
104 | .HasForeignKey("CategoryId")
105 | .OnDelete(DeleteBehavior.Cascade)
106 | .IsRequired();
107 |
108 | b.HasOne("Entities.Product", "Product")
109 | .WithMany("ProductCategories")
110 | .HasForeignKey("ProductId")
111 | .OnDelete(DeleteBehavior.Cascade)
112 | .IsRequired();
113 | });
114 | #pragma warning restore 612, 618
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/Migrations/AppDbContextModelSnapshot.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using DataAccess.MsSql;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8 |
9 | namespace DataAccess.MsSql.Migrations
10 | {
11 | [DbContext(typeof(AppDbContext))]
12 | partial class AppDbContextModelSnapshot : ModelSnapshot
13 | {
14 | protected override void BuildModel(ModelBuilder modelBuilder)
15 | {
16 | #pragma warning disable 612, 618
17 | modelBuilder
18 | .HasAnnotation("ProductVersion", "3.0.0")
19 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
21 |
22 | modelBuilder.Entity("Entities.Category", b =>
23 | {
24 | b.Property("Id")
25 | .ValueGeneratedOnAdd()
26 | .HasColumnType("int")
27 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
28 |
29 | b.Property("IsDigital")
30 | .HasColumnType("bit");
31 |
32 | b.Property("Name")
33 | .HasColumnType("nvarchar(max)");
34 |
35 | b.HasKey("Id");
36 |
37 | b.ToTable("Categories");
38 | });
39 |
40 | modelBuilder.Entity("Entities.Product", b =>
41 | {
42 | b.Property("Id")
43 | .ValueGeneratedOnAdd()
44 | .HasColumnType("int")
45 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
46 |
47 | b.Property("CreatedAt")
48 | .HasColumnType("datetime2");
49 |
50 | b.Property("CreatedBy")
51 | .HasColumnType("int");
52 |
53 | b.Property("IsAvailable")
54 | .HasColumnType("bit");
55 |
56 | b.Property("ModifiedAt")
57 | .HasColumnType("datetime2");
58 |
59 | b.Property("ModifiedBy")
60 | .HasColumnType("int");
61 |
62 | b.Property("Name")
63 | .HasColumnType("nvarchar(max)");
64 |
65 | b.Property("Quantity")
66 | .HasColumnType("int");
67 |
68 | b.HasKey("Id");
69 |
70 | b.ToTable("Products");
71 | });
72 |
73 | modelBuilder.Entity("Entities.ProductCategory", b =>
74 | {
75 | b.Property("ProductId")
76 | .HasColumnType("int");
77 |
78 | b.Property("CategoryId")
79 | .HasColumnType("int");
80 |
81 | b.HasKey("ProductId", "CategoryId");
82 |
83 | b.HasIndex("CategoryId");
84 |
85 | b.ToTable("ProductCategories");
86 | });
87 |
88 | modelBuilder.Entity("Entities.User", b =>
89 | {
90 | b.Property("Id")
91 | .ValueGeneratedOnAdd()
92 | .HasColumnType("int")
93 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
94 |
95 | b.HasKey("Id");
96 |
97 | b.ToTable("Users");
98 | });
99 |
100 | modelBuilder.Entity("Entities.ProductCategory", b =>
101 | {
102 | b.HasOne("Entities.Category", "Category")
103 | .WithMany()
104 | .HasForeignKey("CategoryId")
105 | .OnDelete(DeleteBehavior.Cascade)
106 | .IsRequired();
107 |
108 | b.HasOne("Entities.Product", "Product")
109 | .WithMany("ProductCategories")
110 | .HasForeignKey("ProductId")
111 | .OnDelete(DeleteBehavior.Cascade)
112 | .IsRequired();
113 | });
114 | #pragma warning restore 612, 618
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/After/DataAccess.MsSql/Migrations/20200621074859_Initial.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using DataAccess.MsSql;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace DataAccess.MsSql.Migrations
11 | {
12 | [DbContext(typeof(AppDbContext))]
13 | [Migration("20200621074859_Initial")]
14 | partial class Initial
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "3.0.0")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("Entities.Category", b =>
25 | {
26 | b.Property("Id")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("int")
29 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
30 |
31 | b.Property("IsDigital")
32 | .HasColumnType("bit");
33 |
34 | b.Property("Name")
35 | .HasColumnType("nvarchar(max)");
36 |
37 | b.HasKey("Id");
38 |
39 | b.ToTable("Categories");
40 | });
41 |
42 | modelBuilder.Entity("Entities.Product", b =>
43 | {
44 | b.Property("Id")
45 | .ValueGeneratedOnAdd()
46 | .HasColumnType("int")
47 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
48 |
49 | b.Property("CreatedAt")
50 | .HasColumnType("datetime2");
51 |
52 | b.Property("CreatedBy")
53 | .HasColumnType("int");
54 |
55 | b.Property("IsAvailable")
56 | .HasColumnType("bit");
57 |
58 | b.Property("ModifiedAt")
59 | .HasColumnType("datetime2");
60 |
61 | b.Property("ModifiedBy")
62 | .HasColumnType("int");
63 |
64 | b.Property("Name")
65 | .HasColumnType("nvarchar(max)");
66 |
67 | b.Property("Quantity")
68 | .HasColumnType("int");
69 |
70 | b.HasKey("Id");
71 |
72 | b.ToTable("Products");
73 | });
74 |
75 | modelBuilder.Entity("Entities.ProductCategory", b =>
76 | {
77 | b.Property("ProductId")
78 | .HasColumnType("int");
79 |
80 | b.Property("CategoryId")
81 | .HasColumnType("int");
82 |
83 | b.HasKey("ProductId", "CategoryId");
84 |
85 | b.HasIndex("CategoryId");
86 |
87 | b.ToTable("ProductCategories");
88 | });
89 |
90 | modelBuilder.Entity("Entities.User", b =>
91 | {
92 | b.Property("Id")
93 | .ValueGeneratedOnAdd()
94 | .HasColumnType("int")
95 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
96 |
97 | b.HasKey("Id");
98 |
99 | b.ToTable("Users");
100 | });
101 |
102 | modelBuilder.Entity("Entities.ProductCategory", b =>
103 | {
104 | b.HasOne("Entities.Category", "Category")
105 | .WithMany()
106 | .HasForeignKey("CategoryId")
107 | .OnDelete(DeleteBehavior.Cascade)
108 | .IsRequired();
109 |
110 | b.HasOne("Entities.Product", "Product")
111 | .WithMany("ProductCategories")
112 | .HasForeignKey("ProductId")
113 | .OnDelete(DeleteBehavior.Cascade)
114 | .IsRequired();
115 | });
116 | #pragma warning restore 612, 618
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Before/DataAccess.MsSql/Migrations/20200621075833_Initial.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | using System;
3 | using DataAccess.MsSql;
4 | using Microsoft.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore.Infrastructure;
6 | using Microsoft.EntityFrameworkCore.Metadata;
7 | using Microsoft.EntityFrameworkCore.Migrations;
8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9 |
10 | namespace DataAccess.MsSql.Migrations
11 | {
12 | [DbContext(typeof(AppDbContext))]
13 | [Migration("20200621075833_Initial")]
14 | partial class Initial
15 | {
16 | protected override void BuildTargetModel(ModelBuilder modelBuilder)
17 | {
18 | #pragma warning disable 612, 618
19 | modelBuilder
20 | .HasAnnotation("ProductVersion", "3.0.0")
21 | .HasAnnotation("Relational:MaxIdentifierLength", 128)
22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
23 |
24 | modelBuilder.Entity("Entities.Category", b =>
25 | {
26 | b.Property("Id")
27 | .ValueGeneratedOnAdd()
28 | .HasColumnType("int")
29 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
30 |
31 | b.Property("IsDigital")
32 | .HasColumnType("bit");
33 |
34 | b.Property("Name")
35 | .HasColumnType("nvarchar(max)");
36 |
37 | b.HasKey("Id");
38 |
39 | b.ToTable("Categories");
40 | });
41 |
42 | modelBuilder.Entity("Entities.Product", b =>
43 | {
44 | b.Property("Id")
45 | .ValueGeneratedOnAdd()
46 | .HasColumnType("int")
47 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
48 |
49 | b.Property("CreatedAt")
50 | .HasColumnType("datetime2");
51 |
52 | b.Property("CreatedBy")
53 | .HasColumnType("int");
54 |
55 | b.Property("IsAvailable")
56 | .HasColumnType("bit");
57 |
58 | b.Property("ModifiedAt")
59 | .HasColumnType("datetime2");
60 |
61 | b.Property("ModifiedBy")
62 | .HasColumnType("int");
63 |
64 | b.Property("Name")
65 | .HasColumnType("nvarchar(max)");
66 |
67 | b.Property("Quantity")
68 | .HasColumnType("int");
69 |
70 | b.HasKey("Id");
71 |
72 | b.ToTable("Products");
73 | });
74 |
75 | modelBuilder.Entity("Entities.ProductCategory", b =>
76 | {
77 | b.Property("ProductId")
78 | .HasColumnType("int");
79 |
80 | b.Property("CategoryId")
81 | .HasColumnType("int");
82 |
83 | b.HasKey("ProductId", "CategoryId");
84 |
85 | b.HasIndex("CategoryId");
86 |
87 | b.ToTable("ProductCategories");
88 | });
89 |
90 | modelBuilder.Entity("Entities.User", b =>
91 | {
92 | b.Property("Id")
93 | .ValueGeneratedOnAdd()
94 | .HasColumnType("int")
95 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
96 |
97 | b.HasKey("Id");
98 |
99 | b.ToTable("Users");
100 | });
101 |
102 | modelBuilder.Entity("Entities.ProductCategory", b =>
103 | {
104 | b.HasOne("Entities.Category", "Category")
105 | .WithMany()
106 | .HasForeignKey("CategoryId")
107 | .OnDelete(DeleteBehavior.Cascade)
108 | .IsRequired();
109 |
110 | b.HasOne("Entities.Product", "Product")
111 | .WithMany("ProductCategories")
112 | .HasForeignKey("ProductId")
113 | .OnDelete(DeleteBehavior.Cascade)
114 | .IsRequired();
115 | });
116 | #pragma warning restore 612, 618
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/DataAccessWithoutRepositoryAndUnitOfWork.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29503.13
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Before", "Before", "{86AA0D66-AD74-4C36-B634-8354848CE897}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "After", "After", "{75DDC325-903B-4515-911F-9EA18ADA2813}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebHost", "Before\WebHost\WebHost.csproj", "{8EA152DE-4692-4C6B-80B7-189B1E5E90F9}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Entities", "Before\Entities\Entities.csproj", "{B3755640-9814-4406-A2CE-D0648D342910}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Handlers", "Before\Handlers\Handlers.csproj", "{B0D09000-EC7A-4451-90BF-91533C1D9C5B}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.Interfaces", "Before\Infrastructure.Interfaces\Infrastructure.Interfaces.csproj", "{CA17227C-ADFC-451D-90C5-07194B88B233}"
17 | EndProject
18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataAccess.MsSql", "Before\DataAccess.MsSql\DataAccess.MsSql.csproj", "{371F463C-CCC2-4A08-8ACB-09472269D599}"
19 | EndProject
20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Before\Tests\Tests.csproj", "{4B7C66AE-78AE-4626-B68D-074477B29DE4}"
21 | EndProject
22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Entities", "After\Entities\Entities.csproj", "{4477200F-B00F-43A4-BFEC-41432E0CC0D7}"
23 | EndProject
24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Handlers", "After\Handlers\Handlers.csproj", "{80075F3A-103B-4086-9018-B589B5354200}"
25 | EndProject
26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataAccess.MsSql", "After\DataAccess.MsSql\DataAccess.MsSql.csproj", "{6F1A4653-27C5-416E-8EAA-04E9145B0D37}"
27 | EndProject
28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure.Interfaces", "After\Infrastructure.Interfaces\Infrastructure.Interfaces.csproj", "{938B8F27-5041-41E7-907D-838A718CFD0F}"
29 | EndProject
30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "After\Tests\Tests.csproj", "{17B5C0CE-E1F4-4225-BF68-C69399B465A8}"
31 | EndProject
32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebHost", "After\WebHost\WebHost.csproj", "{711D059A-147F-4240-B4D8-272771B27D2A}"
33 | EndProject
34 | Global
35 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
36 | Debug|Any CPU = Debug|Any CPU
37 | Release|Any CPU = Release|Any CPU
38 | EndGlobalSection
39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
40 | {8EA152DE-4692-4C6B-80B7-189B1E5E90F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {8EA152DE-4692-4C6B-80B7-189B1E5E90F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {8EA152DE-4692-4C6B-80B7-189B1E5E90F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {8EA152DE-4692-4C6B-80B7-189B1E5E90F9}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {B3755640-9814-4406-A2CE-D0648D342910}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {B3755640-9814-4406-A2CE-D0648D342910}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {B3755640-9814-4406-A2CE-D0648D342910}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {B3755640-9814-4406-A2CE-D0648D342910}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {B0D09000-EC7A-4451-90BF-91533C1D9C5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {B0D09000-EC7A-4451-90BF-91533C1D9C5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {B0D09000-EC7A-4451-90BF-91533C1D9C5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {B0D09000-EC7A-4451-90BF-91533C1D9C5B}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {CA17227C-ADFC-451D-90C5-07194B88B233}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
53 | {CA17227C-ADFC-451D-90C5-07194B88B233}.Debug|Any CPU.Build.0 = Debug|Any CPU
54 | {CA17227C-ADFC-451D-90C5-07194B88B233}.Release|Any CPU.ActiveCfg = Release|Any CPU
55 | {CA17227C-ADFC-451D-90C5-07194B88B233}.Release|Any CPU.Build.0 = Release|Any CPU
56 | {371F463C-CCC2-4A08-8ACB-09472269D599}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57 | {371F463C-CCC2-4A08-8ACB-09472269D599}.Debug|Any CPU.Build.0 = Debug|Any CPU
58 | {371F463C-CCC2-4A08-8ACB-09472269D599}.Release|Any CPU.ActiveCfg = Release|Any CPU
59 | {371F463C-CCC2-4A08-8ACB-09472269D599}.Release|Any CPU.Build.0 = Release|Any CPU
60 | {4B7C66AE-78AE-4626-B68D-074477B29DE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61 | {4B7C66AE-78AE-4626-B68D-074477B29DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
62 | {4B7C66AE-78AE-4626-B68D-074477B29DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 | {4B7C66AE-78AE-4626-B68D-074477B29DE4}.Release|Any CPU.Build.0 = Release|Any CPU
64 | {4477200F-B00F-43A4-BFEC-41432E0CC0D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
65 | {4477200F-B00F-43A4-BFEC-41432E0CC0D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
66 | {4477200F-B00F-43A4-BFEC-41432E0CC0D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
67 | {4477200F-B00F-43A4-BFEC-41432E0CC0D7}.Release|Any CPU.Build.0 = Release|Any CPU
68 | {80075F3A-103B-4086-9018-B589B5354200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
69 | {80075F3A-103B-4086-9018-B589B5354200}.Debug|Any CPU.Build.0 = Debug|Any CPU
70 | {80075F3A-103B-4086-9018-B589B5354200}.Release|Any CPU.ActiveCfg = Release|Any CPU
71 | {80075F3A-103B-4086-9018-B589B5354200}.Release|Any CPU.Build.0 = Release|Any CPU
72 | {6F1A4653-27C5-416E-8EAA-04E9145B0D37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
73 | {6F1A4653-27C5-416E-8EAA-04E9145B0D37}.Debug|Any CPU.Build.0 = Debug|Any CPU
74 | {6F1A4653-27C5-416E-8EAA-04E9145B0D37}.Release|Any CPU.ActiveCfg = Release|Any CPU
75 | {6F1A4653-27C5-416E-8EAA-04E9145B0D37}.Release|Any CPU.Build.0 = Release|Any CPU
76 | {938B8F27-5041-41E7-907D-838A718CFD0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
77 | {938B8F27-5041-41E7-907D-838A718CFD0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
78 | {938B8F27-5041-41E7-907D-838A718CFD0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
79 | {938B8F27-5041-41E7-907D-838A718CFD0F}.Release|Any CPU.Build.0 = Release|Any CPU
80 | {17B5C0CE-E1F4-4225-BF68-C69399B465A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
81 | {17B5C0CE-E1F4-4225-BF68-C69399B465A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
82 | {17B5C0CE-E1F4-4225-BF68-C69399B465A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
83 | {17B5C0CE-E1F4-4225-BF68-C69399B465A8}.Release|Any CPU.Build.0 = Release|Any CPU
84 | {711D059A-147F-4240-B4D8-272771B27D2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
85 | {711D059A-147F-4240-B4D8-272771B27D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
86 | {711D059A-147F-4240-B4D8-272771B27D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
87 | {711D059A-147F-4240-B4D8-272771B27D2A}.Release|Any CPU.Build.0 = Release|Any CPU
88 | EndGlobalSection
89 | GlobalSection(SolutionProperties) = preSolution
90 | HideSolutionNode = FALSE
91 | EndGlobalSection
92 | GlobalSection(NestedProjects) = preSolution
93 | {8EA152DE-4692-4C6B-80B7-189B1E5E90F9} = {86AA0D66-AD74-4C36-B634-8354848CE897}
94 | {B3755640-9814-4406-A2CE-D0648D342910} = {86AA0D66-AD74-4C36-B634-8354848CE897}
95 | {B0D09000-EC7A-4451-90BF-91533C1D9C5B} = {86AA0D66-AD74-4C36-B634-8354848CE897}
96 | {CA17227C-ADFC-451D-90C5-07194B88B233} = {86AA0D66-AD74-4C36-B634-8354848CE897}
97 | {371F463C-CCC2-4A08-8ACB-09472269D599} = {86AA0D66-AD74-4C36-B634-8354848CE897}
98 | {4B7C66AE-78AE-4626-B68D-074477B29DE4} = {86AA0D66-AD74-4C36-B634-8354848CE897}
99 | {4477200F-B00F-43A4-BFEC-41432E0CC0D7} = {75DDC325-903B-4515-911F-9EA18ADA2813}
100 | {80075F3A-103B-4086-9018-B589B5354200} = {75DDC325-903B-4515-911F-9EA18ADA2813}
101 | {6F1A4653-27C5-416E-8EAA-04E9145B0D37} = {75DDC325-903B-4515-911F-9EA18ADA2813}
102 | {938B8F27-5041-41E7-907D-838A718CFD0F} = {75DDC325-903B-4515-911F-9EA18ADA2813}
103 | {17B5C0CE-E1F4-4225-BF68-C69399B465A8} = {75DDC325-903B-4515-911F-9EA18ADA2813}
104 | {711D059A-147F-4240-B4D8-272771B27D2A} = {75DDC325-903B-4515-911F-9EA18ADA2813}
105 | EndGlobalSection
106 | GlobalSection(ExtensibilityGlobals) = postSolution
107 | SolutionGuid = {282A7A08-B3BF-418B-944D-7FD4315EC52D}
108 | EndGlobalSection
109 | EndGlobal
110 |
--------------------------------------------------------------------------------