ProcessAsync(string message)
+ {
+ return await Task.FromResult(true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Pages/Error.cshtml b/CQRS_Simple.API/Pages/Error.cshtml
new file mode 100644
index 0000000..6f92b95
--- /dev/null
+++ b/CQRS_Simple.API/Pages/Error.cshtml
@@ -0,0 +1,26 @@
+@page
+@model ErrorModel
+@{
+ ViewData["Title"] = "Error";
+}
+
+Error.
+An error occurred while processing your request.
+
+@if (Model.ShowRequestId)
+{
+
+ Request ID: @Model.RequestId
+
+}
+
+Development Mode
+
+ Swapping to the Development environment displays detailed information about the error that occurred.
+
+
+ The Development environment shouldn't be enabled for deployed applications.
+ It can result in displaying sensitive information from exceptions to end users.
+ For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
+ and restarting the app.
+
diff --git a/CQRS_Simple.API/Pages/Error.cshtml.cs b/CQRS_Simple.API/Pages/Error.cshtml.cs
new file mode 100644
index 0000000..e3f3ef7
--- /dev/null
+++ b/CQRS_Simple.API/Pages/Error.cshtml.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace CQRS_Simple.Pages
+{
+ [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
+ public class ErrorModel : PageModel
+ {
+ private readonly ILogger _logger;
+
+ public ErrorModel(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public string RequestId { get; set; }
+
+ public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
+
+ public void OnGet()
+ {
+ RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
+ }
+ }
+}
diff --git a/CQRS_Simple.API/Pages/_ViewImports.cshtml b/CQRS_Simple.API/Pages/_ViewImports.cshtml
new file mode 100644
index 0000000..c3a0c76
--- /dev/null
+++ b/CQRS_Simple.API/Pages/_ViewImports.cshtml
@@ -0,0 +1,3 @@
+@using CQRS_Simple
+@namespace CQRS_Simple.Pages
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/CQRS_Simple.API/PipelineBehaviors/ValidationBehavior.cs b/CQRS_Simple.API/PipelineBehaviors/ValidationBehavior.cs
new file mode 100644
index 0000000..277a51b
--- /dev/null
+++ b/CQRS_Simple.API/PipelineBehaviors/ValidationBehavior.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentValidation;
+using MediatR;
+
+namespace CQRS_Simple.API.PipelineBehaviors
+{
+ public class ValidationBehavior : IPipelineBehavior
+ where TRequest : IRequest
+ {
+ public readonly IEnumerable> _validators;
+
+ public ValidationBehavior(IEnumerable> validators)
+ {
+ _validators = validators;
+ }
+
+ public Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next)
+ {
+ var context = new ValidationContext(request);
+ var failures = _validators
+ .Select(v => v.Validate(context))
+ .SelectMany(result => result.Errors)
+ .Where(f => f != null)
+ .ToList();
+
+ if (failures.Count != 0)
+ {
+ throw new ValidationException(failures);
+ }
+
+ return next();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/Commands/CreateProductCommand.cs b/CQRS_Simple.API/Products/Commands/CreateProductCommand.cs
new file mode 100644
index 0000000..c8379e4
--- /dev/null
+++ b/CQRS_Simple.API/Products/Commands/CreateProductCommand.cs
@@ -0,0 +1,53 @@
+using System.Threading;
+using System.Threading.Tasks;
+using CQRS_Simple.API.Products.Handlers;
+using CQRS_Simple.Domain.Products;
+using CQRS_Simple.Infrastructure.Dapper;
+using CQRS_Simple.Infrastructure.MQ;
+using CQRS_Simple.Infrastructure.Uow;
+using FluentValidation;
+using MediatR;
+
+namespace CQRS_Simple.API.Products.Commands
+{
+ public class CreateProductCommandValidate : AbstractValidator
+ {
+ public CreateProductCommandValidate()
+ {
+ RuleFor(x => x.Product.Code).Length(10, 256);
+ }
+ }
+
+
+ public class CreateProductCommand : IRequest
+ {
+ public Product Product { get; set; }
+
+ public CreateProductCommand(Product product)
+ {
+ Product = product;
+ }
+
+ public class CreateProductHandle : IRequestHandler
+ {
+ //private readonly IDapperRepository _dapperRepository;
+ private readonly IRepository _repository;
+ private readonly RabbitMQClient _mq;
+
+ public CreateProductHandle(RabbitMQClient mq, IRepository repository)
+ {
+ _mq = mq;
+ _repository = repository;
+ }
+
+ public async Task Handle(CreateProductCommand request, CancellationToken cancellationToken)
+ {
+ _repository.Add(request.Product);
+ var result = 1;
+ _mq.PushMessage(new RabbitData(typeof(CreateProductCommand), request, result));
+
+ return result;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/Commands/DeleteProductCommand.cs b/CQRS_Simple.API/Products/Commands/DeleteProductCommand.cs
new file mode 100644
index 0000000..bb44f2a
--- /dev/null
+++ b/CQRS_Simple.API/Products/Commands/DeleteProductCommand.cs
@@ -0,0 +1,11 @@
+using MediatR;
+
+namespace CQRS_Simple.API.Products.Commands
+{
+ public class DeleteProductCommand : IRequest
+ {
+ public int ProductId { get; set; }
+
+ public DeleteProductCommand(in int id) { ProductId = id; }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/Commands/UpdateProductCommand.cs b/CQRS_Simple.API/Products/Commands/UpdateProductCommand.cs
new file mode 100644
index 0000000..6840670
--- /dev/null
+++ b/CQRS_Simple.API/Products/Commands/UpdateProductCommand.cs
@@ -0,0 +1,11 @@
+using CQRS_Simple.Domain.Products;
+using MediatR;
+
+namespace CQRS_Simple.API.Products.Commands
+{
+ public class UpdateProductCommand : IRequest
+ {
+ public Product Product { get; set; }
+ public UpdateProductCommand(Product product) { Product = product; }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/Dtos/ProductDto.cs b/CQRS_Simple.API/Products/Dtos/ProductDto.cs
new file mode 100644
index 0000000..b8de415
--- /dev/null
+++ b/CQRS_Simple.API/Products/Dtos/ProductDto.cs
@@ -0,0 +1,16 @@
+using CQRS_Simple.Domain.Products;
+
+namespace CQRS_Simple.API.Products.Dtos
+{
+ ///
+ ///
+ ///
+ public class ProductDto
+ {
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string Code { get; set; }
+
+ public string Description { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/Handlers/DeleteProductHandle.cs b/CQRS_Simple.API/Products/Handlers/DeleteProductHandle.cs
new file mode 100644
index 0000000..3de0e82
--- /dev/null
+++ b/CQRS_Simple.API/Products/Handlers/DeleteProductHandle.cs
@@ -0,0 +1,30 @@
+using System.Threading;
+using System.Threading.Tasks;
+using CQRS_Simple.API.Products.Commands;
+using CQRS_Simple.Domain.Products;
+using CQRS_Simple.Infrastructure.Dapper;
+using CQRS_Simple.Infrastructure.MQ;
+using MediatR;
+
+namespace CQRS_Simple.API.Products.Handlers
+{
+ public class DeleteProductHandle : IRequestHandler
+ {
+ private readonly IDapperRepository _dapperRepository;
+ private readonly RabbitMQClient _mq;
+
+ public DeleteProductHandle(IDapperRepository dapperRepository, RabbitMQClient mq)
+ {
+ _dapperRepository = dapperRepository;
+ _mq = mq;
+ }
+
+ public async Task Handle(DeleteProductCommand request, CancellationToken cancellationToken)
+ {
+ var result = await _dapperRepository.RemoveAsync(new Product() { Id = request.ProductId });
+ if (result > 0)
+ _mq.PushMessage(new RabbitData(typeof(DeleteProductCommand), request));
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/Handlers/GetProductHandle.cs b/CQRS_Simple.API/Products/Handlers/GetProductHandle.cs
new file mode 100644
index 0000000..35d7787
--- /dev/null
+++ b/CQRS_Simple.API/Products/Handlers/GetProductHandle.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Autofac;
+using AutoMapper;
+using CQRS_Simple.API.Products.Dtos;
+using CQRS_Simple.API.Products.Queries;
+using CQRS_Simple.Domain.Products;
+using CQRS_Simple.Infrastructure.Uow;
+using MediatR;
+
+namespace CQRS_Simple.API.Products.Handlers
+{
+ public class GetProductsQueryHandle : IRequestHandler,
+ IRequestHandler>
+ {
+ // private readonly IDapperRepository _dapperRepository;
+ private readonly IRepository _dapperRepository;
+ private readonly IMapper _mapper;
+ private readonly ILifetimeScope _container;
+
+ public GetProductsQueryHandle(IRepository dapperRepository,
+ IMapper mapper,
+ ILifetimeScope container
+ )
+ {
+ _dapperRepository = dapperRepository;
+ _mapper = mapper;
+ _container = container;
+ }
+
+ public virtual async Task Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
+ {
+ var result = await _dapperRepository.GetByIdAsync(request.ProductId);
+
+ var repository = _container.Resolve>();
+ // var _repository = _iocManager.GetInstance>();
+
+ repository.UnitOfWork.PrintKey();
+
+ var s = await repository.GetByIdAsync(request.ProductId);
+ s.Name += "2";
+
+ return result == null ? null : _mapper.Map(result);
+ }
+
+ public async Task> Handle(GetProductsQuery request, CancellationToken cancellationToken)
+ {
+ var result = await _dapperRepository.GetAllAsync();
+
+ return result == null ? new List() : _mapper.Map>(result);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/Handlers/UpdateProductHandle.cs b/CQRS_Simple.API/Products/Handlers/UpdateProductHandle.cs
new file mode 100644
index 0000000..1b3d44e
--- /dev/null
+++ b/CQRS_Simple.API/Products/Handlers/UpdateProductHandle.cs
@@ -0,0 +1,30 @@
+using System.Threading;
+using System.Threading.Tasks;
+using CQRS_Simple.API.Products.Commands;
+using CQRS_Simple.Domain.Products;
+using CQRS_Simple.Infrastructure.Dapper;
+using CQRS_Simple.Infrastructure.MQ;
+using MediatR;
+
+namespace CQRS_Simple.API.Products.Handlers
+{
+ public class UpdateProductHandle : IRequestHandler
+ {
+ private readonly IDapperRepository _dapperRepository;
+ private readonly RabbitMQClient _mq;
+
+ public UpdateProductHandle(IDapperRepository dapperRepository, RabbitMQClient mq)
+ {
+ _dapperRepository = dapperRepository;
+ _mq = mq;
+ }
+
+ public async Task Handle(UpdateProductCommand request, CancellationToken cancellationToken)
+ {
+ var result = await _dapperRepository.UpdateAsync(request.Product);
+ if (result > 0)
+ _mq.PushMessage(new RabbitData(typeof(UpdateProductCommand), request));
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/ProductsController.cs b/CQRS_Simple.API/Products/ProductsController.cs
new file mode 100644
index 0000000..bca6036
--- /dev/null
+++ b/CQRS_Simple.API/Products/ProductsController.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Autofac;
+using CQRS_Simple.API.Products.Commands;
+using CQRS_Simple.API.Products.Queries;
+using CQRS_Simple.Domain.Products;
+using CQRS_Simple.Domain.Products.Request;
+using CQRS_Simple.Infrastructure;
+using CQRS_Simple.Infrastructure.Uow;
+using MediatR;
+using Microsoft.AspNetCore.Mvc;
+using Serilog;
+
+namespace CQRS_Simple.API.Products
+{
+ [ApiController]
+ [Route("api/products")]
+ public class ProductsController : ControllerBase
+ {
+ private readonly IMediator _mediator;
+ private readonly IIocManager _iocManager;
+ private readonly IUnitOfWork _unitOfWork;
+
+ public ProductsController(IMediator mediator, IIocManager iocManager, IUnitOfWork unitOfWork)
+ {
+ _mediator = mediator;
+ _iocManager = iocManager;
+ _unitOfWork = unitOfWork;
+ }
+
+ [HttpGet]
+ [Route("GetProduct")]
+ public async Task GetProduct(int id)
+ {
+ var result = await _mediator.Send(new GetProductByIdQuery(id));
+
+ var _r1 = _unitOfWork.GetRepository();
+ _r1.UnitOfWork.PrintKey();
+
+
+ var _repository = _iocManager.GetInstance>();
+ _repository.UnitOfWork.PrintKey();
+
+
+ var _repository2 = _iocManager.GetInstance>();
+ _repository2.UnitOfWork.PrintKey();
+
+
+ var find = await _repository.GetByIdAsync(id);
+
+ if (find != null)
+ {
+ find.Name += "1_";
+ Log.Information(find?.Name);
+ await _unitOfWork.SaveChangesAsync();
+ }
+
+ // throw new Exception("ss");
+
+ return result != null ? (IActionResult)Ok(result) : NotFound();
+ }
+
+ [HttpGet]
+ [Route("GetAll")]
+ public async Task GetAll([FromQuery] ProductsRequestInput input)
+ {
+ var list = await _mediator.Send(new GetProductsQuery(input));
+
+ Debugger.Break();
+
+ return Ok(list);
+ }
+
+ [HttpPost]
+ [Route("Create")]
+ public async Task Create([FromBody]Product input)
+ {
+ var result = await _mediator.Send(new CreateProductCommand(input));
+ return result > 0 ? (IActionResult)Ok(result) : BadRequest();
+ }
+
+ [HttpDelete]
+ [Route("Delete/{id}")]
+ public async Task Delete(int id)
+ {
+ var count = await _mediator.Send(new DeleteProductCommand(id));
+ return count > 0 ? (IActionResult)Ok() : NotFound();
+ }
+
+ [HttpPut]
+ [Route("Update")]
+ public async Task Update([FromBody]Product input)
+ {
+ var count = await _mediator.Send(new UpdateProductCommand(input));
+ return count > 0 ? (IActionResult)Ok() : NotFound();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/Queries/GetProductByIdQuery.cs b/CQRS_Simple.API/Products/Queries/GetProductByIdQuery.cs
new file mode 100644
index 0000000..f4e74b5
--- /dev/null
+++ b/CQRS_Simple.API/Products/Queries/GetProductByIdQuery.cs
@@ -0,0 +1,15 @@
+using CQRS_Simple.API.Products.Dtos;
+using MediatR;
+
+namespace CQRS_Simple.API.Products.Queries
+{
+ public class GetProductByIdQuery : IRequest
+ {
+ public int ProductId { get; set; }
+
+ public GetProductByIdQuery(int productId)
+ {
+ ProductId = productId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/Queries/GetProductsQuery.cs b/CQRS_Simple.API/Products/Queries/GetProductsQuery.cs
new file mode 100644
index 0000000..e93777c
--- /dev/null
+++ b/CQRS_Simple.API/Products/Queries/GetProductsQuery.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using CQRS_Simple.API.Products.Dtos;
+using CQRS_Simple.Domain.Products.Request;
+using MediatR;
+
+namespace CQRS_Simple.API.Products.Queries
+{
+ public class GetProductsQuery : IRequest>
+ {
+ public ProductsRequestInput Input { get; set; }
+
+ public GetProductsQuery(ProductsRequestInput input)
+ {
+ Input = input;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Products/Validation/CreateProductCommandValidator.cs b/CQRS_Simple.API/Products/Validation/CreateProductCommandValidator.cs
new file mode 100644
index 0000000..f8b0ec1
--- /dev/null
+++ b/CQRS_Simple.API/Products/Validation/CreateProductCommandValidator.cs
@@ -0,0 +1,20 @@
+using CQRS_Simple.API.Products.Commands;
+using CQRS_Simple.Domain.Products;
+using CQRS_Simple.Infrastructure.Dapper;
+using FluentValidation;
+
+namespace CQRS_Simple.API.Products.Validation
+{
+ public class CreateProductCommandValidator : AbstractValidator
+ {
+ private readonly IDapperRepository _productDapperRepository;
+ public CreateProductCommandValidator(IDapperRepository productDapperRepository)
+ {
+ _productDapperRepository = productDapperRepository;
+
+ RuleFor(x => x.Product).NotNull();
+
+ RuleFor(x => x.Product.Id).Equal(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/Program.cs b/CQRS_Simple.API/Program.cs
new file mode 100644
index 0000000..a56e2b1
--- /dev/null
+++ b/CQRS_Simple.API/Program.cs
@@ -0,0 +1,54 @@
+using System;
+using System.IO;
+using Autofac.Extensions.DependencyInjection;
+using CQRS_Simple.API;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+using Serilog;
+using Serilog.Formatting.Compact;
+
+namespace CQRS_Simple
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ Log.Logger = new LoggerConfiguration()
+#if DEBUG
+ .MinimumLevel.Debug()
+#else
+ .MinimumLevel.Information()
+#endif
+ .Enrich.FromLogContext()
+ .WriteTo.Console()
+// .WriteTo.File(new RenderedCompactJsonFormatter(), "/logs/log.json")
+ .CreateLogger();
+
+ try
+ {
+ Log.Information("Starting up");
+ CreateHostBuilder(args).Build().Run();
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "Application start-up failed");
+ }
+ finally
+ {
+ Log.CloseAndFlush();
+ }
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .UseSerilog()
+ .UseServiceProviderFactory(new AutofacServiceProviderFactory())
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder
+ .UseContentRoot(Directory.GetCurrentDirectory())
+ .UseIISIntegration()
+ .UseStartup();
+ });
+ }
+}
diff --git a/CQRS_Simple.API/Properties/launchSettings.json b/CQRS_Simple.API/Properties/launchSettings.json
new file mode 100644
index 0000000..06f2b1c
--- /dev/null
+++ b/CQRS_Simple.API/Properties/launchSettings.json
@@ -0,0 +1,28 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:21020",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "CQRS_Simple": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger/index.html",
+ "applicationUrl": "http://localhost:21020",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/CQRS_Simple.API/Startup.cs b/CQRS_Simple.API/Startup.cs
new file mode 100644
index 0000000..d7ba396
--- /dev/null
+++ b/CQRS_Simple.API/Startup.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Reflection;
+using Autofac;
+using Autofac.Extensions.DependencyInjection;
+using CQRS_Simple.API.Modules;
+using CQRS_Simple.Domain.Products.Request;
+using CQRS_Simple.EntityFrameworkCore;
+using CQRS_Simple.Infrastructure;
+using CQRS_Simple.Infrastructure.MQ;
+using CQRS_Simple.Modules;
+using FluentValidation.AspNetCore;
+using MediatR.Extensions.FluentValidation.AspNetCore;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.OpenApi.Models;
+using Newtonsoft.Json.Serialization;
+
+namespace CQRS_Simple.API
+{
+ public class Startup
+ {
+ public IConfigurationRoot _configuration { get; }
+ public ILifetimeScope AutofacContainer { get; private set; }
+
+ public ILoggerFactory _LoggerFactory { get; private set; }
+
+ private const string SqlServerConnection = "ConnectionStrings:Default";
+ private const string MysqlConnection = "ConnectionStrings:Mysql";
+
+
+ public Startup(IWebHostEnvironment env)
+ {
+ var builder = new ConfigurationBuilder()
+ .SetBasePath(env.ContentRootPath)
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
+ .AddEnvironmentVariables();
+
+ _configuration = builder.Build();
+ }
+
+ // ConfigureServices is where you register dependencies. This gets
+ // called by the runtime before the ConfigureContainer method, below.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddDbContext(options =>
+ options.UseMySql(_configuration[MysqlConnection], ServerVersion.Parse("5.7.31-mysql")));
+
+
+ //options.UseSqlServer(_configuration[SqlServerConnection]));
+
+ services.Configure(_configuration.GetSection("RabbitMQ"));
+
+ //services.AddHostedService();
+
+ services.AddControllersWithViews(option =>
+ {
+ option.AllowEmptyInputInBodyModelBinding = true; // false as Default
+ })
+ .AddNewtonsoftJson(c => { c.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); })
+
+ // 注入Api参数的FluentValidation
+ .AddFluentValidation(c => c.RegisterValidatorsFromAssembly(typeof(ProductValidator).Assembly))
+ ;
+
+ // MediatR的FluentValidation 效果和ValidationBehavior一样,取其一
+ // services.AddFluentValidation(new[]
+ // {
+ // typeof(Startup).GetTypeInfo().Assembly,
+ // typeof(ProductsRequestInput).GetTypeInfo().Assembly
+ // });
+
+
+ AddSwagger(services);
+ }
+
+ // ConfigureContainer is where you can register things directly
+ // with Autofac. This runs after ConfigureServices so the things
+ // here will override registrations made in ConfigureServices.
+ // Don't build the container; that gets done for you by the factory.
+ public void ConfigureContainer(ContainerBuilder builder)
+ {
+ // 类型注入
+ builder.Register(c => new CallLogger(Console.Out));
+
+ builder.RegisterModule(new IocManagerModule());
+
+ builder.RegisterModule(new LoggerModule());
+
+ builder.RegisterModule(new InfrastructureModule(_configuration[MysqlConnection]));
+
+ builder.RegisterModule(new MediatorModule());
+
+ builder.RegisterModule(new AutoMapperModule());
+ }
+
+ // Configure is where you add middleware. This is called after
+ // ConfigureContainer. You can use IApplicationBuilder.ApplicationServices
+ // here if you need to resolve things from the container.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
+ {
+ AutofacContainer = app.ApplicationServices.GetAutofacRoot();
+
+
+ //loggerFactory.AddSerilog();
+
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+ else
+ {
+ app.UseExceptionHandler("/Error");
+ }
+
+ app.UseStaticFiles();
+
+ if (!env.IsDevelopment())
+ {
+ app.UseSpaStaticFiles();
+ }
+
+ app.UseRouting();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllerRoute(
+ name: "default",
+ pattern: "{controller}/{action=Index}/{id?}");
+ });
+
+ ConfigureSwagger(app);
+ }
+
+ private static void ConfigureSwagger(IApplicationBuilder app)
+ {
+ app.UseSwagger();
+
+ app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Sample CQRS API V1"); });
+ }
+
+ private void AddSwagger(IServiceCollection services)
+ {
+ services.AddSwaggerGen(options =>
+ {
+ options.SwaggerDoc("v1", new OpenApiInfo
+ {
+ Title = "API",
+ Version = "v1",
+ Description = "A simple example ASP.NET Core Web API",
+ TermsOfService = new Uri("https://somall.top/about")
+ });
+ options.DocInclusionPredicate((docName, description) => true);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.API/appsettings.json b/CQRS_Simple.API/appsettings.json
new file mode 100644
index 0000000..fc46808
--- /dev/null
+++ b/CQRS_Simple.API/appsettings.json
@@ -0,0 +1,14 @@
+{
+ "ConnectionStrings": {
+ "Default": "Server=somall.top; Database=CQRS_Simple;uid=sa;pwd=hiue234Dfdf;Max Pool Size=2000;",
+ "Mysql": "server=119.91.157.50;port=6606;uid=root;pwd=123456;database=cqrs_simple;CharSet=utf8"
+ },
+ "RabbitMQ": {
+ "UserName": "guest",
+ "Password": "guest",
+ "HostName": "127.0.0.1",
+ "Port": 5672,
+ "QueryName": "MyQuery"
+ },
+ "AllowedHosts": "*"
+}
diff --git a/CQRS_Simple.API/wwwroot/favicon.ico b/CQRS_Simple.API/wwwroot/favicon.ico
new file mode 100644
index 0000000..a3a7999
Binary files /dev/null and b/CQRS_Simple.API/wwwroot/favicon.ico differ
diff --git a/CQRS_Simple.Domain/CQRS_Simple.Domain.csproj b/CQRS_Simple.Domain/CQRS_Simple.Domain.csproj
new file mode 100644
index 0000000..ec85ae1
--- /dev/null
+++ b/CQRS_Simple.Domain/CQRS_Simple.Domain.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net6.0
+ 8
+
+ Library
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CQRS_Simple.Domain/Products/Product.cs b/CQRS_Simple.Domain/Products/Product.cs
new file mode 100644
index 0000000..8165aa8
--- /dev/null
+++ b/CQRS_Simple.Domain/Products/Product.cs
@@ -0,0 +1,29 @@
+using CQRS_Simple.Infrastructure;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using FluentValidation;
+
+namespace CQRS_Simple.Domain.Products
+{
+ [Table("Products")]
+ public class Product : Entity, IAggregateRoot
+ {
+ //[StringLength(256)] [Required]
+ public string Name { get; set; }
+
+ //[StringLength(256)]
+ public string Code { get; set; }
+
+ public string Description { get; set; }
+ }
+
+ public class ProductValidator : AbstractValidator
+ {
+ public ProductValidator()
+ {
+ RuleFor(x => x.Name).NotNull();
+ RuleFor(x => x.Name).Length(5, 256);
+ RuleFor(x => x.Code).Length(5, 256);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Domain/Products/Request/ProductsRequestInput.cs b/CQRS_Simple.Domain/Products/Request/ProductsRequestInput.cs
new file mode 100644
index 0000000..9acbc6e
--- /dev/null
+++ b/CQRS_Simple.Domain/Products/Request/ProductsRequestInput.cs
@@ -0,0 +1,20 @@
+using FluentValidation;
+
+namespace CQRS_Simple.Domain.Products.Request
+{
+ public class ProductsRequestInput
+ {
+ public int SkipCount { get; set; }
+
+ public int MaxResultCount { get; set; }
+ }
+
+ public class ProductValidator : AbstractValidator
+ {
+ public ProductValidator()
+ {
+ RuleFor(x => x.MaxResultCount).GreaterThanOrEqualTo(1).WithName("每页数量");
+ RuleFor(x => x.SkipCount).GreaterThanOrEqualTo(0).WithName("忽略行数");
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.EntityFrameworkCore/CQRS_Simple.EntityFrameworkCore.csproj b/CQRS_Simple.EntityFrameworkCore/CQRS_Simple.EntityFrameworkCore.csproj
new file mode 100644
index 0000000..082ccae
--- /dev/null
+++ b/CQRS_Simple.EntityFrameworkCore/CQRS_Simple.EntityFrameworkCore.csproj
@@ -0,0 +1,29 @@
+
+
+
+ Library
+ net6.0
+ 8
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
diff --git a/CQRS_Simple.EntityFrameworkCore/Migrations/20220406070529_InitialCreate.Designer.cs b/CQRS_Simple.EntityFrameworkCore/Migrations/20220406070529_InitialCreate.Designer.cs
new file mode 100644
index 0000000..5db6e2a
--- /dev/null
+++ b/CQRS_Simple.EntityFrameworkCore/Migrations/20220406070529_InitialCreate.Designer.cs
@@ -0,0 +1,45 @@
+//
+using CQRS_Simple.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace CQRS_Simple.EntityFrameworkCore.Migrations
+{
+ [DbContext(typeof(SimpleDbContext))]
+ [Migration("20220406070529_InitialCreate")]
+ partial class InitialCreate
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.3")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ modelBuilder.Entity("CQRS_Simple.Domain.Products.Product", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Code")
+ .HasColumnType("longtext");
+
+ b.Property("Description")
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.ToTable("Products");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/CQRS_Simple.EntityFrameworkCore/Migrations/20220406070529_InitialCreate.cs b/CQRS_Simple.EntityFrameworkCore/Migrations/20220406070529_InitialCreate.cs
new file mode 100644
index 0000000..c6233fa
--- /dev/null
+++ b/CQRS_Simple.EntityFrameworkCore/Migrations/20220406070529_InitialCreate.cs
@@ -0,0 +1,41 @@
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace CQRS_Simple.EntityFrameworkCore.Migrations
+{
+ public partial class InitialCreate : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AlterDatabase()
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateTable(
+ name: "Products",
+ columns: table => new
+ {
+ Id = table.Column(type: "int", nullable: false)
+ .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
+ Name = table.Column(type: "longtext", nullable: true)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Code = table.Column(type: "longtext", nullable: true)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Description = table.Column(type: "longtext", nullable: true)
+ .Annotation("MySql:CharSet", "utf8mb4")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Products", x => x.Id);
+ })
+ .Annotation("MySql:CharSet", "utf8mb4");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Products");
+ }
+ }
+}
diff --git a/CQRS_Simple.EntityFrameworkCore/Migrations/SimpleDbContextModelSnapshot.cs b/CQRS_Simple.EntityFrameworkCore/Migrations/SimpleDbContextModelSnapshot.cs
new file mode 100644
index 0000000..007438f
--- /dev/null
+++ b/CQRS_Simple.EntityFrameworkCore/Migrations/SimpleDbContextModelSnapshot.cs
@@ -0,0 +1,43 @@
+//
+using CQRS_Simple.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace CQRS_Simple.EntityFrameworkCore.Migrations
+{
+ [DbContext(typeof(SimpleDbContext))]
+ partial class SimpleDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "6.0.3")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ modelBuilder.Entity("CQRS_Simple.Domain.Products.Product", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Code")
+ .HasColumnType("longtext");
+
+ b.Property("Description")
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.ToTable("Products");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/CQRS_Simple.EntityFrameworkCore/SimpleDbContext.cs b/CQRS_Simple.EntityFrameworkCore/SimpleDbContext.cs
new file mode 100644
index 0000000..34fc754
--- /dev/null
+++ b/CQRS_Simple.EntityFrameworkCore/SimpleDbContext.cs
@@ -0,0 +1,24 @@
+using System;
+using CQRS_Simple.Domain.Products;
+using Microsoft.EntityFrameworkCore;
+using Serilog;
+
+namespace CQRS_Simple.EntityFrameworkCore
+{
+ public class SimpleDbContext : DbContext
+ {
+ protected SimpleDbContext()
+ {
+ }
+
+ public DbSet Products { get; set; }
+
+ public SimpleDbContext(DbContextOptions options)
+ : base(options)
+ {
+#if DEBUG
+ Log.Debug($"SimpleDbContext Init");
+#endif
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/CQRS_Simple.Infrastructure.csproj b/CQRS_Simple.Infrastructure/CQRS_Simple.Infrastructure.csproj
new file mode 100644
index 0000000..fe12023
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/CQRS_Simple.Infrastructure.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net6.0
+ 8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CQRS_Simple.Infrastructure/Dapper/DapperExtensions.cs b/CQRS_Simple.Infrastructure/Dapper/DapperExtensions.cs
new file mode 100644
index 0000000..a29a488
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Dapper/DapperExtensions.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using Dapper;
+
+namespace CQRS_Simple.Infrastructure.Dapper
+{
+ public static class DapperExtensions
+ {
+ public static async Task InsertAsync(this IDbConnection db, string tableName, object param)
+ {
+ IEnumerable result = await db.QueryAsync(DynamicQuery.GetInsertQuery(tableName, param), param);
+ return result.First();
+ }
+
+ public static async Task UpdateAsync(
+ this IDbConnection db,
+ string tableName, object param)
+ {
+ return await db.ExecuteAsync(DynamicQuery.GetUpdateQuery(tableName, param), param);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/Dapper/DapperRepository.cs b/CQRS_Simple.Infrastructure/Dapper/DapperRepository.cs
new file mode 100644
index 0000000..0c5f08e
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Dapper/DapperRepository.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using Dapper;
+
+namespace CQRS_Simple.Infrastructure.Dapper
+{
+ public class DapperRepository : IDapperRepository where T : Entity
+ {
+ private readonly ISqlConnectionFactory _sqlConnectionFactory;
+ private readonly string _tableName;
+
+ public DapperRepository(ISqlConnectionFactory sqlConnectionFactory)
+ {
+ _sqlConnectionFactory = sqlConnectionFactory;
+ var attr = typeof(T).GetCustomAttributes(typeof(TableAttribute), true).FirstOrDefault();
+ _tableName = (attr as TableAttribute)?.Name;
+ }
+
+ public async Task GetByIdAsync(TC id)
+ {
+ using var db = _sqlConnectionFactory.GetOpenConnection();
+ return await db.QueryFirstOrDefaultAsync(
+ $"SELECT * FROM {_tableName} WHERE Id=@Id",
+ new { Id = id });
+ }
+
+ public async Task AddAsync(T item)
+ {
+ using var db = _sqlConnectionFactory.GetOpenConnection();
+ return await db.InsertAsync(_tableName, item);
+ }
+
+ public async Task RemoveAsync(T item)
+ {
+ using var db = _sqlConnectionFactory.GetOpenConnection();
+ return await db.ExecuteAsync(
+ $"DELETE FROM {_tableName} WHERE Id=@Id",
+ new { Id = item.Id });
+ }
+
+ public async Task UpdateAsync(T item)
+ {
+ using var db = _sqlConnectionFactory.GetOpenConnection();
+ return await db.UpdateAsync(_tableName, item);
+ }
+
+ public async Task> FindAsync(Expression> predicate)
+ {
+ IEnumerable items;
+ var result = DynamicQuery.GetDynamicQuery(_tableName, predicate);
+ using (var db = _sqlConnectionFactory.GetOpenConnection())
+ {
+ items = await db.QueryAsync(result.Sql, (object)result.Param);
+ }
+
+ return items;
+ }
+
+ public async Task> GetAllAsync()
+ {
+ IEnumerable items;
+ using (var db = _sqlConnectionFactory.GetOpenConnection())
+ {
+ items = await db.QueryAsync(
+ $"SELECT * FROM {_tableName}"
+ );
+ }
+
+ return items;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/Dapper/DynamicQuery.cs b/CQRS_Simple.Infrastructure/Dapper/DynamicQuery.cs
new file mode 100644
index 0000000..2f04a12
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Dapper/DynamicQuery.cs
@@ -0,0 +1,201 @@
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+
+namespace CQRS_Simple.Infrastructure.Dapper
+{
+ ///
+ /// Dynamic query class.
+ ///
+ public sealed class DynamicQuery
+ {
+ ///
+ /// Gets the insert query.
+ ///
+ /// Name of the table.
+ /// The item.
+ ///
+ /// The Sql query based on the item properties.
+ ///
+ public static string GetInsertQuery(string tableName, object item)
+ {
+ PropertyInfo[] props = item.GetType().GetProperties(
+ BindingFlags.Public |
+ BindingFlags.Instance);
+ var columns = props.Where(x => x.Name != "Id").Select(p => p.Name).ToArray();
+
+ return string.Format("INSERT INTO {0} ({1}) OUTPUT Inserted.Id VALUES (@{2})",
+ tableName,
+ string.Join(",", columns),
+ string.Join(",@", columns));
+ }
+
+ ///
+ /// Gets the update query.
+ ///
+ /// Name of the table.
+ /// The item.
+ ///
+ /// The Sql query based on the item properties.
+ ///
+ public static string GetUpdateQuery(string tableName, dynamic item)
+ {
+ PropertyInfo[] props = item.GetType().GetProperties(BindingFlags.Public |
+ BindingFlags.Instance);
+
+ string[] columns = props.Where(x => x.Name != "Id").Select(p => p.Name).ToArray();
+
+ var parameters = columns.Select(name => name + "=@" + name).ToList();
+
+ return string.Format("UPDATE {0} SET {1} WHERE Id=@Id", tableName, string.Join(",", parameters));
+ }
+
+ ///
+ /// Gets the dynamic query.
+ ///
+ /// Name of the table.
+ /// The expression.
+ /// A result object with the generated sql and dynamic params.
+ public static QueryResult GetDynamicQuery(string tableName, Expression> expression)
+ {
+ var queryProperties = new List();
+ var body = (BinaryExpression)expression.Body;
+ IDictionary expando = new ExpandoObject();
+ var builder = new StringBuilder();
+
+ // walk the tree and build up a list of query parameter objects
+ // from the left and right branches of the expression tree
+ WalkTree(body, ExpressionType.Default, ref queryProperties);
+
+ // convert the query parms into a SQL string and dynamic property object
+ builder.Append("SELECT * FROM ");
+ builder.Append(tableName);
+ builder.Append(" WHERE ");
+
+ for (int i = 0; i < queryProperties.Count(); i++)
+ {
+ QueryParameter item = queryProperties[i];
+
+ if (!string.IsNullOrEmpty(item.LinkingOperator) && i > 0)
+ {
+ builder.Append(string.Format("{0} {1} {2} @{1} ", item.LinkingOperator, item.PropertyName,
+ item.QueryOperator));
+ }
+ else
+ {
+ builder.Append(string.Format("{0} {1} @{0} ", item.PropertyName, item.QueryOperator));
+ }
+
+ expando[item.PropertyName] = item.PropertyValue;
+ }
+
+ return new QueryResult(builder.ToString().TrimEnd(), expando);
+ }
+
+ ///
+ /// Walks the tree.
+ ///
+ /// The body.
+ /// Type of the linking.
+ /// The query properties.
+ private static void WalkTree(BinaryExpression body, ExpressionType linkingType,
+ ref List queryProperties)
+ {
+ if (body.NodeType != ExpressionType.AndAlso && body.NodeType != ExpressionType.OrElse)
+ {
+ string propertyName = GetPropertyName(body);
+ dynamic propertyValue = body.Right;
+ string opr = GetOperator(body.NodeType);
+ string link = GetOperator(linkingType);
+
+ queryProperties.Add(new QueryParameter(link, propertyName, propertyValue.Value, opr));
+ }
+ else
+ {
+ WalkTree((BinaryExpression)body.Left, body.NodeType, ref queryProperties);
+ WalkTree((BinaryExpression)body.Right, body.NodeType, ref queryProperties);
+ }
+ }
+
+ ///
+ /// Gets the name of the property.
+ ///
+ /// The body.
+ /// The property name for the property expression.
+ private static string GetPropertyName(BinaryExpression body)
+ {
+ string propertyName = body.Left.ToString().Split(new char[] { '.' })[1];
+
+ if (body.Left.NodeType == ExpressionType.Convert)
+ {
+ // hack to remove the trailing ) when convering.
+ propertyName = propertyName.Replace(")", string.Empty);
+ }
+
+ return propertyName;
+ }
+
+ ///
+ /// Gets the operator.
+ ///
+ /// The type.
+ ///
+ /// The expression types SQL server equivalent operator.
+ ///
+ ///
+ private static string GetOperator(ExpressionType type)
+ {
+ switch (type)
+ {
+ case ExpressionType.Equal:
+ return "=";
+ case ExpressionType.NotEqual:
+ return "!=";
+ case ExpressionType.LessThan:
+ return "<";
+ case ExpressionType.GreaterThan:
+ return ">";
+ case ExpressionType.AndAlso:
+ case ExpressionType.And:
+ return "AND";
+ case ExpressionType.Or:
+ case ExpressionType.OrElse:
+ return "OR";
+ case ExpressionType.Default:
+ return string.Empty;
+ default:
+ throw new NotImplementedException();
+ }
+ }
+ }
+
+ ///
+ /// Class that models the data structure in coverting the expression tree into SQL and Params.
+ ///
+ internal class QueryParameter
+ {
+ public string LinkingOperator { get; set; }
+ public string PropertyName { get; set; }
+ public object PropertyValue { get; set; }
+ public string QueryOperator { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The linking operator.
+ /// Name of the property.
+ /// The property value.
+ /// The query operator.
+ internal QueryParameter(string linkingOperator, string propertyName, object propertyValue, string queryOperator)
+ {
+ this.LinkingOperator = linkingOperator;
+ this.PropertyName = propertyName;
+ this.PropertyValue = propertyValue;
+ this.QueryOperator = queryOperator;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/Dapper/IDapperRepository.cs b/CQRS_Simple.Infrastructure/Dapper/IDapperRepository.cs
new file mode 100644
index 0000000..38460c7
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Dapper/IDapperRepository.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+
+namespace CQRS_Simple.Infrastructure.Dapper
+{
+ public interface IDapperRepository where T : Entity
+ {
+ Task AddAsync(T item);
+ Task RemoveAsync(T item);
+ Task UpdateAsync(T item);
+ Task GetByIdAsync(TC id);
+ Task> FindAsync(Expression> predicate);
+ Task> GetAllAsync();
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/Dapper/ISqlConnectionFactory.cs b/CQRS_Simple.Infrastructure/Dapper/ISqlConnectionFactory.cs
new file mode 100644
index 0000000..a2f746a
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Dapper/ISqlConnectionFactory.cs
@@ -0,0 +1,9 @@
+using System.Data;
+
+namespace CQRS_Simple.Infrastructure
+{
+ public interface ISqlConnectionFactory
+ {
+ IDbConnection GetOpenConnection();
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/Dapper/QueryResult.cs b/CQRS_Simple.Infrastructure/Dapper/QueryResult.cs
new file mode 100644
index 0000000..a88e549
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Dapper/QueryResult.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace CQRS_Simple.Infrastructure
+{
+ ///
+ /// A result object with the generated sql and dynamic params.
+ ///
+ public class QueryResult
+ {
+ ///
+ /// The _result
+ ///
+ private readonly Tuple _result;
+
+ ///
+ /// Gets the SQL.
+ ///
+ ///
+ /// The SQL.
+ ///
+ public string Sql
+ {
+ get
+ {
+ return _result.Item1;
+ }
+ }
+
+ ///
+ /// Gets the param.
+ ///
+ ///
+ /// The param.
+ ///
+ public dynamic Param
+ {
+ get
+ {
+ return _result.Item2;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The SQL.
+ /// The param.
+ public QueryResult(string sql, dynamic param)
+ {
+ _result = new Tuple(sql, param);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/Dapper/SqlConnectionFactory.cs b/CQRS_Simple.Infrastructure/Dapper/SqlConnectionFactory.cs
new file mode 100644
index 0000000..2ed300e
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Dapper/SqlConnectionFactory.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Data;
+using System.Data.SqlClient;
+using MySqlConnector;
+
+namespace CQRS_Simple.Infrastructure.Dapper
+{
+ public class SqlConnectionFactory : ISqlConnectionFactory, IDisposable
+ {
+ private readonly string _connectionString;
+ private IDbConnection _connection;
+
+ public SqlConnectionFactory(string connectionString)
+ {
+ this._connectionString = connectionString;
+ }
+
+ public IDbConnection GetOpenConnection()
+ {
+ if (this._connection == null || this._connection.State != ConnectionState.Open)
+ {
+ //this._connection = new SqlConnection(_connectionString);
+ this._connection = new MySqlConnection(_connectionString)
+ {
+ Site = null,
+ ProvideClientCertificatesCallback = null,
+ ProvidePasswordCallback = null,
+ RemoteCertificateValidationCallback = null
+ };
+ this._connection.Open();
+ }
+
+ return this._connection;
+ }
+
+ public void Dispose()
+ {
+ if (this._connection != null && this._connection.State == ConnectionState.Open)
+ {
+ this._connection.Dispose();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/EntityBase.cs b/CQRS_Simple.Infrastructure/EntityBase.cs
new file mode 100644
index 0000000..57822c0
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/EntityBase.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace CQRS_Simple.Infrastructure
+{
+ public class Entity : IEntity
+ {
+ [Key] public virtual TPrimaryKey Id { get; set; }
+
+ private List _domainEvents;
+
+ ///
+ /// Domain events occurred.
+ ///
+ protected IReadOnlyCollection DomainEvents => _domainEvents?.AsReadOnly();
+
+ ///
+ /// Add domain event.
+ ///
+ ///
+ protected void AddDomainEvent(IDomainEvent domainEvent)
+ {
+ _domainEvents ??= new List();
+ _domainEvents.Add(domainEvent);
+ }
+
+ ///
+ /// Clead domain events.
+ ///
+ public void ClearDomainEvents()
+ {
+ _domainEvents?.Clear();
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return Id == null ? 0 : Id.GetHashCode();
+ }
+
+ ///
+ public override string ToString()
+ {
+ return $"[{GetType().Name} {Id}]";
+ }
+ }
+
+ public interface IEntity
+ {
+ TPrimaryKey Id { get; set; }
+
+ void ClearDomainEvents();
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/IAggregateRoot.cs b/CQRS_Simple.Infrastructure/IAggregateRoot.cs
new file mode 100644
index 0000000..9628227
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/IAggregateRoot.cs
@@ -0,0 +1,5 @@
+namespace CQRS_Simple.Infrastructure
+{
+ public interface IAggregateRoot
+ { }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/IDomainEvent.cs b/CQRS_Simple.Infrastructure/IDomainEvent.cs
new file mode 100644
index 0000000..43b99bb
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/IDomainEvent.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace CQRS_Simple.Infrastructure
+{
+ public class DomainEvent : IDomainEvent
+ {
+ ///
+ /// The time when the event occurred.
+ ///
+ public DateTime EventTime { get; set; }
+
+ ///
+ /// The object which triggers the event (optional).
+ ///
+ public object EventSource { get; set; }
+
+ ///
+ /// Constructor.
+ ///
+ protected DomainEvent()
+ {
+ EventTime = DateTime.Now;
+ }
+
+ }
+
+ public interface IDomainEvent
+ {
+ DateTime EventTime { get; }
+
+ object EventSource { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/IocManager.cs b/CQRS_Simple.Infrastructure/IocManager.cs
new file mode 100644
index 0000000..0928314
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/IocManager.cs
@@ -0,0 +1,29 @@
+using Autofac;
+
+namespace CQRS_Simple.Infrastructure
+{
+
+ public interface IIocManager
+ {
+ ILifetimeScope AutofacContainer { get; set; }
+ TService GetInstance();
+
+ }
+
+ public class IocManager : IIocManager
+ {
+ public IocManager(ILifetimeScope container)
+ {
+ AutofacContainer = container;
+ }
+ ///
+ /// Autofac容器
+ ///
+ public ILifetimeScope AutofacContainer { get; set; }
+
+ public TService GetInstance()
+ {
+ return AutofacContainer.Resolve();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/MQ/RabbitData.cs b/CQRS_Simple.Infrastructure/MQ/RabbitData.cs
new file mode 100644
index 0000000..a11b449
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/MQ/RabbitData.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace CQRS_Simple.API.Products.Handlers
+{
+ public class RabbitData
+ {
+ public string Type { get; private set; }
+ public object Request { get; private set; }
+
+ public object Result { get; private set; }
+
+ public RabbitData(Type type, object request, object result = null)
+ {
+ Type = $"{type}";
+ Request = request;
+ Result = result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/MQ/RabbitListener.cs b/CQRS_Simple.Infrastructure/MQ/RabbitListener.cs
new file mode 100644
index 0000000..e65251f
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/MQ/RabbitListener.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+using Serilog;
+
+namespace CQRS_Simple.Infrastructure.MQ
+{
+ public class RabbitListener : IHostedService
+ {
+ private readonly RabbitMQOptions _options;
+ private readonly IConnection connection;
+ private readonly IModel channel;
+
+ public RabbitListener(
+ IOptions optionsAccessor
+ )
+ {
+ _options = optionsAccessor.Value;
+ try
+ {
+ var factory = new ConnectionFactory()
+ {
+ UserName = _options.UserName,
+ Password = _options.Password,
+ HostName = _options.HostName,
+ Port = _options.Port
+ };
+ this.connection = factory.CreateConnection();
+ this.channel = connection.CreateModel();
+ Log.Information($"RabbitMQ 连接成功");
+
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"RabbitListener init error,ex:{ex.Message}");
+ }
+ }
+
+
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ await Register();
+ await Task.CompletedTask;
+ }
+
+ protected string QueueName= "QueueName";
+ protected string RouteKey;
+
+ // 处理消息的方法
+ public virtual async Task Register()
+ {
+ channel.ExchangeDeclare("message", ExchangeType.Topic, true, false, null);
+ channel.QueueDeclare(QueueName, true, false, false, null);
+
+ channel.QueueBind(queue: QueueName, exchange: "message", routingKey: RouteKey);
+
+ var consumer = new EventingBasicConsumer(channel);
+
+ consumer.Received += async (model, ea) =>
+ {
+ var body = ea.Body;
+ var message = Encoding.UTF8.GetString(body.ToArray());
+ var result = await ProcessAsync(message);
+ Log.Information($"收到消息: {message} routerKey: { ea.RoutingKey}");
+ if (result)
+ {
+ channel.BasicAck(ea.DeliveryTag, false);
+ }
+ await Task.Yield();
+ };
+ channel.BasicConsume(queue: QueueName, consumer: consumer);
+
+ await Task.CompletedTask;
+ }
+
+ public virtual Task ProcessAsync(string message)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ channel?.Dispose();
+ connection?.Dispose();
+ return Task.CompletedTask;
+ }
+ }
+
+ public class RabbitMQOptions
+ {
+ public string UserName { get; set; }
+ public string Password { get; set; }
+ public string HostName { get; set; }
+ public int Port { get; set; }
+ public string QueryName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/MQ/RabbitPublisher.cs b/CQRS_Simple.Infrastructure/MQ/RabbitPublisher.cs
new file mode 100644
index 0000000..dd87110
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/MQ/RabbitPublisher.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Text;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+using RabbitMQ.Client;
+using Serilog;
+
+namespace CQRS_Simple.Infrastructure.MQ
+{
+ public class RabbitMQClient : IDisposable
+ {
+ private readonly RabbitMQOptions _options;
+
+ private IModel _channel;
+ private IConnection _connection;
+
+ public RabbitMQClient(
+ IOptions optionsAccessor
+ )
+ {
+ _options = optionsAccessor.Value;
+ try
+ {
+ var factory = new ConnectionFactory()
+ {
+ UserName = _options.UserName,
+ Password = _options.Password,
+ HostName = _options.HostName,
+ Port = _options.Port
+ };
+ this._connection = factory.CreateConnection();
+ this._channel = _connection.CreateModel();
+ Log.Information($"RabbitMQ Client 连接成功");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"RabbitListener init error,ex:{ex.Message}");
+ }
+ }
+
+ public virtual void PushMessage(object message, string queryName = null, string routerKey = "Test.*")
+ {
+ if (queryName == null)
+ queryName = _options.QueryName;
+
+ var exchangeName = "message";
+
+ Log.Debug($"PushMessage queryName:{queryName} routingKey:{routerKey}");
+
+ //定义一个Direct类型交换机
+ _channel.ExchangeDeclare(exchangeName, ExchangeType.Topic, true, false, null);
+
+ //定义一个队列
+ _channel.QueueDeclare(queryName, true, false, false, null);
+
+ //将队列绑定到交换机
+ _channel.QueueBind(queryName, exchangeName, routerKey, null);
+
+ var sendBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
+
+ _channel.BasicPublish(exchangeName, routerKey, null, sendBytes);
+ }
+
+ public void Dispose()
+ {
+ _channel?.Dispose();
+ _connection?.Dispose();
+ Log.Information($"RabbitMQ Client Dispose");
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/TimeInterceptor.cs b/CQRS_Simple.Infrastructure/TimeInterceptor.cs
new file mode 100644
index 0000000..c60e7a7
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/TimeInterceptor.cs
@@ -0,0 +1,40 @@
+using System;
+using System.IO;
+using System.Linq;
+using Castle.DynamicProxy;
+
+
+namespace CQRS_Simple.Infrastructure
+{
+ ///
+ /// 拦截器 需要实现 IInterceptor接口 Intercept方法
+ ///
+ public class CallLogger : IInterceptor
+ {
+ TextWriter _output;
+
+ public CallLogger(TextWriter output)
+ {
+ _output = output;
+ }
+
+ ///
+ /// 拦截方法 打印被拦截的方法执行前的名称、参数和方法执行后的 返回结果
+ ///
+ /// 包含被拦截方法的信息
+ public void Intercept(IInvocation invocation)
+ {
+ if (invocation.Method.IsPublic)
+ {
+ _output.WriteLine("你正在调用方法 \"{0}\" 参数是 {1}... ",
+ invocation.Method.Name,
+ string.Join(", ", invocation.Arguments.Select(a => (a ?? "").ToString()).ToArray()));
+
+ //在被拦截的方法执行完毕后 继续执行
+ invocation.Proceed();
+
+ _output.WriteLine("方法执行完毕,返回结果:{0}", invocation.ReturnValue);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/Uow/IRepository.cs b/CQRS_Simple.Infrastructure/Uow/IRepository.cs
new file mode 100644
index 0000000..8d05d8d
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Uow/IRepository.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+
+namespace CQRS_Simple.Infrastructure.Uow
+{
+ public interface IRepository where T : Entity
+ {
+ IUnitOfWork UnitOfWork { get; }
+ T GetById(TC id);
+ Task GetByIdAsync(TC id);
+ IEnumerable GetAll();
+ IEnumerable Get(Expression> predicate);
+ void Add(T entity);
+ void Delete(T entity);
+ void Update(T entity);
+
+ Task> GetAllAsync();
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/Uow/IUnitOfWork.cs b/CQRS_Simple.Infrastructure/Uow/IUnitOfWork.cs
new file mode 100644
index 0000000..225372d
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Uow/IUnitOfWork.cs
@@ -0,0 +1,16 @@
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Threading.Tasks;
+
+namespace CQRS_Simple.Infrastructure.Uow
+{
+ public interface IUnitOfWork : IDisposable
+ {
+ DbContext Context { get; }
+ int SaveChanges();
+ Task SaveChangesAsync();
+
+ IRepository GetRepository() where T : Entity;
+ void PrintKey();
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/Uow/Repository.cs b/CQRS_Simple.Infrastructure/Uow/Repository.cs
new file mode 100644
index 0000000..270ebcd
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Uow/Repository.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.EntityFrameworkCore;
+
+namespace CQRS_Simple.Infrastructure.Uow
+{
+ public class Repository : IRepository where T : Entity
+ {
+ private readonly IUnitOfWork _unitOfWork;
+
+ public Repository(IUnitOfWork unitOfWork)
+ {
+ _unitOfWork = unitOfWork;
+ }
+ public void Add(T entity)
+ {
+ _unitOfWork.Context.Set().Add(entity);
+ }
+
+ public void Delete(T entity)
+ {
+ T existing = _unitOfWork.Context.Set().Find(entity);
+ if (existing != null) _unitOfWork.Context.Set().Remove(existing);
+ }
+
+ public IUnitOfWork UnitOfWork => _unitOfWork;
+
+ public T GetById(TPrimaryKey id)
+ {
+ return _unitOfWork.Context.Set().FirstOrDefault(x => id.Equals(x.Id));
+ }
+ public Task GetByIdAsync(TPrimaryKey id)
+ {
+ return _unitOfWork.Context.Set().FirstOrDefaultAsync(x => id.Equals(x.Id));
+ }
+
+ public IEnumerable GetAll()
+ {
+ return _unitOfWork.Context.Set().AsEnumerable();
+ }
+
+ public IEnumerable Get(System.Linq.Expressions.Expression> predicate)
+ {
+ return _unitOfWork.Context.Set().Where(predicate).AsEnumerable();
+ }
+
+ public void Update(T entity)
+ {
+ _unitOfWork.Context.Entry(entity).State = EntityState.Modified;
+ _unitOfWork.Context.Set().Attach(entity);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _unitOfWork.Context.Set().ToListAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Infrastructure/Uow/UnitOfWork.cs b/CQRS_Simple.Infrastructure/Uow/UnitOfWork.cs
new file mode 100644
index 0000000..427cebf
--- /dev/null
+++ b/CQRS_Simple.Infrastructure/Uow/UnitOfWork.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Storage;
+using Serilog;
+
+namespace CQRS_Simple.Infrastructure.Uow
+{
+ public class UnitOfWork : IUnitOfWork
+ {
+ private readonly IIocManager _iocManager;
+ private Guid KEY { get; }
+
+ public DbContext Context { get; }
+
+ public IDbContextTransaction Transaction;
+
+ public UnitOfWork(DbContext context, IIocManager iocManager)
+ {
+ _iocManager = iocManager;
+ Context = context;
+
+ Transaction = context.Database.BeginTransaction();
+
+ KEY = Guid.NewGuid();
+#if DEBUG
+ Log.Information($"UnitOfWork Init {KEY}");
+#endif
+ }
+ public int SaveChanges()
+ {
+ return Context.SaveChanges();
+ }
+
+ public async Task SaveChangesAsync()
+ {
+ return await CommitAsync();
+ }
+
+ public IRepository GetRepository() where T : Entity
+ {
+ return _iocManager.GetInstance>();
+ }
+
+ public void PrintKey()
+ {
+ Log.Information($"PrintKey:{KEY}");
+ }
+
+ ///
+ /// 手动清理
+ ///
+ public void CleanUp()
+ {
+ Commit();
+
+ Transaction?.Dispose();
+ Context?.Dispose();
+
+#if DEBUG
+ Log.Information($"Context CleanUp");
+#endif
+ }
+
+
+ private int Commit()
+ {
+ var result = 0;
+ try
+ {
+ result = Context.SaveChanges();
+ Transaction?.Commit();
+ return result;
+ }
+ catch (Exception e)
+ {
+ result = -1;
+ Transaction?.Rollback();
+ Log.Error("Context Transaction Error");
+ Log.Error(e.Message);
+ }
+ return result;
+ }
+ private async Task CommitAsync()
+ {
+ var result = 0;
+ try
+ {
+ result = await Context.SaveChangesAsync();
+ Transaction?.Commit();
+ return result;
+ }
+ catch (Exception e)
+ {
+ result = -1;
+ if (Transaction != null)
+ await Transaction.RollbackAsync();
+ Log.Error("Context Transaction Error");
+ Log.Error(e.Message);
+ }
+ return result;
+ }
+
+
+
+ public void Dispose()
+ {
+ Context?.Dispose();
+#if DEBUG
+ Log.Information($"Context Dispose");
+
+#endif
+ }
+ }
+}
\ No newline at end of file
diff --git a/CQRS_Simple.Tests/CQRS_Simple.Tests.csproj b/CQRS_Simple.Tests/CQRS_Simple.Tests.csproj
new file mode 100644
index 0000000..c12d880
--- /dev/null
+++ b/CQRS_Simple.Tests/CQRS_Simple.Tests.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net6.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/CQRS_Simple.Tests/UnitTest1.cs b/CQRS_Simple.Tests/UnitTest1.cs
new file mode 100644
index 0000000..27da1c2
--- /dev/null
+++ b/CQRS_Simple.Tests/UnitTest1.cs
@@ -0,0 +1,14 @@
+using System;
+using Xunit;
+
+namespace CQRS_Simple.Tests
+{
+ public class UnitTest1
+ {
+ [Fact]
+ public void Test1()
+ {
+
+ }
+ }
+}
diff --git a/CQRS_Simple.sln b/CQRS_Simple.sln
new file mode 100644
index 0000000..010d1c1
--- /dev/null
+++ b/CQRS_Simple.sln
@@ -0,0 +1,58 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29519.181
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CQRS_Simple.API", "CQRS_Simple.API\CQRS_Simple.API.csproj", "{0B262E43-DCAD-4FB5-B394-E4DEE455C605}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CQRS_Simple.Domain", "CQRS_Simple.Domain\CQRS_Simple.Domain.csproj", "{D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CQRS_Simple.Infrastructure", "CQRS_Simple.Infrastructure\CQRS_Simple.Infrastructure.csproj", "{14AB059D-1475-4A91-AAF4-312FCB265E81}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CQRS_Simple.EntityFrameworkCore", "CQRS_Simple.EntityFrameworkCore\CQRS_Simple.EntityFrameworkCore.csproj", "{F6AFAF16-2BA5-4789-9EE4-A6F75CBBF4F7}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{31A46403-BFDD-499A-8EC2-2EABDC096A47}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "API", "API", "{439AFE5F-24A5-4F70-9BEC-690E124C5D30}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CQRS_Simple.Tests", "CQRS_Simple.Tests\CQRS_Simple.Tests.csproj", "{824FCC5C-EE42-4AD8-8B00-DBC98EFA75A7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {0B262E43-DCAD-4FB5-B394-E4DEE455C605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0B262E43-DCAD-4FB5-B394-E4DEE455C605}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0B262E43-DCAD-4FB5-B394-E4DEE455C605}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0B262E43-DCAD-4FB5-B394-E4DEE455C605}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {14AB059D-1475-4A91-AAF4-312FCB265E81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {14AB059D-1475-4A91-AAF4-312FCB265E81}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {14AB059D-1475-4A91-AAF4-312FCB265E81}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {14AB059D-1475-4A91-AAF4-312FCB265E81}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F6AFAF16-2BA5-4789-9EE4-A6F75CBBF4F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F6AFAF16-2BA5-4789-9EE4-A6F75CBBF4F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F6AFAF16-2BA5-4789-9EE4-A6F75CBBF4F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F6AFAF16-2BA5-4789-9EE4-A6F75CBBF4F7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {824FCC5C-EE42-4AD8-8B00-DBC98EFA75A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {824FCC5C-EE42-4AD8-8B00-DBC98EFA75A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {824FCC5C-EE42-4AD8-8B00-DBC98EFA75A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {824FCC5C-EE42-4AD8-8B00-DBC98EFA75A7}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {0B262E43-DCAD-4FB5-B394-E4DEE455C605} = {439AFE5F-24A5-4F70-9BEC-690E124C5D30}
+ {D3EA4B65-1A8C-4F16-ABF9-837CDBD78CB5} = {31A46403-BFDD-499A-8EC2-2EABDC096A47}
+ {14AB059D-1475-4A91-AAF4-312FCB265E81} = {31A46403-BFDD-499A-8EC2-2EABDC096A47}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3BA00842-BEC3-4017-A48A-8EB7D8B1D18A}
+ EndGlobalSection
+EndGlobal
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fb1067e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+## 这是一个 DDD CQRS 的项目初始模版项目
+
+### 后端技术栈
+
+- .netcore 3.1
+- MediatR
+- EntityFrameworkCore 3.1
+- Dapper
+- RabbitMQ (Producer & Consumer )
+- AutoMapper 9
+- FluentValidation
+- IdentityServer4 (todo)
+- Serilog
+