Commit be19a9e3 by 丁松杰

添加部分测试代码

parent d827c767
Showing with 386 additions and 752 deletions
using NewArchitectureLab.Apps.Product;
using Pole.Application.Command;
using Pole.Application.Cqrs;
using Pole.Domain.UnitOfWork;
using Pole.Grpc.ExtraType;
using PoleSample.Apis.Product;
using Product.Api.Domain.Event;
using Product.Api.Domain.ProductTypeAggregate;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Product.Api.Application.CommandHandler
{
public class AddProductTypeCommandHandler : ICommandHandler<Command<AddProductTypeRequest, CommonCommandResponse>, CommonCommandResponse>
{
private readonly IProductTypeRepository _productTypeRepository;
private readonly IUnitOfWork _unitOfWork;
public AddProductTypeCommandHandler(IProductTypeRepository productTypeRepository, IUnitOfWork unitOfWork)
{
_productTypeRepository = productTypeRepository;
_unitOfWork = unitOfWork;
}
public async Task<CommonCommandResponse> Handle(Command<AddProductTypeRequest, CommonCommandResponse> request, CancellationToken cancellationToken)
{
var productType = new Domain.ProductTypeAggregate.ProductType(Guid.NewGuid().ToString("N"), request.Data.Name);
_productTypeRepository.Add(productType);
ProductTypeAddedDomainEvent productTypeAddedDomainEvent = new ProductTypeAddedDomainEvent
{
ProductTypeId = productType.Id,
ProductTypeName = productType.Name
};
productType.AddDomainEvent(productTypeAddedDomainEvent);
var result = await _productTypeRepository.SaveEntitiesAsync();
await _unitOfWork.CompeleteAsync();
return CommonCommandResponse.SuccessResponse;
}
}
}
using Pole.Application.EventBus;
using Pole.Domain;
using Product.Api.Domain.Event;
using Product.Api.Domain.ProductAggregate;
using Product.IntegrationEvents;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Product.Api.Application.DomainEventHandler
{
public class AddDefaultProductWhenProductTypeAdded2DomainEventHandler : IDomainEventHandler<ProductTypeAddedDomainEvent>
{
private readonly IProductRepository _productRepository;
private readonly IEventBus _eventBus;
public AddDefaultProductWhenProductTypeAdded2DomainEventHandler(IProductRepository productRepository, IEventBus eventBus)
{
_productRepository = productRepository;
_eventBus = eventBus;
}
public async Task Handle(ProductTypeAddedDomainEvent request, CancellationToken cancellationToken)
{
Product.Api.Domain.ProductAggregate.Product product = new Product.Api.Domain.ProductAggregate.Product(Guid.NewGuid().ToString("N"), request.ProductTypeName, 100, request.ProductTypeId);
_productRepository.Add(product);
var backId = Guid.NewGuid().ToString("N");
ProductAddedIntegrationEvent productAddedIntegrationEvent = new ProductAddedIntegrationEvent()
{
BacketId = backId,
Price = product.Price,
ProductName = product.Name,
ProductId = product.Id
};
await _eventBus.Publish(productAddedIntegrationEvent, product.Id);
await _productRepository.SaveEntitiesAsync();
}
}
}
using Pole.Domain;
using Product.Api.Domain.Event;
using Product.Api.Domain.ProductAggregate;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Product.Api.Application.DomainEventHandler
{
public class AddDefaultProductWhenProductTypeAddedDomainEventHandler : IDomainEventHandler<ProductTypeAddedDomainEvent>
{
private readonly IProductRepository _productRepository;
public AddDefaultProductWhenProductTypeAddedDomainEventHandler(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public async Task Handle(ProductTypeAddedDomainEvent request, CancellationToken cancellationToken)
{
Product.Api.Domain.ProductAggregate.Product product = new Product.Api.Domain.ProductAggregate.Product(Guid.NewGuid().ToString("N"), request.ProductTypeName, 100, request.ProductTypeId);
_productRepository.Add(product);
await _productRepository.SaveEntitiesAsync();
}
}
}
using Pole.ReliableMessage.Abstraction;
using Product.Api.Application.Query.Abstraction;
using Product.Api.Domain.ProductAggregate;
using Product.IntegrationEvents;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Application.IntegrationEvent.CallBack
{
public class ProductAddedIntegrationEventCallback : IReliableEventCallback<ProductAddedIntegrationEvent, string>
{
private readonly IProductQuery _productQuery;
public ProductAddedIntegrationEventCallback(IProductQuery productQuery)
{
_productQuery = productQuery;
}
public async Task<bool> Callback(string callbackParemeter)
{
return await _productQuery.GetById(callbackParemeter) != null;
}
}
}
using Pole.Core.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Application.Query.Abstraction
{
public interface IProductQuery: IScopedDenpendency
{
Task<Product.Api.Domain.ProductAggregate.Product> GetById(string id);
}
}
using Microsoft.EntityFrameworkCore;
using Product.Api.Application.Query.Abstraction;
using Product.Api.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Application.Query
{
public class EfCoreProductQuery : IProductQuery
{
private readonly ProductDbContext _productDbContext;
public EfCoreProductQuery(ProductDbContext productDbContext)
{
_productDbContext = productDbContext;
}
public Task<Domain.ProductAggregate.Product> GetById(string id)
{
return _productDbContext.Products.FirstOrDefaultAsync(m => m.Id == id);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Orleans;
using Product.Api.Grains.Abstraction;
namespace Product.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ProductTypeController : ControllerBase
{
private readonly IClusterClient clusterClient;
public ProductTypeController(IClusterClient clusterClient)
{
this.clusterClient = clusterClient;
}
[HttpGet("AddProductType")]
public void AddProductType(string name = "test")
{
var newId = Guid.NewGuid().ToString("N").ToLower();
var grain = clusterClient.GetGrain<IProductTypeGrain>(newId);
grain.AddProductType(newId, name);
}
}
}
\ No newline at end of file
using Pole.Domain;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Domain.ProductAggregate
{
public class Product : Entity, IAggregateRoot
{
public Product(string id ,string name,long price, string productTypeId)
{
Id = id;
Name = name;
Price = price;
ProductTypeId = productTypeId;
}
public string Name { get;private set; }
public long Price { get;private set; }
public string ProductTypeId { get;private set; }
}
}
using Pole.Domain;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Domain.ProductTypeAggregate
{
public interface IProductTypeRepository:IRepository<ProductType>
{
}
}
using Pole.Domain; using Pole.Core.Domain;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Product.Api.Domain.ProductTypeAggregate namespace Product.Api.Domain.AggregatesModel.ProductTypeAggregate
{ {
public class ProductType : Entity, IAggregateRoot public class ProductType : Entity, IAggregateRoot
{ {
public ProductType(string id,string name) public string Name { get; set; }
{
Id = id;
Name = name;
}
public string Name { get;private set; }
} }
} }
using Pole.Domain;
using PoleSample.Apis.Product;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Domain.Event
{
public class ProductTypeAddedDomainEvent: IDomainEvent
{
public string ProductTypeName { get; set; }
public string ProductTypeId { get; set; }
}
}
using Pole.Domain; using Orleans;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Product.Api.Domain.ProductAggregate namespace Product.Api.Grains.Abstraction
{ {
public interface IProductRepository : IRepository<Product> public interface IProductTypeGrain: IGrainWithStringKey
{ {
Task<bool> AddProductType(string id, string name);
} }
} }
using Orleans;
using Product.Api.Domain.AggregatesModel.ProductTypeAggregate;
using Product.Api.Grains.Abstraction;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Grains
{
public class ProductTypeGrain : Grain<ProductType>, IProductTypeGrain
{
public async Task<bool> AddProductType(string id, string name)
{
if (State != null) return false;
ProductType productType = new ProductType
{
Id = id,
Name = name
};
State = productType;
await WriteStateAsync();
return true;
}
}
}
using Grpc.Core;
using NewArchitectureLab.Apps.Product;
using Pole.Application.Command;
using Pole.Application.Cqrs;
using Pole.Grpc.ExtraType;
using PoleSample.Apis.Product;
using Product.Api.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Grpc
{
public class ProductTypeService: PoleSample.Apis.Product.ProductType.ProductTypeBase
{
private readonly ICommandBus _commandBus;
public ProductTypeService(ICommandBus commandBus)
{
_commandBus = commandBus;
}
public override Task<CommonCommandResponse> Add(AddProductTypeRequest request, ServerCallContext context)
{
var cpmmand = new Command<AddProductTypeRequest, CommonCommandResponse>(request);
return _commandBus.Send(cpmmand);
}
}
}
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Infrastructure.EntityConfigurations
{
public class ProductEntityTypeEntityTypeConfiguration : IEntityTypeConfiguration<Product.Api.Domain.ProductAggregate.Product>
{
public void Configure(EntityTypeBuilder<Product.Api.Domain.ProductAggregate.Product> builder)
{
builder.ToTable(nameof(Product));
builder.Property(m => m.Id).HasMaxLength(32);
builder.Property(m => m.Name).HasMaxLength(256).IsRequired();
builder.Property(m => m.ProductTypeId).HasMaxLength(32).IsRequired();
builder.Ignore(m => m.DomainEvents);
builder.HasIndex(m => m.ProductTypeId);
builder.HasKey(m => m.Id);
}
}
}
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Product.Api.Domain.ProductTypeAggregate; using Product.Api.Domain.AggregatesModel.ProductTypeAggregate;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
...@@ -22,4 +22,5 @@ namespace Product.Api.Infrastructure.EntityConfigurations ...@@ -22,4 +22,5 @@ namespace Product.Api.Infrastructure.EntityConfigurations
builder.HasKey(m => m.Id); builder.HasKey(m => m.Id);
} }
} }
} }
using MediatR; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Product.Api.Domain.AggregatesModel.ProductTypeAggregate;
using Pole.EntityframeworkCore;
using Product.Api.Infrastructure.EntityConfigurations; using Product.Api.Infrastructure.EntityConfigurations;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
...@@ -11,8 +11,8 @@ namespace Product.Api.Infrastructure ...@@ -11,8 +11,8 @@ namespace Product.Api.Infrastructure
{ {
public class ProductDbContext : DbContext public class ProductDbContext : DbContext
{ {
public DbSet<Product.Api.Domain.ProductAggregate.Product> Products { get; set; } //public DbSet<Product.Api.Domain.ProductAggregate.Product> Products { get; set; }
public DbSet<Product.Api.Domain.ProductTypeAggregate.ProductType> ProductTypes { get; set; } public DbSet<ProductType> ProductTypes { get; set; }
public ProductDbContext(DbContextOptions options) : base(options) public ProductDbContext(DbContextOptions options) : base(options)
{ {
...@@ -21,8 +21,9 @@ namespace Product.Api.Infrastructure ...@@ -21,8 +21,9 @@ namespace Product.Api.Infrastructure
{ {
base.OnModelCreating(builder); base.OnModelCreating(builder);
builder.ApplyConfiguration(new ProductEntityTypeEntityTypeConfiguration()); //builder.ApplyConfiguration(new ProductEntityTypeEntityTypeConfiguration());
builder.ApplyConfiguration(new ProductTypeEntityTypeConfiguration()); builder.ApplyConfiguration(new ProductTypeEntityTypeConfiguration());
} }
} }
} }
using Pole.Domain.EntityframeworkCore;
using Pole.Domain.UnitOfWork;
using Product.Api.Domain.ProductAggregate;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Infrastructure.Repository
{
public class ProductRepository : EFCoreRepository<Product.Api.Domain.ProductAggregate.Product>, IProductRepository
{
public ProductRepository(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
}
using Pole.Domain.EntityframeworkCore;
using Pole.Domain.UnitOfWork;
using Product.Api.Domain.ProductTypeAggregate;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Product.Api.Infrastructure.Repository
{
public class ProductTypeRepository : EFCoreRepository<ProductType>, IProductTypeRepository
{
public ProductTypeRepository(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
}
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Product.Api.Infrastructure;
namespace Product.Api.Migrations
{
[DbContext(typeof(ProductDbContext))]
[Migration("20200108090435_init")]
partial class init
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Product.Api.Domain.ProductAggregate.Product", b =>
{
b.Property<string>("Id")
.HasColumnType("character varying(32)")
.HasMaxLength(32);
b.Property<string>("Name")
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<long>("Price")
.HasColumnType("bigint");
b.Property<string>("ProductId")
.HasColumnType("character varying(32)")
.HasMaxLength(32);
b.HasKey("Id");
b.HasIndex("ProductId");
b.ToTable("Product");
});
#pragma warning restore 612, 618
}
}
}
using Microsoft.EntityFrameworkCore.Migrations;
namespace Product.Api.Migrations
{
public partial class Modify_Product_ProductId : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "ProductId",
table: "Product",
maxLength: 32,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(32)",
oldMaxLength: 32,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Product",
maxLength: 256,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256,
oldNullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "ProductId",
table: "Product",
type: "character varying(32)",
maxLength: 32,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 32);
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "Product",
type: "character varying(256)",
maxLength: 256,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 256);
}
}
}
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Product.Api.Infrastructure;
namespace Product.Api.Migrations
{
[DbContext(typeof(ProductDbContext))]
[Migration("20200114090656_Add_Product")]
partial class Add_Product
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.0")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Product.Api.Domain.ProductAggregate.Product", b =>
{
b.Property<string>("Id")
.HasColumnType("character varying(32)")
.HasMaxLength(32);
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<long>("Price")
.HasColumnType("bigint");
b.Property<string>("ProductTypeId")
.IsRequired()
.HasColumnType("character varying(32)")
.HasMaxLength(32);
b.HasKey("Id");
b.HasIndex("ProductTypeId");
b.ToTable("Product");
});
modelBuilder.Entity("Product.Api.Domain.ProductTypeAggregate.ProductType", b =>
{
b.Property<string>("Id")
.HasColumnType("character varying(32)")
.HasMaxLength(32);
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.HasKey("Id");
b.ToTable("ProductType");
});
#pragma warning restore 612, 618
}
}
}
using Microsoft.EntityFrameworkCore.Migrations;
namespace Product.Api.Migrations
{
public partial class Add_Product : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Product_ProductId",
table: "Product");
migrationBuilder.DropColumn(
name: "ProductId",
table: "Product");
migrationBuilder.AddColumn<string>(
name: "ProductTypeId",
table: "Product",
maxLength: 32,
nullable: false,
defaultValue: "");
migrationBuilder.CreateTable(
name: "ProductType",
columns: table => new
{
Id = table.Column<string>(maxLength: 32, nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProductType", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Product_ProductTypeId",
table: "Product",
column: "ProductTypeId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ProductType");
migrationBuilder.DropIndex(
name: "IX_Product_ProductTypeId",
table: "Product");
migrationBuilder.DropColumn(
name: "ProductTypeId",
table: "Product");
migrationBuilder.AddColumn<string>(
name: "ProductId",
table: "Product",
type: "character varying(32)",
maxLength: 32,
nullable: false,
defaultValue: "");
migrationBuilder.CreateIndex(
name: "IX_Product_ProductId",
table: "Product",
column: "ProductId");
}
}
}
...@@ -9,18 +9,18 @@ using Product.Api.Infrastructure; ...@@ -9,18 +9,18 @@ using Product.Api.Infrastructure;
namespace Product.Api.Migrations namespace Product.Api.Migrations
{ {
[DbContext(typeof(ProductDbContext))] [DbContext(typeof(ProductDbContext))]
[Migration("20200108092455_Modify_Product_ProductId")] [Migration("20200217053642_Init")]
partial class Modify_Product_ProductId partial class Init
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.0") .HasAnnotation("ProductVersion", "3.1.1")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Product.Api.Domain.ProductAggregate.Product", b => modelBuilder.Entity("Product.Api.Domain.AggregatesModel.ProductTypeAggregate.ProductType", b =>
{ {
b.Property<string>("Id") b.Property<string>("Id")
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
...@@ -31,19 +31,9 @@ namespace Product.Api.Migrations ...@@ -31,19 +31,9 @@ namespace Product.Api.Migrations
.HasColumnType("character varying(256)") .HasColumnType("character varying(256)")
.HasMaxLength(256); .HasMaxLength(256);
b.Property<long>("Price")
.HasColumnType("bigint");
b.Property<string>("ProductId")
.IsRequired()
.HasColumnType("character varying(32)")
.HasMaxLength(32);
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("ProductId"); b.ToTable("ProductType");
b.ToTable("Product");
}); });
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
......
...@@ -2,34 +2,27 @@ ...@@ -2,34 +2,27 @@
namespace Product.Api.Migrations namespace Product.Api.Migrations
{ {
public partial class init : Migration public partial class Init : Migration
{ {
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "Product", name: "ProductType",
columns: table => new columns: table => new
{ {
Id = table.Column<string>(maxLength: 32, nullable: false), Id = table.Column<string>(maxLength: 32, nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true), Name = table.Column<string>(maxLength: 256, nullable: false)
Price = table.Column<long>(nullable: false),
ProductId = table.Column<string>(maxLength: 32, nullable: true)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_Product", x => x.Id); table.PrimaryKey("PK_ProductType", x => x.Id);
}); });
migrationBuilder.CreateIndex(
name: "IX_Product_ProductId",
table: "Product",
column: "ProductId");
} }
protected override void Down(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "Product"); name: "ProductType");
} }
} }
} }
...@@ -15,36 +15,10 @@ namespace Product.Api.Migrations ...@@ -15,36 +15,10 @@ namespace Product.Api.Migrations
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
.HasAnnotation("ProductVersion", "3.1.0") .HasAnnotation("ProductVersion", "3.1.1")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
modelBuilder.Entity("Product.Api.Domain.ProductAggregate.Product", b => modelBuilder.Entity("Product.Api.Domain.AggregatesModel.ProductTypeAggregate.ProductType", b =>
{
b.Property<string>("Id")
.HasColumnType("character varying(32)")
.HasMaxLength(32);
b.Property<string>("Name")
.IsRequired()
.HasColumnType("character varying(256)")
.HasMaxLength(256);
b.Property<long>("Price")
.HasColumnType("bigint");
b.Property<string>("ProductTypeId")
.IsRequired()
.HasColumnType("character varying(32)")
.HasMaxLength(32);
b.HasKey("Id");
b.HasIndex("ProductTypeId");
b.ToTable("Product");
});
modelBuilder.Entity("Product.Api.Domain.ProductTypeAggregate.ProductType", b =>
{ {
b.Property<string>("Id") b.Property<string>("Id")
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
......
...@@ -3,30 +3,29 @@ ...@@ -3,30 +3,29 @@
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Protobuf Include="..\..\proto\service\Product\*.proto" AdditionalImportDirs="..\..\proto" GrpcServices="Server" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.26.0" /> <PackageReference Include="Grpc.AspNetCore" Version="2.26.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.1">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.0" /> <PackageReference Include="Microsoft.Orleans.Client" Version="3.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0"> <PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="3.0.2">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.0" /> <PackageReference Include="Microsoft.Orleans.Core" Version="3.0.2" />
<PackageReference Include="Microsoft.Orleans.OrleansRuntime" Version="3.0.2" />
<PackageReference Include="Microsoft.Orleans.Server" Version="3.0.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Pole.Core\Pole.Core.csproj" />
<ProjectReference Include="..\..\..\src\Pole.EventBus.Rabbitmq\Pole.EventBus.Rabbitmq.csproj" />
<ProjectReference Include="..\..\..\src\Pole.EventStorage.PostgreSql\Pole.EventStorage.PostgreSql.csproj" />
<ProjectReference Include="..\..\..\src\Pole.Orleans.Provider.EntityframeworkCore\Pole.Orleans.Provider.EntityframeworkCore.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\Pole.Application\Pole.Application.csproj" /> <Folder Include="Domain\AggregatesModel\ProductAggregate\" />
<ProjectReference Include="..\..\..\src\Pole.Domain.EntityframeworkCore\Pole.Domain.EntityframeworkCore.csproj" />
<ProjectReference Include="..\..\..\src\Pole.Domain\Pole.Domain.csproj" />
<ProjectReference Include="..\..\..\src\Pole.Grpc\Pole.Grpc.csproj" />
<ProjectReference Include="..\..\..\src\Pole.ReliableMessage.Masstransit\Pole.ReliableMessage.Masstransit.csproj" />
<ProjectReference Include="..\..\..\src\Pole.ReliableMessage.Storage.Mongodb\Pole.ReliableMessage.Storage.Mongodb.csproj" />
<ProjectReference Include="..\..\..\src\Pole.ReliableMessage\Pole.ReliableMessage.csproj" />
<ProjectReference Include="..\..\intergrationEvents\Product.IntegrationEvents\Product.IntegrationEvents.csproj" />
</ItemGroup> </ItemGroup>
<ProjectExtensions><VisualStudio><UserProperties appsettings_1json__JsonSchema="http://json.schemastore.org/appsscript" /></VisualStudio></ProjectExtensions>
</Project> </Project>
\ No newline at end of file
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Pole.Domain.EntityframeworkCore.MediatR; //using Pole.Domain.EntityframeworkCore.MediatR;
using Product.Api.Infrastructure; //using Product.Api.Infrastructure;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
...@@ -11,18 +11,18 @@ using System.Threading.Tasks; ...@@ -11,18 +11,18 @@ using System.Threading.Tasks;
namespace Product.Api namespace Product.Api
{ {
public class ProductDbContextDesignFactory : IDesignTimeDbContextFactory<ProductDbContext> //public class ProductDbContextDesignFactory : IDesignTimeDbContextFactory<ProductDbContext>
{ //{
public ProductDbContext CreateDbContext(string[] args) // public ProductDbContext CreateDbContext(string[] args)
{ // {
IConfigurationRoot configuration = new ConfigurationBuilder() // IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory()) // .SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.Development.json") // .AddJsonFile("appsettings.Development.json")
.Build(); // .Build();
var optionsBuilder = new DbContextOptionsBuilder<ProductDbContext>() // var optionsBuilder = new DbContextOptionsBuilder<ProductDbContext>()
.UseNpgsql(configuration["postgres:main"]); // .UseNpgsql(configuration["postgres:main"]);
return new ProductDbContext(optionsBuilder.Options); // return new ProductDbContext(optionsBuilder.Options);
} // }
} //}
} }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Orleans.Hosting;
using Product.Api.Infrastructure;
using Pole.Orleans.Provider.EntityframeworkCore;
using Orleans;
using Product.Api.Grains;
namespace Product.Api namespace Product.Api
{ {
...@@ -21,6 +22,12 @@ namespace Product.Api ...@@ -21,6 +22,12 @@ namespace Product.Api
.ConfigureWebHostDefaults(webBuilder => .ConfigureWebHostDefaults(webBuilder =>
{ {
webBuilder.UseStartup<Startup>(); webBuilder.UseStartup<Startup>();
})
.UseOrleans(siloBuilder =>
{
siloBuilder.UseLocalhostClustering();
siloBuilder.AddEfGrainStorageAsDefault<ProductDbContext>();
}); });
} }
} }
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Threading.Tasks;
using GreenPipes;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
...@@ -11,9 +5,6 @@ using Microsoft.EntityFrameworkCore; ...@@ -11,9 +5,6 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Npgsql;
using Pole.ReliableMessage.Storage.Mongodb;
using Product.Api.Grpc;
using Product.Api.Infrastructure; using Product.Api.Infrastructure;
namespace Product.Api namespace Product.Api
...@@ -31,68 +22,68 @@ namespace Product.Api ...@@ -31,68 +22,68 @@ namespace Product.Api
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddDbContext<ProductDbContext>(options => services.AddDbContextPool<ProductDbContext>(options =>
options.UseNpgsql(Configuration["postgres:main"])); options.UseNpgsql(Configuration["postgres:write"]));
services.AddControllers();
//services.AddGrpc(option =>
//{
// if (Environment.IsDevelopment())
// {
// option.EnableDetailedErrors = true;
// }
//});
services.AddGrpc(option => //services.AddGrpcValidation();
{ //services.AddGrpcRequestValidator(this.GetType().Assembly);
if (Environment.IsDevelopment())
{
option.EnableDetailedErrors = true;
}
});
services.AddGrpcValidation();
services.AddGrpcRequestValidator(this.GetType().Assembly);
services.AddPole(option => //services.AddPole(option =>
{ //{
option.AddManageredAssemblies(this.GetType().Assembly); // option.AddManageredAssemblies(this.GetType().Assembly);
option.AutoInjectionDependency(); // option.AutoInjectionDependency();
option.AutoInjectionCommandHandlersAndDomainEventHandlers(); // option.AutoInjectionCommandHandlersAndDomainEventHandlers();
option.AddPoleEntityFrameworkCoreDomain(); // option.AddPoleEntityFrameworkCoreDomain();
option.AddPoleReliableMessage(messageOption => // option.AddPoleReliableMessage(messageOption =>
{ // {
messageOption.AddMasstransitRabbitmq(rabbitoption => // messageOption.AddMasstransitRabbitmq(rabbitoption =>
{ // {
rabbitoption.RabbitMqHostAddress = Configuration["RabbitmqConfig:HostAddress"]; // rabbitoption.RabbitMqHostAddress = Configuration["RabbitmqConfig:HostAddress"];
rabbitoption.RabbitMqHostUserName = Configuration["RabbitmqConfig:HostUserName"]; // rabbitoption.RabbitMqHostUserName = Configuration["RabbitmqConfig:HostUserName"];
rabbitoption.RabbitMqHostPassword = Configuration["RabbitmqConfig:HostPassword"]; // rabbitoption.RabbitMqHostPassword = Configuration["RabbitmqConfig:HostPassword"];
rabbitoption.QueueNamePrefix = Configuration["ServiceName"]; // rabbitoption.QueueNamePrefix = Configuration["ServiceName"];
rabbitoption.EventHandlerNameSuffix = "IntegrationEventHandler"; // rabbitoption.EventHandlerNameSuffix = "IntegrationEventHandler";
rabbitoption.RetryConfigure = // rabbitoption.RetryConfigure =
r => // r =>
{ // {
r.Intervals(TimeSpan.FromSeconds(0.1) // r.Intervals(TimeSpan.FromSeconds(0.1)
, TimeSpan.FromSeconds(1) // , TimeSpan.FromSeconds(1)
, TimeSpan.FromSeconds(4) // , TimeSpan.FromSeconds(4)
, TimeSpan.FromSeconds(16) // , TimeSpan.FromSeconds(16)
, TimeSpan.FromSeconds(64) // , TimeSpan.FromSeconds(64)
); // );
r.Ignore<DbUpdateException>(exception => // r.Ignore<DbUpdateException>(exception =>
{ // {
var sqlException = exception.InnerException as PostgresException; // var sqlException = exception.InnerException as PostgresException;
return sqlException != null && sqlException.SqlState == "23505"; // return sqlException != null && sqlException.SqlState == "23505";
}); // });
}; // };
}); // });
messageOption.AddMongodb(mongodbOption => // messageOption.AddMongodb(mongodbOption =>
{ // {
mongodbOption.ServiceCollectionName = Configuration["ServiceName"]; // mongodbOption.ServiceCollectionName = Configuration["ServiceName"];
mongodbOption.Servers = Configuration.GetSection("MongoConfig:Servers").Get<MongoHost[]>(); // mongodbOption.Servers = Configuration.GetSection("MongoConfig:Servers").Get<MongoHost[]>();
}); // });
messageOption.AddEventAssemblies(typeof(Startup).Assembly) // messageOption.AddEventAssemblies(typeof(Startup).Assembly)
.AddEventHandlerAssemblies(typeof(Startup).Assembly); // .AddEventHandlerAssemblies(typeof(Startup).Assembly);
messageOption.NetworkInterfaceGatewayAddress = Configuration["ReliableMessageOption:NetworkInterfaceGatewayAddress"]; // messageOption.NetworkInterfaceGatewayAddress = Configuration["ReliableMessageOption:NetworkInterfaceGatewayAddress"];
}); // });
}); //});
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{ {
app.UsePoleReliableMessage(); //app.UsePoleReliableMessage();
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
...@@ -102,7 +93,8 @@ namespace Product.Api ...@@ -102,7 +93,8 @@ namespace Product.Api
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapGrpcService<ProductTypeService>(); endpoints.MapDefaultControllerRoute();
//endpoints.MapGrpcService<ProductTypeService>();
endpoints.MapGet("/", async context => endpoints.MapGet("/", async context =>
{ {
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
......
...@@ -8,23 +8,12 @@ ...@@ -8,23 +8,12 @@
} }
}, },
"postgres": { "postgres": {
"main": "Server=192.168.0.248;Port=5432;Username=postgres;Password=comteck2020!@#;Database=Pole_samples_Product;Enlist=True;Timeout=0;Command Timeout=600;Pooling=false;MinPoolSize=20;MaxPoolSize=500;" "write": "Server=115.238.140.58;Port=5432;Username=postgres;Password=comteck2020!@#;Database=Pole-Product;Enlist=True;Timeout=0;Command Timeout=600;Pooling=false;MinPoolSize=20;MaxPoolSize=500;"
}, },
"ServiceName": "Product", "ServiceName": "Product",
"RabbitmqConfig": { "RabbitmqConfig": {
"HostAddress": "rabbitmq://192.168.0.248/", "HostAddress": "rabbitmq://192.168.0.248/",
"HostUserName": "comteck", "HostUserName": "comteck",
"HostPassword": "comteck3030" "HostPassword": "comteck3030"
},
"MongoConfig": {
"Servers": [
{
"Host": "192.168.0.248",
"Port": "27017"
}
]
},
"ReliableMessageOption": {
"NetworkInterfaceGatewayAddress": "192.168.0.1"
} }
} }
...@@ -8,28 +8,17 @@ ...@@ -8,28 +8,17 @@
} }
}, },
"postgres": { "postgres": {
"main": "Server=192.168.0.248;Port=5432;Username=postgres;Password=comteck2020!@#;Database=Pole_samples_Product;Enlist=True;Timeout=0;Command Timeout=600;Pooling=false;MinPoolSize=20;MaxPoolSize=500;" "write": "Server=192.168.0.248;Port=5432;Username=postgres;Password=comteck2020!@#;Database=Pole_samples_Product;Enlist=True;Timeout=0;Command Timeout=600;Pooling=false;MinPoolSize=20;MaxPoolSize=500;"
}, },
"ServiceName": "Product", "ServiceName": "Product",
"RabbitmqConfig": { "RabbitmqConfig": {
"HostAddress": "rabbitmq://192.168.0.248/", "HostAddress": "rabbitmq://192.168.0.248/",
"HostUserName": "comteck", "HostUserName": "comteck",
"HostPassword": "comteck3030" "HostPassword": "comteck3030"
},
"MongoConfig": {
"Servers": [
{
"Host": "192.168.0.248",
"Port": "27017"
}
]
},
"ReliableMessageOption": {
"NetworkInterfaceGatewayAddress": "192.168.0.1"
},
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
} }
//"Kestrel": {
// "EndpointDefaults": {
// "Protocols": "Http2"
// }
//}
} }
using Pole.Core.EventBus.Event;
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Core.Domain
{
public abstract class Entity
{
string _id;
public virtual string Id
{
get
{
return _id;
}
set
{
_id = value;
}
}
public List<IEvent> DomainEvents { get; private set; }
public bool IsTransient()
{
return string.IsNullOrEmpty(this._id);
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is Entity))
return false;
if (Object.ReferenceEquals(this, obj))
return true;
if (this.GetType() != obj.GetType())
return false;
Entity item = (Entity)obj;
if (item.IsTransient() || this.IsTransient())
return false;
else
return item.Id == this.Id;
}
public override int GetHashCode()
{
return this.Id.GetHashCode();
}
public static bool operator ==(Entity left, Entity right)
{
if (Object.Equals(left, null))
return (Object.Equals(right, null)) ? true : false;
else
return left.Equals(right);
}
public static bool operator !=(Entity left, Entity right)
{
return !(left == right);
}
public void AddDomainEvent(IEvent eventItem)
{
DomainEvents = DomainEvents ?? new List<IEvent>();
DomainEvents.Add(eventItem);
}
public void RemoveDomainEvent(IEvent eventItem)
{
if (DomainEvents is null) return;
DomainEvents.Remove(eventItem);
}
public void ClearDomainEvents()
{
DomainEvents?.Clear();
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Core.Domain
{
public interface IAggregateRoot { }
}
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Orleans;
using Orleans.Providers;
using Orleans.Runtime;
using Orleans.Storage;
using Pole.Orleans.Provider.EntityframeworkCore.Conventions;
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public static class GrainStorageServiceCollectionExtensions
{
public static IServiceCollection ConfigureGrainStorageOptions<TContext, TGrain, TEntity>(
this IServiceCollection services,
Action<GrainStorageOptions<TContext, TGrain, TEntity>> configureOptions = null)
where TContext : DbContext
where TGrain : Grain<TEntity>
where TEntity : class, new()
{
return services
.AddSingleton<IPostConfigureOptions<GrainStorageOptions<TContext, TGrain, TEntity>>,
GrainStoragePostConfigureOptions<TContext, TGrain, TEntity, TEntity>>()
.Configure<GrainStorageOptions<TContext, TGrain, TEntity>>(typeof(TGrain).FullName, options =>
{
configureOptions?.Invoke(options);
});
}
public static IServiceCollection ConfigureGrainStorageOptions<TContext, TGrain, TGrainState, TEntity>(
this IServiceCollection services,
Action<GrainStorageOptions<TContext, TGrain, TEntity>> configureOptions = null)
where TContext : DbContext
where TGrain : Grain<TGrainState>
where TGrainState : new()
where TEntity : class
{
return services
.AddSingleton<IPostConfigureOptions<GrainStorageOptions<TContext, TGrain, TEntity>>,
GrainStoragePostConfigureOptions<TContext, TGrain, TGrainState, TEntity>>()
.Configure<GrainStorageOptions<TContext, TGrain, TEntity>>(typeof(TGrain).FullName, options =>
{
configureOptions?.Invoke(options);
});
}
public static IServiceCollection AddEfGrainStorage<TContext>(
this IServiceCollection services,
string providerName = ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME)
where TContext : DbContext
{
services.TryAddSingleton(typeof(IEntityTypeResolver), typeof(EntityTypeResolver));
services.TryAddSingleton(typeof(IGrainStorageConvention), typeof(GrainStorageConvention));
services.TryAddSingleton(typeof(IGrainStateEntryConfigurator<,,>),
typeof(DefaultGrainStateEntryConfigurator<,,>));
services.AddSingleton(typeof(EntityFrameworkGrainStorage<TContext>));
services.TryAddSingleton<IGrainStorage>(sp =>
sp.GetServiceByName<IGrainStorage>(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME));
services.AddSingletonNamedService<IGrainStorage>(providerName,
(sp, name) => sp.GetRequiredService<EntityFrameworkGrainStorage<TContext>>());
return services;
}
}
}
using Microsoft.EntityFrameworkCore;
using Orleans.Hosting;
using Orleans.Providers;
using System;
using System.Collections.Generic;
using System.Text;
namespace Pole.Orleans.Provider.EntityframeworkCore
{
public static class GrainStorageSiloHostBuilderExtensions
{
public static ISiloHostBuilder AddEfGrainStorageAsDefault<TContext>(this ISiloHostBuilder builder)
where TContext : DbContext
{
return builder.AddEfGrainStorage<TContext>(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME);
}
public static ISiloHostBuilder AddEfGrainStorage<TContext>(this ISiloHostBuilder builder,
string providerName)
where TContext : DbContext
{
return builder
.ConfigureServices(services => { services.AddEfGrainStorage<TContext>(providerName); });
}
public static ISiloBuilder AddEfGrainStorageAsDefault<TContext>(this ISiloBuilder builder)
where TContext : DbContext
{
return builder.AddEfGrainStorage<TContext>(ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME);
}
public static ISiloBuilder AddEfGrainStorage<TContext>(this ISiloBuilder builder,
string providerName)
where TContext : DbContext
{
return builder
.ConfigureServices(services => { services.AddEfGrainStorage<TContext>(providerName); });
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment