.NET Core微服务之服务间的调用方式(REST and RPC) - EdisonZhou - 博客园

mikel阅读(502)

来源: .NET Core微服务之服务间的调用方式(REST and RPC) – EdisonZhou – 博客园

Tip: 此篇已加入.NET Core微服务基础系列文章索引

一、REST or RPC ?

1.1 REST & RPC

微服务之间的接口调用通常包含两个部分,序列化和通信协议。常见的序列化协议包括json、xml、hession、protobuf、thrift、text、bytes等;通信比较流行的是http、soap、websockect,RPC通常基于TCP实现,常用框架例如dubbo,netty、mina、thrift。

REST:严格意义上说接口很规范,操作对象即为资源,对资源的四种操作(post、get、put、delete),并且参数都放在URL上,但是不严格的说Http+json、Http+xml,常见的http api都可以称为Rest接口。

RPC:即我们常说的远程过程调用,就是像调用本地方法一样调用远程方法,通信协议大多采用二进制方式。

1.2 HTTP vs 高性能二进制协议

HTTP相对更规范,更标准,更通用,无论哪种语言都支持HTTP协议。如果你是对外开放API,例如开放平台,外部的编程语言多种多样,你无法拒绝对每种语言的支持,相应的,如果采用HTTP,无疑在你实现SDK之前,支持了所有语言,所以,现在开源中间件,基本最先支持的几个协议都包含RESTful。

RPC协议性能要高的多,例如Protobuf、Thrift、Kyro等,(如果算上序列化)吞吐量大概能达到http的二倍。响应时间也更为出色。千万不要小看这点性能损耗,公认的,微服务做的比较好的,例如,netflix、阿里,曾经都传出过为了提升性能而合并服务。如果是交付型的项目,性能更为重要,因为你卖给客户往往靠的就是性能上微弱的优势。

所以,最佳实践一般是对外REST,对内RPC,但是追求极致的性能会消耗很多额外的成本,所以一般情况下对内一般也REST,但对于个别性能要求较高的接口使用RPC。

二、案例结构

这里假设有两个服务,一个ClinetService和一个PaymentService,其中PaymentService有两部分,一部分是基于REST风格的WebApi部分,它主要是负责一些对性能没有要求的查询服务,另一部分是基于TCP的RPC Server,它主要是负责一些对性能要求高的服务,比如支付和支出等涉及到钱的接口。假设User在消费ClientService时需要调用PaymentService根据客户账户获取Payment History(走REST)以及进行交易事务操作(走RPC)。

三、REST调用

3.1 一个好用的REST Client : WebApiClient

使用过Java Feign Client的人都知道,一个好的声明式REST客户端可以帮我们省不少力。在.NET下,园子里的大大老九就写了一款类似于Feign Client的REST Client:WebApiClient。WebApiClient是开源在github上的一个httpClient客户端库,内部基于HttpClient开发,是一个只需要定义C#接口(interface),并打上相关特性,即可异步调用http-api的框架 ,支持.net framework4.5+、netcoreapp2.0和netstandard2.0。它的GitHub地址是:https://github.com/dotnetcore/WebApiClient

如何安装?

NuGet>Install-Package WebApiClient-JIT

3.2 使用实例:走API Gateway

Step1.定义HTTP接口

复制代码
    [HttpHost("http://yourgateway:5000")]
    public interface IPaymentWebApi: IHttpApi
    {
        // GET api/paymentservice/history/edisonzhou
        // Return 原始string内容
        [HttpGet("/api/paymentservice/history/{account}")]
        ITask<IList<string>> GetPaymentHistoryByAccountAsync(string account);
    }
复制代码

这里需要注意的是,由于我们要走API网关,所以这里定义的HttpHost地址是一个假的,后面具体调用时会覆盖掉,当然你也可以直接把地址写在这里,不过我更倾向于写到配置文件中,然后把这里的HttpHost设置注释掉。

Step2.在Controller中即可异步调用:

复制代码
    [Route("api/[controller]")]
    public class PaymentController : Controller
    {
        private readonly string gatewayUrl;public PaymentController(IConfiguration _configuration)
        {
            gatewayUrl = _configuration["Gateway:Uri"];
        }

        [HttpGet("{account}")]
        public async Task<IList<string>> Get(string account)
        {
            using (var client = HttpApiClient.Create<IPaymentWebApi>(gatewayUrl))
            {
                var historyList = await client.GetPaymentHistoryByAccountAsync(account);
                // other business logic code here
                // ......
                return historyList;
            }
        }
  }
复制代码

当然你也可以在Service启动时注入一个单例的IPaymentServiceWebApi实例,然后直接在各个Controller中直接使用,这样更加类似于Feign Client的用法:

(1)StartUp类注入

复制代码
    public void ConfigureServices(IServiceCollection services)
    {

        // IoC - WebApiClient
        services.AddSingleton(HttpApiClient.Create<IPaymentServiceWebApi>(Configuration["PaymentService:Url"]));

    }
复制代码

(2)Controller中直接使用

复制代码
    [HttpPost]
    public async Task<string> Post([FromBody]ModelType model, [FromServices]IPaymentServiceWebApi restClient)
    {
        ......
        var result = await restClient.Save(model);
        ......
    }
复制代码

这里PaymentService的实现很简单,就是返回了一个String集合:

复制代码
    // GET api/history/{account}
    [HttpGet("{account}")]
    public IList<string> Get(string account)
    {
        // some database logic
        // ......
        IList<string> historyList = new List<string>
        {
            "2018-06-10,10000RMB,Chengdu",
            "2018-06-11,11000RMB,Chengdu",
            "2018-06-12,12000RMB,Beijing",
            "2018-06-13,10030RMB,Chengdu",
            "2018-06-20,10400RMB,HongKong"
        };

        return historyList;
    }
复制代码

最终调用结果如下:

3.3 使用实例:直接访问具体服务

在服务众多,且单个服务就部署了多个实例的情况下,我们可以通过API网关进行中转,但是当部分场景我们不需要通过API网关进行中转的时候,比如:性能要求较高,负载压力较小单个实例足够等,我们可以直接与要通信的服务进行联接,也就不用从API网关绕一圈。

Step1.改一下HTTP接口:

复制代码
    [HttpHost("http://paymentservice:8880")]
    public interface IPaymentDirectWebApi: IHttpApi
    {
        // GET api/paymentservice/history/edisonzhou
        // Return 原始string内容
        [HttpGet("/api/history/{account}")]
        ITask<IList<string>> GetPaymentHistoryByAccountAsync(string account);
    }
复制代码

同理,这里的HttpHost也是后面需要被覆盖的,原因是我们将其配置到了配置文件中。

Step2.改一下调用代码:

复制代码
    [Route("api/[controller]")]
    public class PaymentController : Controller
    {
        private readonly string gatewayUrl;
        private readonly string paymentServiceUrl;

        public PaymentController(IConfiguration _configuration)
        {
            gatewayUrl = _configuration["Gateway:Uri"];
            paymentServiceUrl = _configuration["PaymentService:Uri"];
        }

        [HttpGet("{account}")]
        public async Task<IList<string>> Get(string account)
        {
            #region v2 directly call PaymentService
            using (var client = HttpApiClient.Create<IPaymentDirectWebApi>(paymentServiceUrl))
            {
                var historyList = await client.GetPaymentHistoryByAccountAsync(account);
                // other business logic code here
                // ......
                return historyList;
            }
            #endregion
        }
复制代码

最终调用结果如下:

四、RPC调用

4.1 Thrift简介

Thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Go,Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。

当然,还有gRPC也可以选择,不过从网上的性能测试来看,Thrift性能应该优于gRPC 2倍以上,但是gRPC的文档方面要比Thrift友好很多。

4.2 Thrift的使用

(1)下载Thrift (这里选择Windows版)

下载完成后解压,这里我将其改名为thrift.exe(去掉了版本号),一会在命令行敲起来更方便一点。

(2)编写一个PaymentService.thrift,这是一个IDL中间语言

复制代码
namespace csharp Manulife.DNC.MSAD.Contracts

service PaymentService { 
    TrxnResult Save(1:TrxnRecord trxn) 
}

enum TrxnResult { 
    SUCCESS = 0, 
    FAILED = 1, 
}

struct TrxnRecord { 
    1: required i64 TrxnId; 
    2: required string TrxnName; 
    3: required i32 TrxnAmount; 
    4: required string TrxnType; 
    5: optional string Remark; 
}
复制代码

(3)根据thrift语法规则生成C#代码

cmd>thrift.exe -gen csharp PaymentService.thrift

(4)创建一个Contracts类库项目,将生成的C#代码放进去

4.3 增加RPC Server

(1)新增一个控制台项目,作为我们的Payment Service RPC Server,并引用Contracts类库项目

(2)引入thrift-netcore包:

NuGet>Install-Package apache-thrift-netcore

(3)加入一个新增的PaymentService实现类

复制代码
    public class PaymentServiceImpl : Manulife.DNC.MSAD.Contracts.PaymentService.Iface
    {
        public TrxnResult Save(TrxnRecord trxn)
        {
            // some business logic here
            //Thread.Sleep(1000 * 1);
            Console.WriteLine("Log : TrxnName:{0}, TrxnAmount:{1}, Remark:{2}", trxn.TrxnName, trxn.TrxnAmount, trxn.Remark);
            return TrxnResult.SUCCESS;
        }
    }
复制代码

这里输出日志仅仅是为了测试。

(4)编写启动RPC Server的主程序

复制代码
    public class Program
    {
        private const int port = 8885;

        public static void Main(string[] args)
        {
            Console.WriteLine("[Welcome] PaymentService RPC Server is lanuched...");
            TServerTransport transport = new TServerSocket(port);
            var processor = new Manulife.DNC.MSAD.Contracts.PaymentService.Processor(new PaymentServiceImpl());
            TServer server = new TThreadedServer(processor, transport);
            // lanuch
            server.Serve();
        }
    }
复制代码

(5)如果是多个服务实现的话,也可以如下这样启动:

复制代码
    public static void Main(string[] args)
    {
        Console.WriteLine("[Welcome] PaymentService RPC Server is lanuched...");
        TServerTransport transport = new TServerSocket(port);
        var processor1 = new Manulife.DNC.MSAD.Contracts.PaymentService.Processor(new PaymentServiceImpl());
        var processor2 = new Manulife.DNC.MSAD.Contracts.PayoutService.Processor(new PayoutServiceImpl());
        var processorMulti = new Thrift.Protocol.TMultiplexedProcessor();
        processorMulti.RegisterProcessor("Service1", processor1);
        processorMulti.RegisterProcessor("Service2", processor2);
        TServer server = new TThreadedServer(processorMulti, transport);
        // lanuch
        server.Serve();
    }
复制代码

4.4 调用RPC

在ClientService中也引入apache-thrift-netcore包,然后在调用的地方修改如下:

复制代码
    [HttpPost]
    public string Post([FromBody]TrxnRecordDTO trxnRecordDto)
    {
        // RPC - use Thrift
        using (TTransport transport = new TSocket(
            configuration["PaymentService:RpcIP"], 
            Convert.ToInt32(configuration["PaymentService:RpcPort"])))
        {
            using (TProtocol protocol = new TBinaryProtocol(transport))
            {
                using (var serviceClient = new PaymentService.Client(protocol))
                {
                    transport.Open();
                    TrxnRecord record = new TrxnRecord
                    {
                        TrxnId = GenerateTrxnId(), 
                        TrxnName = trxnRecordDto.TrxnName,
                        TrxnAmount = trxnRecordDto.TrxnAmount,
                        TrxnType = trxnRecordDto.TrxnType,
                        Remark = trxnRecordDto.Remark
                    };
                    var result = serviceClient.Save(record);

                    return Convert.ToInt32(result) == 0 ? "Trxn Success" : "Trxn Failed";
                }
            }
        }
    }

    private long GenerateTrxnId()
    {
        return 10000001;
    }
复制代码

最终测试结果如下:

五、小结

本篇简单的介绍了下微服务架构下服务之间调用的两种常用方式:REST与RPC,另外前面介绍的基于消息队列的发布/订阅模式也是服务通信的方式之一。本篇基于WebApiClient这个开源库介绍了如何进行声明式的REST调用,以及Thrift这个RPC框架介绍了如何进行RPC的通信,最后通过一个小例子来结尾。最后,服务调用的最佳实践一般是对外REST,对内RPC,但是追求极致的性能会消耗很多额外的成本,所以一般情况下对内一般也REST,但对于个别性能要求较高的接口使用RPC。

参考资料

远方的行者,《微服务 RPC和REST

杨中科,《.NET Core微服务课程:Thrift高效通讯

醉眼识朦胧,《Thrift入门初探–thrift安装及java入门实例

focus-lei,《.net core下使用Thrift

宝哥在路上,《Thrift性能测试与分析

.NetCore使用protobuf 生成C#代码(Grpc) - zeran - 博客园

mikel阅读(1156)

来源: .NetCore使用protobuf 生成C#代码(Grpc) – zeran – 博客园

我使用vs2019,需要安装几个nuget包

Google.protobuf

Google.protobuf.Tools

Grpc.AspnetCore

Grpc.Net.Client

Grpc.Tools

编写.proto文件

复制代码
syntax ="proto3";

option csharp_namespace="WeService01.Controllers";
package WeService01.Controllers;

message users{
int32 ID=1;
string name=2;
string login_name=3;
int32 roleid=4;
bool is_man=5;
}
message getusers{
int32 ID=1;
string name=2;
}
message getusersresponse{
int32 code=1;
string msg=2;
users usermodel =3;
}
service userservice{
 rpc Getuser(getusers) returns (getusersresponse);
 rpc Add(users) returns (getusersresponse);
};
复制代码

下载proto的是生成包

https://github.com/protocolbuffers/protobuf/releases

下载protoc编译工具并解压

 

然后执行命令,也可以在vs的“程序包管理器控制台”执行(E:\protoc-3.15.2-win64\bin\protoc 可以看命令参数)
E:\protoc-3.15.2-win64\bin\protoc user.proto –csharp_out=E:\gitee_public\WeService01\WeService01\proto –proto_path=E:\gitee_public\WeService01\WeService01\proto

E:\protoc-3.15.2-win64\bin\为protoc.exe所在位置,–csharp_out 为生成文件的位置,–proto_path为需要编译的.proto文件的目录

 

Github上优秀的.NET Core项目 - lingfeng95 - 博客园

mikel阅读(687)

来源: Github上优秀的.NET Core项目 – lingfeng95 – 博客园

Github上优秀的.NET Core开源项目的集合。内容包括:库、工具、框架、模板引擎、身份认证、数据库、ORM框架、图片处理、文本处理、机器学习、日志、代码分析、教程等。

Github地址:https://github.com/jasonhua95/awesome-dotnet-core ,【awesome-dotnet-core】

其中的翻译有可能有问题,大家发现了及时提出来,其他的比较好的项目也可以提出来,我会及时添加修改上去的。

一般

框架, 库和工具

API

  • autorest – Swagger(OpenAPI)规范代码生成器,具有C#和Razor模板。支持C#,Java,Node.js,TypeScript,Python和Ruby。
  • aspnet-api-versioning – 提供一组库,这些库可将服务API版本添加到ASP.NET Web API,具有ASP.NET Web API的OData和ASP.NET Core。
  • AspNetCoreRateLimit – ASP.NET限速中间件。
  • CondenserDotNet – 使用Kestrel和Consul的API Condenser / Reverse Proxy,包括轻量级consul库。
  • Flurl – 适用于.NET的Fluent URL构建器和可测试的HTTP。
  • GraphQL
    • Dapper.GraphQL – 一个旨在将Dapper和graphql-dotnet项目集成在一起的库,主要考虑的是易用性和性能。
    • graphql-aspnetcore – ASP.NET Core MiddleWare创建GraphQL端点。
    • graphql-convention – 该库是GraphQL的补充层,使您可以使用现有的属性和方法作为字段解析器,将.NET类自动包装到GraphQL模式定义中。
    • graphiql-dotnet – 用于ASP.NET Core的GraphiQL中间件。
    • graphql-dotnetcore – 基于graphql-js的.NETQL GraphQL。
    • graphql-dotnet – GraphQL for .NET。
    • graphql-dotnet-server – GraphQL for .NET – 订阅传输WebSockets。
    • Hot Chocolate – .Net Core和.NET Framework的GraphQL服务器。
    • FSharp.Data.GraphQL – Facebook GraphQL查询语言的FSharp实现。
    • parser – .NET中GraphQL的词法分析器和解析器。
    • tanka-graphql – GraphQL执行库和服务器库,支持SignalR,Apollo,模式操纵以及Apollo和graphql-js熟悉的其他功能。
  • halcyon – ASP.NET的HAL实现。
  • JSON API .NET Core – 用于ASP.Net Core的JSON API框架。
  • LightNode – 基于OWIN构建的Micro RPC / REST框架
  • NetCoreStack.Proxy – 适用于.NET Standard 2.0的类型安全的分布式REST库(NetCoreStack Flying Proxy)
  • NSwag – 用于.NET,Web API和TypeScript的Swagger / OpenAPI工具链。
  • OData – 开放数据协议(OData)支持创建基于HTTP的数据服务,允许使用统一资源标识符识别资源( URIs)并在抽象数据模型中定义,由Web客户端使用简单的HTTP消息进行发布和编辑。
  • OpenAPI Generator – 可以通过 OpenAPI Generator,在给定 OpenAPI 规范(v2, v3)的情况下自动生成 API 客户端库、server stubs、文档以及配置。
  • refit – 适用于.NET Core,Xamarin和.NET的自动类型安全REST库。
  • RestClient.Net – 适用于所有C#跨平台的REST客户端。
  • RestEase – 易于使用的类型安全REST API客户端库,简单且可自定义。
  • RestLess – .Net Standard的自动类型安全无反射REST API客户端库。
  • Restier – RESTier是一个RESTful API开发框架,用于在.NET平台上构建基于OData V4的标准化RESTful服务。
  • Restsharp – 用于.NET的简单REST和HTTP API客户端
  • Swashbuckle – Swagger工具,生成API文档,包括用于测试的UI。
  • WebAPIContrib for ASP.NET CORE – ASP.NET Core的附加组件和扩展库。

应用程序框架

  • ASP.NET Boilerplate – ABP是一个通用的WEB应用程序框架和项目模板。
  • Abp vNext – 该项目是ABP Web应用程序框架的下一代。
  • AsyncEx – async / await的帮助程序库。
  • Aeron.NET – 高效可靠的UDP单播,UDP组播和IPC消息传输。
  • akka.net – Akka是一个基于scala语言的Actor模型库,旨在构建一套高并发、分布式、自动容错、消息驱动应用的工具集。
  • Aggregates.NET – Aggregates.NET是一个框架,可以帮助开发人员将优秀的NServiceBus和EventStore库集成在一起。
  • ASP.NET MVC – 官方WEB应用程序框架,MVC。
  • ASP.NET Core – ASP.NET Core是一个跨平台的.NET框架。
  • Butterfly Server .NET – 允许用最少的工作量构建实时Web应用程序,分布式追踪的服务器端库。
  • CAP – CAP是处理分布式事务的解决方案,还具有EventBus功能,它轻巧,易于使用且高效。
  • Carter – Carter是一个路由框架,使代码更加简单明确。
  • Chromely – Electron.NET的轻量级替代品,构建HTML5桌面应用程序框架。
  • Cinchoo ETL – 用于.NET的ETL框架(用于CSV,Flat,Xml,JSON,键值对格式文件的分析器/写入器)。
  • CQRSlite – 用于帮助在C#中编写CQRS和Eventsourcing应用程序的轻量级框架。
  • dataaccess_aspnetcore – EF的UnitOfWork和Repositories的基类。
  • DNTFrameworkCore – DNTFrameworkCore 是一个轻量级且可扩展的基础结构,用于基于ASP.NET Core构建高质量的Web应用程序
  • DotNetCorePlugins – 用于动态加载.NET Core程序集,将其作为主应用程序的扩展来执行与Assembly.LoadFrom不同。
  • DotnetSpider – DotnetSpider,一个类似于WebMagic和Scrapy的.NET标准爬虫库。它是轻量级,高效且快速的高级Web爬网和抓取框架。
  • DotNetty – netty端口,事件驱动的异步网络应用程序框架。
  • dotvvm – Web应用程序的开源MVVM框架。
  • ElectronNET – 使用ASP.NET NET Core构建跨平台桌面应用程序。
  • EmbedIO – 一个小型的,跨平台,基于模块的Web服务器。
  • Ether.Network – Ether.Network是一个开源网络库,允许开发人员通过sockets创建简单,快速和可扩展的套接字服务器或客户端的基本库。
  • EventFlow – EventFlow是一个易于使用的基本CQRS + ES框架。
  • ExcelDataReader – 用C#编写的轻量级快速库,用于读取Microsoft Excel文件。
  • ExtCore – 用于创建模块化和可扩展的Web应用程序框​​架。
  • Finbuckle.MultiTenant – Finbuckle.MultiTenant是ASP.NET Core的多租户库。它提供用于租户解析,每个租户应用程序配置和每个租户数据隔离的功能。
  • fission – Fission 是一个构建在 Kubernetes 之上的 FaaS框架。ission 利用Kubernetes 集群管理、调度、网络管理等,将容器编排功能留给 Kubernetes,而 Fission 就专注于 FaaS 特性。
  • grpc – 远程过程调用(RPC)为构建分布式应用程序和服务提供了有用的抽象,grpc库。
  • Halibut – 使用基于SSL的JSON-RPC的.NET安全通信框架。
  • MagicOnion – MagicOnion是一个实时网络引擎,如SignalR,Socket.io和RPC-Web API框架。
  • MassTransit – .NET分布式应用程序框架。
  • microdot – 一个开源的.NET微服务框架。
  • MoreLINQ – LINQ to Objects的扩展。
  • Nancy – 用于在.NET和Mono上构建基于HTTP的服务的轻量级框架。
  • opencvsharp – OpenCV的跨平台库。
  • orleans – Orleans是一个跨平台的,用于构建分布式应用程序框架
  • protoactor-dotnet – Golang和C#的快速分布式Actor。
  • resin – 面向文档的搜索引擎,具有列索引,多重集合查询,基于JSON的查询语言和HTTP API。
  • RService.io – 用于ASP.NET Core的轻量级REST服务框架
  • ServiceStack – ServiceStack是一个简单,快速,通用和高效的全功能Web和 Web服务框架。
  • Steeltoe OSS – 用于常见微服务模式的.NET工具包。
  • Strathweb.TypedRouting.AspNetCore – 一个在ASP.NET Core MVC项目中启用强类型路由的库。
  • Xer.Cqrs – 轻巧易用的CQRS + DDD库。
  • X.PagedList – 用于轻松分页ASP.NET / ASP.NET Core中任何IEnumerable / IQueryable的库。

应用程序模板

身份认证和授权

区块链

  • BTCPayServer – BTCPay Server是一个免费的开源加密货币支付处理器,它使您可以直接以比特币和山寨币接收支付,而无需任何费用,交易成本或中间商。
  • Meadow – 一个集成的以太坊实施和工具套件,专注于Solidity测试和开发。
  • NBitcoin – 用于.NET框架的综合比特币库。
  • NBlockchain – 用于构建支持区块链的应用程序的.NET标准库
  • NBXplorer – 比特币和NBitcoin资源管理器客户端。
  • NEO – 为智能经济打造的开放网络,Neo利用区块链技术。
  • Nethereum – 将以太坊的热爱带到.NET。
  • Nethermind – .NET Core以太坊客户端
  • StratisBitcoinFullNode – 简单且经济实惠的端到端解决方案,用于在.Net框架上开发,测试和部署本机C#区块链应用程序。
  • Trezor.Net – Trezor加密货币硬件钱包的跨平台C#库。
  • WalletWasabi – 注重隐私的比特币钱包。内置Tor,CoinJoin和硬币控制功能。

机器人

  • BotSharp – BotSharp是AI Bot平台构建者的开源机器学习框架。
  • NadekoBot – 用C#编写的开源,通用的Discord聊天机器人。
  • Telegram.Bot – Telegram Bot API客户端。
  • Funogram – F#Telegram Bot Api库。

自动部署

  • cake-build – 跨平台构建自动化系统。
  • Colorful.Console – 设置您的C#控制台输出样式!
  • dotnet-docker – 用于.NET Core和.NET Core Tools的基本Docker镜像。
  • Dockerize.NET – .NET Cli工具,用于将.NET Core应用程序打包到Docker映像中:“ dotnet dockerize”
  • FlubuCore – 跨平台构建和自动化部署系统,用C#代码构建项目,执行,部署脚本。
  • GitInfo – 来自MSBuild,C#和VB的Git和SemVer信息,一种MSBuild编译工具。
  • GitVersioning – 使用version.json文件生成的唯一版本标记程序集和程序包等,并包括用于非官方构建的git commit ID。
  • go-dotnet – .NET Core Runtime的PoC Go包装器。
  • Image2Docker – 将现有Windows应用程序工作,移植到Docker的PowerShell模块。
  • LocalAppVeyor – .NET Core全局工具,可将appveyor.yml部署AppVeyor到本地。
  • msbuild – Microsoft Build Engine是一个用于构建应用程序的平台。
  • Nuke – 跨平台构建自动化系统。
  • Opserver – Stack Exchange的监控系统。
  • vsts-agent – Visual Studio Team Services构建和发布代理。

css, js帮助工具

  • BundlerMinifier – Visual Studio扩展,让您可以配置JS,CSS和HTML文件的捆绑和缩小。
  • JavaScriptViewEngine – 用于在JavaScript环境中呈现标记的ASP.NET MVC ViewEngine。适用于React和Angular服务器端呈现。
  • Smidge – 用于ASP.NET Core的轻量级运行时CSS / JavaScript文件缩小,组合,压缩和管理库。
  • Web Markup Minifier – 包含一组标记最小化器的.NET库。该项目的目标是通过减少HTML,XHTML和XML代码的大小来提高Web应用程序的性能。

缓存

  • CacheManager – 用C#编写的.NET的开源缓存抽象层。它支持各种缓存提供程序并实现许多高级功能。
  • EasyCaching – 开源缓存库,包含基本用法和缓存的一些高级用法,可以帮助我们更轻松地处理缓存。
  • Faster – Microsoft的快速key,value存储库。
  • Foundatio – 用于构建分布式应用程序的可插入基础库。
  • Microsoft Caching – 用于内存缓存和分布式缓存的库。
  • Stack Exchange Redis – 用于.NET语言的高性能通用redis客户端(C#等)。

内容管理系统CMS

  • Awesome-CMS-Core – Awesome-CMS-Core是一个使用ASP.Net Core和ReactJS构建的开源CMS,考虑到模块分离问题并提供最新的技术趋势,如.Net Core,React,Webpack,SASS,后台作业,Message Queue。
  • Blogifier.Core – Blogifier是用ASP.NET Core编写的简单,美观,轻巧的开源博客。
  • Cofoundry – Cofoundry是一个可扩展且灵活的.NET Core CMS和应用程序框架,专注于代码优先开发。
  • CoreWiki – 一个简单的ASP.NET core wiki。
  • dasblog-core – DasBlog博客项目。
  • Lynicon – Lynicon CMS系统。
  • Miniblog – ASP.NET Core博客引擎。
  • NetCoreCMS – NetCoreCMS是使用ASP.Net Core 2.0 MVC开发的模块化主题支持的内容管理系统。
  • Orchard Core CMS – 在模块化和可扩展的应用程序框架之上使用ASP.NET Core构建的开源内容管理系统。
  • Piranha CMS – 用于ASP.NET核心和实体框架核心的轻量级且不显眼的开源CMS。
  • Platformus – 基于ASP.NET Core 1.0和ExtCore框架的免费,开源和跨平台的CMS。
  • SimpleContent – 用于ASP.NET Core的简单而灵活的内容和博客引擎,可以使用或不使用数据库。
  • Squidex – Squidex是一个开源的CMS,基于MongoDB,CQRS和事件。
  • Swastika I/O Core CMS – 基于SIOH框架的ASP.NET Core / Dotnet核心系统(例如CMS,电子商务,论坛,问题解答,CRM …)
  • Weapsy – 基于DDD和CQRS的开源ASP.NET核心CMS。它支持开箱即用的MSSQL,MySQL,SQLite和PostgreSQL。
  • Wyam – 模块化静态内容和静态站点生成器。
  • ZKEACMS – 视觉设计,通过拖放构建网站

代码分析和指标

  • awesome-static-analysis – 针对各种编程语言的静态分析工具,链接和代码质量检查器的精选列表。
  • Code Analysis
    • CodeFormatter – CodeFormatter是使用Roslyn来自动重写我们的代码格式。
    • DevSkim – DevSkim是IDE扩展和语言分析器的框架,可在开发人员编写代码时在开发环境中提供内联安全性分析。
    • RefactoringEssentials – Visual Studio扩展工具,支持分析和重构代码。
    • roslyn-analyzers – Roslyn分析器分析您的代码的样式,质量和可维护性,设计和其他问题。
    • StyleCopAnalyzers – StyleCop规则实现的.NET编译器平台。
  • Metrics
    • AppMetrics – 用于记录和报告应用程序中的指标。
    • Audit.NET – 一个可扩展的框架,用于审核.NET和.NET Core中的执行操作。
    • BenchmarkDotNet – 用于基准测试的强大.NET库。
    • coverlet – Coverlet是.NET的跨平台代码覆盖框架。
    • MiniCover – 跨平台代码覆盖工具
    • NBench – .NET应用程序的性能基准测试框架
    • Nexogen.Libraries.Metrics – 用于在.NET中收集应用程序指标并将其导出到Prometheus的库。
    • OpenCover – 代码覆盖工具(仅适用于WINDOWS OS)
    • PerformanceMonitor – .NET应用程序性能监视器。
    • prometheus-net – .NET指标,监视系统,检测应用程序的prometheus库。
    • Prometheus.Client – Prometheus客户端。

压缩

  • lz4net – 适用于所有.NET平台的超快速压缩算法。
  • sharpcompress – 完全管理的C#库,用于处理许多压缩类型和格式。

编译器

  • Fable – F#到JavaScript编译器。
  • fparsec – F#和C#的解析器组合库。
  • IL2C – IL2C-ECMA-335 CIL / MSIL到C语言的翻译器。
  • Mond – 用C#编写的动态类型脚本语言,带有REPL,调试器和简单的嵌入API。
  • peachpie – .NET的开源PHP编译器。
  • Pidgin – 用于C#的轻量级,快速且灵活的解析库,由Stack Overflow开发。
  • roslyn – Roslyn .NET编译器提供具有丰富代码分析API的C#和Visual Basic语言。
  • Sprache – 小型,友好的C#解析器框架。

密码

  • BCrypt.Net – BCrypt密码库。
  • BCrypt.NET-Core – 用于安全存储密码的BCrypt.NET库。
  • BouncyCastle PCL – Bouncy Castle Crypto包是加密算法和协议的库。
  • multiformats – 用于编码/解码Multihashes的库,它是一个“容器”,用于描述计算摘要的散列算法。
  • nsec – NSec是基于libsodium的.NET Core新加密库。
  • SecurityDriven.Inferno – 专业的加密库。

数据库

  • DBreeze – C#.NET MONO NOSQL(嵌入式键值存储)ACID多范例数据库管理系统。
  • JsonFlatFileDataStore – 简单的JSON平面文件数据存储,支持打字和动态数据。
  • LiteDB – LiteDB是一个小型,快速,轻量的NoSQL嵌入式数据库。
  • NoDb – 文档数据库,个人博客和网站以及小型小册子网站是不使用数据库的不错的选择。
  • marten – Postgresql作为.NET应用程序的文档数据库和事件存储的库。
  • StringDB – StringDB是一个模块化的键/值对档案数据库,旨在消耗少量的ram并生成少量的数据库。
  • yessql – 适用于任何RDBMS的.NET文档数据库。

数据库驱动程序

数据库工具库

  • DbUp – 可帮助您将更改部署到SQL Server数据库,跟踪已经运行的SQL脚本,并运行使数据库更新所需的更改脚本。
  • Evolve – 使用纯SQL脚本的简单数据库迁移工具。受到Flyway的启发。
  • EFCorePowerTools – EF工具库 – reverse engineering, migrations and model。
  • fluentmigrator – .NET的迁移框架,就像Ruby on Rails Migrations一样。
  • monitor-table-change-with-sqltabledependency – 获取有关记录表更改的SQL Server通知。
  • roundhouse – RoundhousE是用于.NET的数据库迁移实用程序,它使用sql文件和基于源代码控制的版本控制。
  • SharpRepository – SharpRepository是一个用C#编写的通用存储库,它包括对各种关系,文档和对象数据库的支持,包括Entity Framework,RavenDB,MongoDb和Db4o。 SharpRepository还包括Xml和InMemory存储库实现。
  • TrackableEntities.Core – 使用.NET Core跨服务边界进行更改跟踪。
  • Mongo.Migration – MongoDB的即时迁移库。

日期和时间

  • Exceptionless.DateTimeExtensions – DateTimeRange,工作日和各种DateTime,DateTimeOffset,TimeSpan扩展方法。
  • FluentDateTime – 允许您编写更清晰的DateTime表达式和操作。部分灵感来自Ruby DateTime Extensions。
  • nodatime – 日期和时间API库。

分布式计算

  • AspNetCore.Diagnostics.HealthChecks – HealthChecks企业级核心诊断程序。
    • BeatPulse – ASP.NET Core应用程序的活动状况,健康检查库。
  • Foundatio – 可插拔的,用于构建松耦合的分布式应用程序库。
  • Rafty – RAFT 的实现库。
  • Obvs – 一个可观察微服务总线的库,基于Rx的接口。
  • Ocelot – Ocelot创建的API网关。
  • OpenTracing -API和分布式跟踪工具。
  • Polly – Polly是一个.NET弹性和瞬态故障处理库,允许开发人员以流畅和线程安全的方式表达诸如重试,断路器,超时,隔离头和回退之类的策略。
  • ProxyKit – HTTP反向代理的工具包。

电子商务与支付

  • nopCommerce – 免费的开源电子商务购物车(ASP.NET MVC / ASP.NET核心MVC),拥有庞大的社区和充满新功能的市场,主题和插件。
  • GrandNode – 基于ASP.NET Core 2.1和MongoDB的多平台免费开源电子商务购物车。
  • PayPal – 用于PayPal的RESTful API的.NET SDK。
  • SimplCommerce – 基于.NET Core构建的超级简单电子商务系统。
  • Stripe – 用于stripe.com REST API的类型.NET客户端。

异常

响应式编程

  • CSharpFunctionalExtensions – C#的功能扩展。
  • DynamicData – 基于Rx.NET的Reactive 集合。
  • echo-process – C#的Actor库,其中包含支持Redis持久性的其他模块,以及JS集成。
  • FsCheck – FsCheck是用于自动测试.NET程序的工具。
  • Giraffe – 适用于F#开发人员的本机功能ASP.NET核心Web框架。
  • language-ext – C#功能语言扩展。
  • LaYumba.Functional – C#中的函数式编程的代码示例。
  • NetMQ.ReactiveExtensions – 使用Reactive Extensions(RX)轻松地在网络上的任何位置发送消息。传输协议是ZeroMQ。
  • Optional – Optional类型库.
  • reactive-streams-dotnet – Reactive库。
  • ReactiveUI – 一个MVVM框架,它与Reactive Extensions for .NET集成,以创建在任何移动或桌面平台上运行的优雅,可测试的用户界面。
  • Rx.NET – Rx.NET库。
  • Qactive – Reactive 可查询库。
  • sodium – Reactive 多语言库。

图片

  • GLFWDotNet – GLFW的.NET绑定。
  • ImageProcessor – 一个流畅的System.Drawing包装器,用于处理图像文件。
  • ImageSharp – 图像文件处理库。
  • LibVLCSharp – LibVLCSharp是基于VideoLAN的LibVLC库的.NET平台的跨平台音频和视频API。
  • Magick.NET – 功能强大的图像处理库,支持超过100种主要文件格式(不包括子格式)。
  • MagicScaler – 适用于.NET的MagicScaler高性能,高质量图像处理管道
  • QRCoder – 二维码实现库
  • SharpBgfx – bgfx图形库的C#绑定。
  • Structure.Sketching – 用于支持.NET Core的.NET应用程序的图像处理库。
  • veldrid – 一个用于.NET的低级硬件加速3D图形库。
  • ZXing.Net 二维码、条形码的生成和读取

图形用户界面GUI

  • Avalonia – 跨平台UI框架。
  • AvaloniaEdit – 基于Avalonia的文本编辑器组件。
  • ShellProgressBar – 可视化(并行)控制台应用程序库。
  • Qml.Net – 使用Qml.Net在.NET中构建跨平台的桌面应用程序。
  • WinApi – 一个简单,直接,超薄的CLR库,用于高性能Win32 Native Interop,具有自动化,窗口,DirectX,OpenGL和Skia助手。

集成开发环境IDE

  • Mono – MonoDevelop使开发人员能够在Linux,Windows和Mac OS X上快速编写桌面和Web应用程序。
  • rider – 基于IntelliJ平台和ReSharper的跨平台C#IDE。
  • Omnisharp – 开源项目系列,每个项目都有一个目标:在您选择的编辑器中实现出色的.NET体验。
  • SharpDevelop – SharpDevelop是一个免费的集成开发环境(IDE),适用于Microsoft.NET平台上的C#,VB.NET,Boo,IronPython,IronRuby和F#项目。它(几乎)完全用C#编写,并带有您期望在IDE中使用的功能以及更多功能。
  • Visual Studio Code – 它结合了代码编辑器的简单性和开发人员的核心编辑 – 构建 – 调试周期所需的工具。VS Code提供全面的编辑和调试支持,可扩展性模型以及与现有工具的轻量级集成。
  • Visual Studio Community – 功能完备且可扩展的免费 IDE,可用于创建新式 Android、iOS、Windows 应用以及 Web 应用和云服务。

国际化

控制反转IOC

  • AutoDI – 使用IL编译的超快依赖注入库。
  • Autofac – IoC容器。
  • Castle.Windsor – IoC容器。
  • DryIoc – 快速,小巧,功能齐全的IoC。
  • Grace – Grace是一款功能丰富的依赖注入容器,其设计考虑了易用性和性能。
  • Inyector – AspNetCore的依赖注入自动化。
  • Lamar – 快速的IOC工具库。
  • LightInject – 超轻量级IoC容器。
  • SimpleInjector – 简单,灵活,快速的依赖注入库。
  • Stashbox – 基于.NET的解决方案的轻量级,可移植依赖注入框架。

日志

机器学习和科学研究

  • Accord – Accord.NET项目为.NET提供了机器学习,统计,人工智能,计算机视觉和图像处理方法。
  • ML.NET – ML.NET是.NET的开源和跨平台机器学习框架。
  • Spreads – 用于数据流实时探索和分析的库。
  • TensorFlowSharp – 适用于.NET语言的TensorFlow API。
  • WaveFunctionCollapse – 借助量子力学的思想,从单个例子​​生成itmap和tilemap。
  • SiaNet – 具有CUDA / OpenCL支持的易于使用的C#深度学习。

邮件

  • FluentEmail – 电子邮件发送库。
  • MailBody – 使用流畅的界面(.NET)创建电子邮件。
  • MailKit – 用于IMAP,POP3和SMTP的跨平台.NET库。
  • MailMergeLib – SMTP邮件客户端库,为文本,内嵌图像和附件提供邮件合并功能,以及发送邮件的良好吞吐量和容错能力。
  • MimeKit – 跨平台.NET MIME创建和解析器库,支持S/MIME, PGP, DKIM, TNEF and Unix mbox。
  • netDumbster – 用于测试的.Net假SMTP服务器。克隆流行的Dumbster。
  • Papercut – 简单桌面SMTP服务器。
  • PreMailer.Net – css和样式结合的邮件库。
  • SendGrid Client – C# library for the SendGrid v3 mail endpoint.
  • SmtpServer – 用于创建自己的SMTP服务器的库。
  • StrongGrid – SendGrid的v3 API客户端。不仅允许您发送电子邮件,还允许您批量导入联系人,管理列表和段,为列表创建自定义字段等。还包括SendGrid Webhooks的解析器。

数学

  • UnitConversion – 用于.NET Core和.NET Framework的可扩展单元转换库。
  • AutoDiff – 一个库,提供快速,准确和自动的数学函数微分(计算导数/梯度)。

大杂烩

  • AdvanceDLSupport – 基于P/Invoke的库。
  • AngleSharp – 尖括号解析器库。它解析HTML5,MathML,SVG和CSS,以构建基于官方W3C规范的DOM。可与python的beautifulsoup4相媲美。
  • AgileMapper – AgileMapper是一个零配置,高度可配置的对象 – 对象映射库,具有可查看的执行计划。
  • AspNetCore Extension Library – ASP.NET Core扩展库。
  • AutoMapper – .NET中基于约定的对象关系映射库。
  • Baget – 轻量级NuGet服务器。
  • Bleak – Windows本机DLL注入库。
  • Bullseye – 用于描述和运行目标及其依赖项的.NET包。
  • Castle.Core – Castle Core提供常见的Castle Project抽象,包括日志记录服务。
  • Chessie – Railway-oriented编程库。
  • CliWrap – 命令行界面的包装库。
  • commanddotnet – 在类中为您的命令行应用程序接口建模。
  • CommonMark.NET – 在C#中实现CommonMark规范,用于将Markdown文档转换为HTML。
  • ConsoleTableExt – 用于为.NET控制台应用程序创建表的Fluent库。
  • CoordinateSharp – 一个可以快速格式化和转换地理坐标以及提供基于位置的太阳和月亮信息(日落,日出,月亮照明等)的库。 )。
  • datatables – JQuery DataTables的帮助程序。
  • DinkToPdf – 用于wkhtmltopdf库的C#.NET包装库,它使用Webkit引擎将HTML页面转换为PDF。
  • dotnet-env – 用于从.env文件加载环境变量的.NET库。
  • DotNet.Glob – 快速通配库。优于正则表达式。
  • Dotnet outdated – 显示过时的NuGet的工具库。
  • Dotnet Script – 从.NET CLI运行C#脚本。
  • Dotnet Serve – 用于.NET Core CLI的简单命令行HTTP服务器。
  • Eighty – 一个简单的HTML生成库
  • Enums.NET – Enums.NET是一个高性能类型安全的.NET枚举实用程序库
  • FastExpressionCompiler – 快速ExpressionTree编译器。
  • FluentDocker – FluentDocker是一个与docker-machine,docker-compose和docker交互的库。
  • FluentFTP – FTP和FTPS客户端,具有广泛的FTP命令,SSL / TLS连接,散列/校验等。
  • Fody – 编辑.net程序集的可扩展工具。
  • HdrHistogram.NET – 高动态范围(HDR)直方图。
  • httpclient-interception – 用于拦截服务器端HTTP依赖关系的.NET标准库。
  • Humanizer – Humanizer满足您操作和显示字符串,枚举,日期,时间,时间跨度,数字和数量的所有.NET需求。
  • Humidifier – Humidifier允许您以编程方式构建AWS CloudFormation模板。
  • impromptu-interface – 将DLR与Reflect.Emit结合使用的库。
  • JqueryDataTablesServerSide – 用于JQuery DataTable的Asp.Net Core服务器端库,具有分页,过滤,排序和Excel导出功能。
  • LibSass Host – 围绕LibSass库的.NET包装器,能够支持虚拟文件系统。
  • markdig – 可兼容Markdown处理库。
  • NFlags – 解析CLI和开箱即用功能的库。
  • NReco.LambdaParser – 将字符串表达式(公式,方法调用,条件)解析为LINQ表达式树,可以编译为lambda并进行求值。
  • NuGet Trends – 查看NuGet软件包的采用情况以及NuGet的最新趋势程序。
  • NYoutubeDL – youtube-dl库。
  • Otp.NET – 在C#中实现TOTP RFC 6238和HOTP RFC 4226。
  • pose – 用委托替换任何.NET方法(包括静态和非虚拟)
  • PuppeteerSharp – Puppeteer Sharp是官方Node.JS Puppeteer API的.NET端口。
  • readline – 可以代替内置组件Console.ReadLine()的库。
  • ReflectionMagic – Framework to drastically simplify your private reflection code using C# dynamic
  • Relinq – 使用re-linq,现在比以往更容易创建功能齐全的LINQ提供商。
  • ReverseMarkdown – Html到Markdown转换器库,附带一些unix shell终端优势。
  • PdfReport.Core – PdfReport.Core是一个代码优先的报告引擎,它建立在iTextSharp.LGPLv2.Core和EPPlus.Core库之上。
  • Scientist – 用于重构关键路径的.NET库。它是GitHub的Ruby Scientist库的一个端口。
  • Scrutor – Microsoft.Extensions.DependencyInjection的程序集扫描扩展。
  • Sheller – 读取Shell脚本的库。
  • SmartFormat.NET – string.Format的可扩展替代品。
  • Stocks
    • Trady – Trady是一个用于计算技术指标的便捷库,它的目标是成为一个自动交易系统,提供股票数据馈送,指标计算,策略建立和自动交易。
  • System.Linq.Dynamic.Core – System Linq Dynamic功能。
  • UnitsNet – Units.NET为您提供所有常用的度量单位和它们之间的转换。
  • Validation
    • FluentValidation – 流行的.NET验证库,用于构建强类型的验证规则。
    • Guard – 高性能,可扩展的参数验证库。
    • Valit – Valit是对.NET Core的简单验证库,减少if的使用。
  • warden-stack – 针对您的应用程序,资源和基础架构的“运行状况检查”。让守望者守在手表上。
  • WebEssentials.AspNetCore.ServiceWorker – ASP.NET核心渐进式Web应用程序。
  • Xabe.FFmpeg – 用于FFmpeg的.NET标准包装器。它允许在不知道FFmpeg如何工作的情况下处理媒体,并且可以用于将自定义参数传递给来自C#应用程序的FFmpeg。
  • YoutubeExplode – 用于提取元数据和下载Youtube视频和播放列表的终极库。

网络

  • AspNetCore.Proxy – Proxy代理库。
  • CurlThin – 轻量级cURL绑定库,支持通过curl_multi接口进行多个同时传输。
  • NETStandard.HttpListener – HttpListener(NETStandard)。
  • Networker – 一个简单易用的.NET TCP和UDP网络库,旨在实现灵活,可扩展和快速。

办公软件

  • EPPlus – 使用.NET创建高级Excel电子表格。
  • npoi – 可以读取/写入未安装Microsoft Office的Office格式的.NET库。没有COM +,没有互操作。
  • Open-XML-SDK – Open XML SDK提供了使用Office Word,Excel和PowerPoint文档的工具。

操作系统

  • CosmosOS – Cosmos是操作系统的“构建工具包”。使用托管语言(例如C#,VB.NET等)构建自己的OS!

对象关系映射ORM

分析

  • Glimpse – 适用于.NET的轻量级,开源,实时诊断和洞察分析器。 不稳定的版本
  • MiniProfiler – 一个简单但有效的ASP.NET网站迷你探查器。

sql生成器

  • SqlKata – 优雅的Sql查询生成器,支持复杂查询,连接,子查询,嵌套条件,供应商引擎目标等等

消息队列

  • emitter – 连接所有设备的免费开源实时消息服务。此发布 – 订阅消息传递API是为了提高速度和安全性而构建的。
  • EventStore – 使用JavaScript中的复杂事件处理的开源,功能数据库。
  • Foundatio – 内存,redis和azure实现的通用接口。
  • MediatR – 中介模式库。
  • MediatR.Extensions.Microsoft.DependencyInjection – MediatR的扩展程序
  • Mediator.Net – .Net的简单中介,用于发送支持管道的命令,发布事件和请求响应。
  • MicroBus – MicroBus中介模式库。
  • MQTTnet – MQTTnet是一个用于基于MQTT的通信的高性能.NET库。
  • netmq – NetMQ是轻量级消息传递库。
  • OpenCQRS – 用于DDD,CQRS和事件的.NET核心库,具有Azure Service Bus集成。 Command和Event存储支持的数据库提供程序包括:DocumentDB,MongoDB,SQL Server,MySQL,PostgreSQL和SQLite。
  • rabbitmq-dotnet-client – RabbitMQ .NET客户端。
  • RawRabbit – 用于通过RabbitMq进行通信的现代.NET框架。
  • Rebus – .NET的简单和精简服务总线实现。
  • Restbus – RabbitMq的消息传递库。
  • Tossit – 简单易用的库,用于分布式作业/工作人员逻辑。内置RabbitMQ实现处理的分布式消息。

报表

  • FastReport – .NET Core 2.x / .Net Framework 4.x的开源报告生成器。 FastReport可用于MVC,Web API应用程序。

任务计划

  • Chroniton.NetCore – 用于在日程安排上运行任务(作业)的轻量级健壮库。
  • Coravel – .Net Core符合Laravel:调度,排队等
  • FluentScheduler – 具有流畅界面的自动作业调度程序。
  • Gofer.NET – 用于.NET Core的分布式后台任务/作业的简易C#API。
  • HangfireIO – 在ASP.NET应用程序内执行即发即忘,延迟和重复性工作。
  • LiquidState – 高效异步和同步状态机。
  • NCrontab – 用于.NET的Crontab。
  • quartznet – Quartz.NET任务计划程序。
  • stateless – 用于在C#代码中创建状态机的简单库。

开发工具包SDKs

  • AWS SDK – Amazon Web Services(AWS).NET Core SDK组件。每个AWS服务都有自己的NuGet包。
  • azure-event-hubs-dotnet – Azure事件中心的.NET标准客户端库。
  • Blockchain clients
  • CakeMail.RestClient – CakeMail API的客户端。允许您发送交易电子邮件,批量电子邮件,管理列表和联系人等。
  • consuldotnet – 面向领事的.NET API。
  • csharp-nats – 用于NATS消息传递系统的C#.NET客户端。
  • DarkSkyCore – .NET标准库,用于使用Dark Sky API
  • Docker.DotNet – 用于Docker API的.NET(C#)客户端库。
  • firebase-admin-dotnet – Firebase Admin .NET SDK
  • google-cloud-dotnet – 适用于.NET的Google Cloud Client Libraries。
  • Manatee.Trello – 一个完全面向对象的.Net包装器,用于Trello用C#编写的RESTful API。
  • Microphone – 使用Consul或ETCD集群的Web Api或NancyFx运行自托管REST服务的轻量级框架。
  • octokit.net – 用于.NET的GitHub API客户端库。
  • PreStorm – ArcGIS Server的并行REST客户端。
  • SendGrid-csharp – 用于使用完整SendGrid API的C#客户端库。
  • statsd-csharp-client – 与.NET标准兼容的C#客户端与Etsy的优秀服务器。
  • tweetinvi – 直观的.NET C#库,用于访问Twitter REST和STREAM API。

安全

  • aspnetcore-security-headers – 用于向ASP.NET Core应用程序添加安全标头的中间件。
  • HtmlSanitizer – 清除HTML以避免XSS攻击。
  • jose-jwt – 用于处理JOSE对象的库(JWT,JWA,JWS及相关)。
  • Jwt.Net – Jwt.Net,一个用于.NET的JWT(JSON Web令牌)实现。
  • JWT Simple Server – 用于ASP.NET Core的轻量级动态jwt服务器。
  • NWebsec – ASP.NET的安全库。
  • reCAPTCHA – 用于ASP.NET Core的reCAPTCHA 2.0。
  • roslyn-security-guard – 旨在帮助.NET应用程序进行安全审计的Roslyn分析器。
  • OwaspHeaders – .NET Core中间件,用于注入Owasp推荐的HTTP标头,以提高安全性。
  • Security – 于Web应用程序的安全性和授权的中间件。
  • SecurityHeaders – 允许向ASP.NET Core网站添加安全标头的小包。

搜索

  • Algolia.Search – 官方Algolia .NET客户端的存储库。
  • AutoComplete – 持久,简单,强大且可移植的自动完成库。
  • Elasticsearch.Net & NEST – NEST和Elasticsearch.Net的存储库,这是两个官方Elasticsearch .NET客户端。
  • ElasticsearchCRUD – Elasticsearch .NET API。
  • SearchExtensions – IQueryable接口的高级搜索功能,例如Entity Framework查询。
  • SimMetrics.Net – 相似度量标准库,例如从编辑距离(Levenshtein,Gotoh,Jaro等)到其他指标,(例如Soundex,Chapman)
  • SolrExpress – 用于Solr的简单轻量级查询.NET库,采用可控,可构建和快速失败的方式。

序列化

  • BinarySerializer – 二进制序列化库,用于控制字节和位级别的数据格式。
  • bond – 用于处理模式化数据的跨平台框架。它支持跨语言的序列化和强大的通用机制,可以有效地处理数据。 Bond广泛用于Microsoft的高规模服务。
  • Channels – 基于推送的.NET流。
  • CsvHelper – 帮助读写CSV文件的库。
  • Edi.Net – EDI Serializer / Deserializer。支持EDIFact,X12和TRADACOMS格式。
  • ExtendedXmlSerializer – 用于.NET的扩展Xml序列化程序。
  • Jil – 基于Sigil构建的快速.NET JSON(De)串行器。
  • MessagePack
  • Newtonsoft.Json – 适用于.NET的流行高性能JSON框架。
  • protobuf-net – 用于惯用.NET的协议缓冲库。
  • Schema.NET – Schema.org对象变成了强类型的C#POCO类,用于.NET。所有类都可以序列化为JSON / JSON-LD和XML,通常用于表示html页面头部的结构化数据。
  • ServiceStack.Text – JSON,JSV和CSV文本序列化器。
  • TinyCsvParser – 易于使用,易于扩展和高性能的库,用于使用.NET进行CSV解析。
  • Wire – POCO对象的二进制序列化程序。
  • YamlDotNet – .NET
  • ZeroFormatter – 用于.NET的快速二进制(de)序列化程序。
  • Utf8Json – 用于C#(.NET,.NET Core,Unity,Xamarin)的绝对最快和零分配JSON序列化器。
  • YAXLib – 用于.NET Framework和.NET Core的XML序列化库。非常灵活和强大。

模板引擎

  • dotliquid – TobiasLütke的Liquid模板语言的.NET端口。
  • fluid – 开源.NET模板引擎,尽可能接近Liquid模板语言。
  • Portable.Xaml – 用于读/写xaml文件的可移植.NET库。
  • Razor – 用于MVC Web应用程序视图页面的CSHTML文件的分析器和代码生成器。
  • RazorLight – 基于Microsoft针对.NET Core的Razor解析引擎的模板引擎。
  • Scriban – A fast, powerful, safe and lightweight text templating language and engine for .NET.

测试

  • Bogus – 简单而健全的C#假数据生成器。基于并从着名的faker.js移植。
  • CoreBDD – xUnit.net的BDD框架
  • FakeItEasy – .NET的简易模拟库。
  • FluentAssertions – 一组.NET扩展方法,允许您更自然地指定TDD或BDD样式测试的预期结果。
  • GenFu – 可用于生成实际测试数据的库。
  • LightBDD – BDD框架允许创建易于阅读和维护的测试。
  • mockhttp – 为Microsoft的HttpClient库测试图层。
  • moq.netcore – 最受欢迎且最友好的.NET模拟框架。
  • MSpec – 用于编写BDD样式测试的流行测试框架。
  • MyTested.AspNetCore.Mvc – 流畅的测试 framework for ASP.NET Core MVC.
  • Netling – 加载测试客户端,以便轻松进行Web测试。
  • NSpec – 针对C#的战斗强化测试框架,受Mocha和RSpec的启发。
  • NSubstitute – .NET模拟框架的友好替代品。
  • nunit – 面向.NET Core的NUnit测试运行器。
  • shouldly – 断言框架Should be!
  • SpecFlow – SpecFlow是用于.NET的实用BDD解决方案。
  • Storyteller – 一种制定可执行规范的工具。
  • Stubbery – 一个用于在.NET中创建和运行Api存根的简单库。
  • Testavior – Testavior是一个轻量级解决方案,可帮助您开发ASP.NET Core的行为测试。
  • TestStack.BDDfy – 最简单的BDD框架!
  • xBehave.net – 一个xUnit.net扩展,用于描述使用自然语言的测试。
  • xUnit.net – 一个免费的,开源的,以社区为中心的.NET Framework单元测试工具。

工具

  • CommandLineUtils – .NET Core和.NET Framework的命令行解析和实用程序。
  • docfx – 用于构建和发布.NET项目API文档的工具
  • dotnetfiddle – .NET沙箱,供开发人员快速尝试代码和共享代码片段。
  • dotnet-tools – .NET Core命令行(dotnet CLI)的工具扩展列表。
  • EntryPoint – .Net Core和.Net Framework 4.5+的可组合CLI(命令行)参数解析器。
  • Fake JSON Server – 用于原型设计或作为CRUD后端的假REST API。无需定义类型,使用动态类型。数据存储在单个JSON文件中。具有身份验证,WebSocket通知,异步长时间运行操作,错误/延迟的随机生成以及实验性GraphQL支持。
  • gitignore.io – 为您的项目创建有用的.gitignore文件。
  • ICanHasDotnetCore – 扫描上传的packages.config文件或GitHub存储库,并确定nuget包是否针对.NET Standard。
  • json2csharp – 从JSON生成C#类。
  • letsencrypt-win-simple – 适用于Windows的简单ACME客户端。
  • Linq_Faster – 数组,Span 和List 的类似于Linq的扩展。
  • mRemoteNG – 下一代mRemote,开源,标签,多协议,远程连接管理器
  • NJsonSchema – NJsonSchema是一个.NET库,用于读取,生成和验证JSON Schema draft v4 + schemas。
  • NuKeeper – 自动更新.NET项目中的nuget包。
  • NuGetPackageExplorer – 使用GUI创建,更新和部署Nuget软件包。
  • NugetVisualizer – 为一组给定的git存储库或文件夹可视化所有nuget包及其相应的版本。
  • OctoLinker – 使用适用于GitHub的OctoLinker浏览器扩展,有效地浏览projects.json文件。
  • posh-dotnet – [dotnet CLI]的“PowerShell”标签完成(https://github.com/dotnet/cli)。
  • Rin – ASP.NET Core的请求/响应Inspector中间件。像Glimpse。
  • scoop – Windows的命令行安装程序。
  • SerilogAnalyzer – 使用Serilog日志库对基于Roslyn的代码进行分析。检查常见错误和使用问题。
  • SharpZipLib – #ziplib是一个完全用C#编写的适用于.NET平台的Zip,GZip,Tar和BZip2库。
  • ShareX – 免费的开源程序,可让您捕捉或记录屏幕的任何区域,只需按一下键即可共享。它还允许将图像,文本或其他类型的文件上传到80多个支持的目的地,您可以从中选择。 https://getsharex.com
  • SharpLab – .NET代码游乐场,显示代码编译的中间步骤和结果。 https://sharplab.io
  • sourcelink – SourceLink是一个语言和源代码控制不可知系统,用于为二进制文件提供一流的源代码调试体验。
  • System.CommandLine – System.CommandLine:命令行解析,调用和呈现终端输出。
  • X.Web.Sitemap – 简单站点地图生成器。
  • X.Web.RSS – 简单站点RSS生成器。
  • SmartCode – SmartCode= IDataSource -> IBuildTask -> IOutput => Build Everything!!! (Including [Code generator])

Web框架

  • WebAssembly
    • Blazor – Blazor是使用C#/ Razor和HTML的.NET Web框架,可在带有WebAssembly的浏览器中运行。
      • Awesome Blazor – Blazor的资源,Blazor是使用C#/ Razor和HTML的.NET Web框架,可在具有WebAssembly的浏览器中运行。
      • Blazor Redux – 将Redux状态存储与Blazor连接。
    • Ooui – 是使用Web技术的.NET跨平台的小型UI库。
  • ReactJS.NET – 用于JSX编译和React组件的服务器端呈现的.NET库。
  • redux.NET – .NET应用程序的可预测状态容器。

Web Socket

  • Fleck – Fleck是C#中的WebSocket服务器实现。 Fleck不需要继承,容器或其他引用。
  • SignalR Server – Web应用程序的实时Web功能,包括服务器端推送。
  • SuperSocket – 轻量级,跨平台和可扩展的套接字服务器应用程序框架。
  • WampSharp – [Web应用程序消息传递协议]的C#实现- 提供远程消息传递模式的协议过程通过WebSockets调用和发布/预订。
  • websocket-manager – ASP .NET Core的实时库。

Windows服务

工作流

  • CoreWF – Windows Workflow Foundation(WF)到.NET Core的端口。
  • workflow-core – .NET Standard的轻量级工作流引擎。
  • WorkflowEngine.NET – 在应用程序中添加工作流程的组件。
  • Wexflow – 高性能,可扩展,模块化和跨平台的工作流引擎。

线路图

入门套件

  • Arch – .NET Core库的集合。
    • AutoHistory – 自动记录数据更改历史记录的插件。
  • AspNetCore-Angular2-Universal – 跨平台 – 用于SEO,Bootstrap,i18n国际化(ngx-translate),Webpack的服务器端渲染,TypeScript,带Karma的单元测试,WebAPI REST设置,SignalR,Swagger文档等等!
  • ASP.NET Core Starter Kit – 使用Visual Studio Code,C#,F#,JavaScript,ASP.NET Core,EF Core,React(ReactJS),Redux,Babel进行跨平台的Web开发。单页应用样板。
  • aspnetcore-spa generator – Yeoman生成器,用于构建全新的ASP.NET Core单页面应用程序,该应用程序使用Angular 2 / React / React与Redux / Knockout / Aurelia在客户端上。
  • ASP.Net Core Vue Starter – Asp.NETCore 2.0 Vue 2(ES6)SPA入门套件,包含路由,Vuex等等!
  • bitwarden-core – 核心基础设施后端(API,数据库等)https://bitwarden.com
  • dotNetify – 构建实时HTML5 / C#.NET Web应用程序的简单,轻量级但功能强大的方法。
  • generator-aspnet – 用于ASP.NET Core的yo生成器。
  • Nucleus – 在后端使用ASP.NET Core API分层架构和基于JWT的身份验证的Vue启动应用程序模板
  • react-aspnet-boilerplate – 使用ASP.NET Core 1构建同构React应用程序的起点,利用现有技术。
  • saaskit – 用于构建SaaS应用程序的开发人员工具包。
  • serverlessDotNetStarter – .NET Core入门解决方案-通过无服务器框架进行部署,并且可以在VS Code中进行本地调试。

例子

文章

书籍

备忘录

视频学习

视频播客

C#净化版WebApi框架 - kiba518 - 博客园

mikel阅读(594)

来源: C#净化版WebApi框架 – kiba518 – 博客园

前言

我们都知道WebApi是依赖于ASP.NET MVC的HttpRouteCollection进行路由 。

但WebApi和MVC之间是没有依赖关系的, WebApi的基类ApiController继承于IHttpController,而MVC的基类Controller 继承于IController。

WebApi和MVC虽然都使用HttpRouteCollection进行路由,但WebApi经历的通道是ServicesContainer,而MVC经历通道还是原始的IHttpModule。

但用Visual Studio创建的MVC WebApi项目通常会带很多功能,而这些功能,很多是我们并不想用的,或者我们想用其他开源控件代替它。

而直接创建空项目的WebApi又太原始。

所以,让我们编写一个简洁版本的WebApi吧。

净化版WebApi预览

首先,我们先看下净化版WebApi的结构。

如上图所示,代码结构很简单,除开配置文件,整个Web项目只有2个文件;而需要被调用的WebApi都被封装到了WebApi程序集中了。

接下来我们一起看下编写这个净化版WebApi的过程吧。

净化版WebApi编写

WebApiConfig

首先,引入必要的Dll,如下图所示。

然后,我们编写Web项目的写WebApiConfig;代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new WebApiAttribute());
        // 解决json序列化时的循环引用问题
        config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        // 对 JSON 数据使用混合大小写。跟属性名同样的大小.输出
        config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver();
        // Web API 路由
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name"DefaultApi",
            routeTemplate: "webapi/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

可以看到WebApiConfig是个静态类中,我们在其中创建了静态注册方法Register,在方法内,我们主要在做一件事,那就是为HttpConfiguration对象做配置。

而在配置中,我们将WepApi的路由配置成了webapi/{controller}/{id},也就是说,我们的WebApi未来的访问地址将为【http://localhost:5180/webapi/Login】这样的模式。

在WebApiConfig类中,我们还用到了这样一个类WebApiAttribute,我们在为HttpConfiguration对象的Filters属性,添加了这个类的对象。

通过Filters属性这个字样,我们可以得出,这个类主要应用应该是过滤。

下面我们看一下这个类的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class WebApiAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        //API执行前触发
        if (true)//当前设置,所有API都可以被调用
        {
            base.OnActionExecuting(actionContext);
        }
        else
        {
            throw new Exception("Error");
        }
    
    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        //API执行后触发 若发生例外则不在这边处理
        if (actionExecutedContext.Exception != null)
            return;
        base.OnActionExecuted(actionExecutedContext);
    }
}

通过阅读代码,我们应该可以发现,这是一个AOP的过滤器。

在执行真正WebApi之前,会先进入这里进行过滤,过滤通过的API,才会调用 base.OnActionExecuting(actionContext)方法进行调用和执行。

结束调用同理,结束调用前,会在该类中进行拦截和过滤处理。

配置文件

WebApiConfig编写结束了,现在,我们需要将这个静态类注册到项目中。

打开Global.asax文件,编写如下代码:

1
2
3
4
5
protected void Application_Start()
{
    GlobalConfiguration.Configure(WebApiConfig.Register); 
    GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();//删除XML格式 回應
}

可以看到,我们已通过Configure方法,将我们编写好的WebApiConfig添加到了全局配置中了。

因为网站访问都存在跨域问题,所以我们再向Global.asax中添加如下代码处理:

1
2
3
4
5
6
7
8
9
10
protected void Application_BeginRequest(object sender, System.EventArgs e)
{
    var req = System.Web.HttpContext.Current.Request;
    if (req.HttpMethod == "OPTIONS")//过滤options请求,用于js跨域
    {
        Response.StatusCode = 200;
        Response.SubStatusCode = 200;
        Response.End();
    }
}

到此Web项目的编写就完成了,下面我们在WebApi程序集中,编写个简单的WebApi,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LoginController : BaseApiController
{
    public BaseResult Get()
    {
        try
        
            return new BaseResult() { IsSuccess=true };
        }
        catch (Exception ex)
        {
            
            throw ex;
        }
    }<br>}
public class BaseApiController : ApiController
{  
    public string Options()
    {
        return null;
    }
}

然后我们运行网站,进行WebApi访问。

如上图所示,我们的WebApi访问成功。

—————————————————————————————————-

到此C#净化版WebApi框架就介绍完了。

框架代码已经传到Github上了,欢迎大家下载。

Github地址:https://github.com/kiba518/WebApi

—————————————————————————————————-

JWT+ASP.NET Core集成方案 - 、天上月 - 博客园

mikel阅读(538)

来源: JWT+ASP.NET Core集成方案 – 、天上月 – 博客园

JWT

JSON Web Token 经过数字签名后,无法伪造,一个能够在各方之间安全的传输JSON对象的开放标准(RFC 7519

创建项目和解决方案

dotnet new webapi -n SampleApi
cd SampleApi
dotnet new sln -n SampleApp
dotnet sln  add .\SampleApi.csproj

引用包

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

该包已经依赖Microsoft.IdentityModel.TokensSystem.IdentityModel.Tokens.Jwt,该包由Azure AD 团队提供,所以不在aspnetcore6 运行时中。

  • 或直接修改jwtaspnetcore.csproj,引用包
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
  • appsettings.json

  "Authentication": {
    "JwtBearer": {
      "Issuer": "http://api.sampleapi.com",
      "Audience": "SampleApi",
      "SecurityKey": "SecurityKey23456"
    }
  }
  • Issuer:令牌的颁发者。一般就写成域名,实际可任意
  • Audience 颁发给谁。一般写成项目名,实际可任意
  • SecurityKey:签名验证的KEY;至少 128bit ,即16个英文字符以上,实际可任意英文字符

定义一个JwtSettings


public class JwtSettings
{
    public JwtSettings(byte[] key, string issuer, string audience)
    {
        Key = key;
        Issuer = issuer;
        Audience = audience;
    }

    /// <summary>
    ///令牌的颁发者
    /// </summary>
    public string Issuer { get; }

    /// <summary>
    /// 颁发给谁
    /// </summary>
    public string Audience { get; }

    public byte[] Key { get; }

    public TokenValidationParameters TokenValidationParameters => new TokenValidationParameters
    {
        //验证Issuer和Audience
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
        ValidateLifetime = true,
        ValidIssuer = Issuer,
        ValidAudience = Audience,
        IssuerSigningKey = new SymmetricSecurityKey(Key)
    };

    public static JwtSettings FromConfiguration(IConfiguration configuration)
    {
        var issuser = configuration["Authentication:JwtBearer:Issuer"] ?? "default_issuer";
        var auidence = configuration["Authentication:JwtBearer:Audience"] ?? "default_auidence";
        var securityKey = configuration["Authentication:JwtBearer:SecurityKey"] ?? "default_securitykey";

        byte[] key = Encoding.ASCII.GetBytes(securityKey);

        return new JwtSettings(key, issuser, auidence);
    }
}

中间件Middleware引用

        app.UseAuthentication();//认证
        app.UseAuthorization();//授权

定义JWT扩展方法服务注入

    public static IServiceCollection AddJwt(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddScoped<IStorageUserService, StorageUserService>();

        var jwtSettings = JwtSettings.FromConfiguration(configuration);
        services.AddSingleton(jwtSettings);

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options => options.TokenValidationParameters = jwtSettings.TokenValidationParameters);

        return services;
    }

引用服务

services.AddJwt(Configuration);

定义一个数据库的实体类,数据库访问 为模拟数据

public class SysUser
{
    public int Id { get; set; }
    public string UserName { get; set; }
}
public interface IStorageUserService
{
    /// <summary>
    /// 根据登录验证用户
    /// </summary>
    /// <param name="loginInfo"></param>
    /// <returns></returns>
    Task<SysUser> CheckPasswordAsync(LoginInfo loginInfo);
}
public class StorageUserService : IStorageUserService
{
    public async Task<SysUser> CheckPasswordAsync(LoginInfo loginInfo)
    {
        return await Task.FromResult(
          new SysUser 
          { 
            Id = new Random().Next(10000), 
            UserName = loginInfo.UserName 
          }
        );
    }
}

AuthController登录GenerateToken

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using SampleApi.Models;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace SampleApi.Auth;

/// <summary>
/// 登录认证个人信息
/// </summary>
[ApiController]
[Route("/api/[controller]/[action]")]
[AllowAnonymous]
public class AuthController : ControllerBase
{
    private readonly IStorageUserService _userService;
    private readonly JwtSettings _jwtSettings;

    public AuthController(JwtSettings jwtSettings, IStorageUserService userService)
    {
        _jwtSettings = jwtSettings;
        _userService = userService;
    }

    /// <summary>
    /// 登录,生成访问Toekn
    /// </summary>
    /// <param name="loginInfo"></param>
    /// <returns></returns>
    [HttpPost]
    public async Task<IActionResult> GenerateToken(LoginInfo loginInfo)
    {
        SysUser user = await _userService.CheckPasswordAsync(loginInfo);
        if (user == null)
        {
            return Ok(new
            {
                Status = false,
                Message = "账号或密码错误"
            });
        }

        var claims = new List<Claim>();

        claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
        claims.Add(new Claim(ClaimTypes.Name, user.UserName));

        var key = new SymmetricSecurityKey(_jwtSettings.Key);
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _jwtSettings.Issuer,
            audience: _jwtSettings.Audience,
            claims: claims,
            expires: DateTime.Now.AddMinutes(120),
            signingCredentials: creds
            );
        return Ok(new
        {
            Status = true,
            Token = new JwtSecurityTokenHandler().WriteToken(token)
        });
    }
}

aspnetcore6默认集成了swagger,直接运行项目,实际上为模拟数据库请求,所以点击登录接口即可。

{
  "status": true,
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijc4NjciLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNjQzMDMyNzA1LCJpc3MiOiJodHRwOi8vYXBpLnNhbXBsZWFwaS5jb20iLCJhdWQiOiJTYW1wbGVBcGkifQ.Rl8XAt2u0aZRxEJw2mVUnV6S9WzQ65qUYjqXDTneCxE"
}

当使用Swagger测试时,增加,可配置全局请求头。增加一个扩展方法。

services.AddSwagger(Configuration);
  public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddSwaggerGen(options =>
        {
            try
            {
                options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{typeof(Startup).Assembly.GetName().Name}.xml"), true);
            }
            catch (Exception ex)
            {
                Log.Warning(ex.Message);
            }
            options.SwaggerDoc("v1", new OpenApiInfo
            {
                Title = "SampleApp - HTTP API",
                Version = "v1",
                Description = "The SampleApp Microservice HTTP API. This is a Data-Driven/CRUD microservice sample"
            });

            options.AddSecurityRequirement(new OpenApiSecurityRequirement()
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference()
                            {
                                Id =  "Bearer",
                                Type = ReferenceType.SecurityScheme
                            }
                        },
                        Array.Empty<string>()
                    }
                });
            options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme
            {
                Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
                Name = "Authorization", //jwt默认的参数名称
                In = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中)
                Type = SecuritySchemeType.ApiKey
            });

        });
        services.AddEndpointsApiExplorer();

        return services;

    }

获取当前用户信息

    /// <summary>
    /// 编码Token
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpGet]
    [AllowAnonymous]
    public CurrentUser DecodeToken(string token)
    {
        var jwtTokenHandler = new JwtSecurityTokenHandler();

        if (jwtTokenHandler.CanReadToken(token))
        {
            JwtPayload jwtPayload = new JwtSecurityTokenHandler().ReadJwtToken(token).Payload;
            string? userIdOrNull = jwtPayload.Claims.FirstOrDefault(r => r.Type == ClaimTypes.NameIdentifier)?.Value;
            string? UserName = jwtPayload.Claims.FirstOrDefault(r => r.Type == ClaimTypes.Name)?.Value;
            CurrentUser currentUser = new CurrentUser
            {
                UserId = userIdOrNull == null ? null : Convert.ToInt32(userIdOrNull),
                UserName = UserName
            };
            return currentUser;
        }
        return null;
    }

根据请求头获取用户信息

IStorageUserService增加接口,StorageUserService的实现,创建一个CurrentUser类

public class StorageUserService : IStorageUserService
{
    private readonly IHttpContextAccessor _contextAccessor;

    public StorageUserService(IHttpContextAccessor contextAccessor)
    {
        _contextAccessor = contextAccessor;
    }

    public async Task<CurrentUser> GetUserByRequestContext()
    {
        var user = _contextAccessor.HttpContext.User;

        string? userIdOrNull = user.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
        string? UserName = user.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;

        CurrentUser currentUser = new CurrentUser
        {
            IsAuthenticated = user.Identity.IsAuthenticated,
            UserId = userIdOrNull == null ? null : Convert.ToInt32(userIdOrNull),
            UserName = UserName
        };
        return await Task.FromResult(currentUser);
    }
}

public class CurrentUser
{
    /// <summary>
    /// 是否登录
    /// </summary>
    public bool IsAuthenticated { get; set; }
    /// <summary>
    /// 用户Id
    /// </summary>
    public int? UserId { get; set; }
    /// <summary>
    /// 用户名
    /// </summary>
    public string? UserName { get; set; }
}
public interface IStorageUserService
{
    /// <summary>
    /// 根据Request Header携带Authorization:Bearer+空格+AccessToken获取当前登录人信息
    /// </summary>
    /// <returns></returns>
    Task<CurrentUser> GetUserByRequestContext();
}

AuthController调用服务

    /// <summary>
    /// 根据Request Header携带Authorization:Bearer+空格+AccessToken获取当前登录人信息
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    [Authorize]
    public async Task<CurrentUser> GetUserByRequestContext()
    {
        return await _userService.GetUserByRequestContext();
    }

在swagger右上角,点击Authorize,header的参数结构: “Authorization: Bearer+空格+ {token}”

开源地址

SampleApp/SampleApi at master · luoyunchong/SampleApp (github.com)

.NET +JWT

JSON Web Token Libraries – jwt.io 可以看到,.NET有6个类库实现了JWT。

有二个常用的。

  1. 微软 Azure团队的实现:AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet: IdentityModel extensions for .Net (github.com)
  2. jwt-dotnet/jwt: Jwt.Net, a JWT (JSON Web Token) implementation for .NET (github.com)

C# 理解lock - 闯.漠北 - 博客园

mikel阅读(503)

来源: C# 理解lock – 闯.漠北 – 博客园

一. 为什么要lock,lock了什么?

当我们使用线程的时候,效率最高的方式当然是异步,即各个线程同时运行,其间不相互依赖和等待。但当不同的线程都需要访问某个资源的时候,就需要同步机制了,也就是说当对同一个资源进行读写的时候,我们要使该资源在同一时刻只能被一个线程操作,以确保每个操作都是有效即时的,也即保证其操作的原子性。lock是C#中最常用的同步方式,格式为lock(objectA){codeB} 。

lock(objectA){codeB} 看似简单,实际上有三个意思,这对于适当地使用它至关重要:
1. objectA被lock了吗?没有则由我来lock,否则一直等待,直至objectA被释放。
2. lock以后在执行codeB的期间其他线程不能调用codeB,也不能使用objectA。
3. 执行完codeB之后释放objectA,并且codeB可以被其他线程访问。

二. lock(this)怎么了?

我们看一个例子:

  1. using System;
  2. using System.Threading;
  3. namespace Namespace1
  4. {
  5.     class C1
  6.     {
  7.         private bool deadlocked = true;
  8.         //这个方法用到了lock,我们希望lock的代码在同一时刻只能由一个线程访问
  9.         public void LockMe(object o)
  10.         {
  11.             lock (this)
  12.             {
  13.                 while(deadlocked)
  14.                 {
  15.                     deadlocked = (bool)o;
  16.                     Console.WriteLine(“Foo: I am locked :(“);
  17.                     Thread.Sleep(500);
  18.                 }
  19.             }
  20.         }
  21.         //所有线程都可以同时访问的方法
  22.         public void DoNotLockMe()
  23.         {
  24.             Console.WriteLine(“I am not locked :)”);
  25.         }
  26.     }
  27.     class Program
  28.     {
  29.         static void Main(string[] args)
  30.         {
  31.             C1 c1 = new C1();
  32.             //在t1线程中调用LockMe,并将deadlock设为true(将出现死锁)
  33.             Thread t1 = new Thread(c1.LockMe);
  34.             t1.Start(true);
  35.             Thread.Sleep(100);
  36.             //在主线程中lock c1
  37.             lock (c1)
  38.             {
  39.                 //调用没有被lock的方法
  40.                 c1.DoNotLockMe();
  41.                 //调用被lock的方法,并试图将deadlock解除
  42.                 c1.LockMe(false);
  43.             }
  44.         }
  45.     }

复制代码

在t1线程中,LockMe调用了lock(this), 也就是Main函数中的c1,这时候在主线程中调用lock(c1)时,必须要等待t1中的lock块执行完毕之后才能访问c1,即所有c1相关的操作都无法完成,于是我们看到连c1.DoNotLockMe()都没有执行。

把C1的代码稍作改动:

  1.     class C1
  2.     {
  3.         private bool deadlocked = true;
  4.         private object locker = new object();
  5.         //这个方法用到了lock,我们希望lock的代码在同一时刻只能由一个线程访问
  6.         public void LockMe(object o)
  7.         {
  8.             lock (locker)
  9.             {
  10.                 while(deadlocked)
  11.                 {
  12.                     deadlocked = (bool)o;
  13.                     Console.WriteLine(“Foo: I am locked :(“);
  14.                     Thread.Sleep(500);
  15.                 }
  16.             }
  17.         }
  18.         //所有线程都可以同时访问的方法
  19.         public void DoNotLockMe()
  20.         {
  21.             Console.WriteLine(“I am not locked :)”);
  22.         }
  23.     }

复制代码

这次我们使用一个私有成员作为锁定变量(locker),在LockMe中仅仅锁定这个私有locker,而不是整个对象。这时候重新运行程序,可以看到虽然t1出现了死锁,DoNotLockMe()仍然可以由主线程访问;LockMe()依然不能访问,原因是其中锁定的locker还没有被t1释放。

关键点:
1. lock(this)的缺点就是在一个线程(例如本例的t1)通过执行该类的某个使用”lock(this)”的方法(例如本例的LockMe())锁定某对象之后, 导致整个对象无法被其他线程(例如本例的主线程)访问 – 因为很多人在其他线程(例如本例的主线程)中使用该类的时候会使用类似lock(c1)的代码。
2. 锁定的不仅仅是lock段里的代码,锁本身也是线程安全的。
3. 我们应该使用不影响其他操作的私有对象作为locker。
4. 在使用lock的时候,被lock的对象(locker)一定要是引用类型的,如果是值类型,将导致每次lock的时候都会将该对象装箱为一个新的引用对象(事实上如果使用值类型,C#编译器(3.5.30729.1)在编译时就会给出一个错误)。

第二节:抢单流程优化1(小白写法→lock写法→服务器缓存+队列(含lock)→Redis缓存+原子性+队列【干掉lock】) - Yaopengfei - 博客园

mikel阅读(473)

来源: 第二节:抢单流程优化1(小白写法→lock写法→服务器缓存+队列(含lock)→Redis缓存+原子性+队列【干掉lock】) – Yaopengfei – 博客园

一. 小白写法

1.设计思路

纯DB操作

DB查库存→判断库存→(DB扣减库存+DB创建订单)

2.分析

A.响应非常慢,导致大量请求拿不到结果而报错

B.存在超卖现象

C.扣减库存错误

3.压测结果

前提:原库存为10000,这里统计2s内可处理的并发数,以90%百分位为例,要求错误率为0。

代码分享:

复制代码
       /// <summary>
        /// 原始版本-纯DB操作
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <param name="arcId">商品编号</param>
        /// <param name="totalPrice">订单总额</param>
        /// <returns></returns>
        public string POrder1(string userId, string arcId, string totalPrice)
        {
            try
            {
                //1. 查询库存
                var sArctile = _baseService.Entities<T_SeckillArticle>().Where(u => u.articleId == arcId).FirstOrDefault();
                if (sArctile.articleStockNum - 1 > 0)
                {
                    //2. 扣减库存
                    sArctile.articleStockNum--;

                    //3. 进行下单
                    T_Order tOrder = new T_Order();
                    tOrder.id = Guid.NewGuid().ToString("N");
                    tOrder.userId = userId;
                    tOrder.orderNum = Guid.NewGuid().ToString("N");
                    tOrder.articleId = arcId;
                    tOrder.orderTotalPrice = Convert.ToDecimal(totalPrice);
                    tOrder.addTime = DateTime.Now;
                    tOrder.orderStatus = 0;
                    _baseService.Add<T_Order>(tOrder);
                    _baseService.SaveChange();

                    return "下单成功";
                }
                else
                {
                    //卖完了
                    return "卖完了";
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }

        }
复制代码

测试结果:

(1). 100并发,需要1788ms,订单数量插入正确,但库存扣减错误。

(2). 200并发,需要4453ms,订单数量插入正确,但库存扣减错误。

 

二. lock写法

1.设计思路

纯DB操作的基础上Lock锁

Lock { DB查库存→判断库存→(DB扣减库存+DB创建订单) }

2.分析

A. 解决超卖现象

B. 响应依旧非常慢,导致大量请求拿到结果而报错

3.压测结果

前提:原库存为10000,这里统计2s内可处理的并发数,以90%百分位为例,要求错误率为0。

代码分享:

复制代码
        /// <summary>
        /// 02-纯DB操作+Lock锁
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <param name="arcId">商品编号</param>
        /// <param name="totalPrice">订单总额</param>
        /// <returns></returns>
        public string POrder2(string userId, string arcId, string totalPrice)
        {
            try
            {
                lock (_lock)
                {
                    //1. 查询库存
                    var sArctile = _baseService.Entities<T_SeckillArticle>().Where(u => u.articleId == arcId).FirstOrDefault();
                    if (sArctile.articleStockNum - 1 > 0)
                    {
                        //2. 扣减库存
                        sArctile.articleStockNum--;

                        //3. 进行下单
                        T_Order tOrder = new T_Order();
                        tOrder.id = Guid.NewGuid().ToString("N");
                        tOrder.userId = userId;
                        tOrder.orderNum = Guid.NewGuid().ToString("N");
                        tOrder.articleId = arcId;
                        tOrder.orderTotalPrice = Convert.ToDecimal(totalPrice);
                        tOrder.addTime = DateTime.Now;
                        tOrder.orderStatus = 0;
                        _baseService.Add<T_Order>(tOrder);
                        _baseService.SaveChange();

                        return "下单成功";
                    }
                    else
                    {
                        //卖完了
                        return "卖完了";
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
复制代码

(1). 30并发,需要2132ms,订单数量插入正确,库存扣减正确。

(2). 100并发,需要9186ms,订单数量插入正确,库存扣减正确。

 

三. 服务器缓存+队列

1.设计思路

生产者和消费者模式→流量削峰(异步的模式平滑处理请求)

A. Lock{ 事先同步DB库存到缓存→缓存查库存→判断库存→订单相关信息服务端队列中 }

B. 消费者从队列中取数据批量提交信息,依次进行(DB扣减库存+DB创建订单)

2.分析

A. 接口中彻底干掉了DB操作, 并发数提升非常大

B. 服务宕机,原队列中的下单信息全部丢失

C. 但是生产者和消费者必须在一个项目及一个进程内

3.压测结果

前提:原库存为10000,这里统计2s内可处理的并发数,以90%百分位为例,要求错误率为0。

代码分享:

初始化库存到内存缓存中

复制代码
    /// <summary>
    /// 后台任务-初始化库存到缓存中
    /// </summary>
    public class CacheBackService : BackgroundService
    {
        private IMemoryCache _cache;
        private StackExchange.Redis.IDatabase _redisDb;
        private IConfiguration _Configuration;

        public CacheBackService(IMemoryCache cache,RedisHelp redisHelp, IConfiguration Configuration)
        {
            _cache = cache;
            _redisDb = redisHelp.GetDatabase();
            _Configuration = Configuration;
        }

        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // EFCore的上下文默认注入的请求内单例的,而CacheBackService要注册成全局单例的
            // 由于二者的生命周期不同,所以不能相互注入调用,这里手动new一个EF上下文
            var optionsBuilder = new DbContextOptionsBuilder<ESHOPContext>();
            optionsBuilder.UseSqlServer(_Configuration.GetConnectionString("EFStr"));
            ESHOPContext context = new ESHOPContext(optionsBuilder.Options);
            IBaseService _baseService = new BaseService(context);

            //初始化库存信息,连临时写在这个位置,充当服务器启动的时候初始化
            var data = await _baseService.Entities<T_SeckillArticle>().Where(u => u.id == "300001").FirstOrDefaultAsync();
   //服务器缓存
                _cache.Set<int>($"{data.articleId}-sCount", data.articleStockNum);      
        }
    }
复制代码

队列定义和下单接口

复制代码
    /// <summary>
    /// 基于内存的队列
    /// </summary>
    public static class MyQueue
    {
      private static  ConcurrentQueue<string> _queue = new ConcurrentQueue<string>();
        public static ConcurrentQueue<string> GetQueue()
        {
            return _queue;
        }
    }
        /// <summary>
        /// 03-服务端缓存+队列版本+Lock
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <param name="arcId">商品编号</param>
        /// <param name="totalPrice">订单总额</param>
        /// <returns></returns>
        public string POrder3(string userId, string arcId, string totalPrice)
        {
            try
            {
                lock (_lock)
                {
                    //1. 查询库存
                    int count = _cache.Get<int>($"{arcId}-sCount");
                    if (count - 1 >= 0)
                    {
                        //2. 扣减库存
                        count = count - 1;
                        _cache.Set<int>($"{arcId}-sCount", count);

                        //3. 将下单信息存到消息队列中
                        var orderNum = Guid.NewGuid().ToString("N");
                        MyQueue.GetQueue().Enqueue($"{userId}-{arcId}-{totalPrice}-{orderNum}");

                        //4. 把部分订单信息返回给前端
                        return $"下单成功,订单信息为:userId={userId},arcId={arcId},orderNum={orderNum}";
                    }
                    else
                    {
                        //卖完了
                        return "卖完了";
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
复制代码

基于内存的消费者

复制代码
     /// <summary>
    /// 后台任务--基于内存队列的消费者(已经测试)
    /// </summary>
    public class CustomerService : BackgroundService
    {
        private IConfiguration _Configuration;
        public CustomerService(IConfiguration Configuration)
        {
            _Configuration = Configuration;
        }
        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
        {

            // EFCore的上下文默认注入的请求内单例的,而CacheBackService要注册成全局单例的
            // 由于二者的生命周期不同,所以不能相互注入调用,这里手动new一个EF上下文
            var optionsBuilder = new DbContextOptionsBuilder<ESHOPContext>();
            optionsBuilder.UseSqlServer(_Configuration.GetConnectionString("EFStr"));
            ESHOPContext context = new ESHOPContext(optionsBuilder.Options);
            IBaseService _baseService = new BaseService(context);

            Console.WriteLine("下面开始执行消费业务");
            while (true)
            {
                try
                {
                    string data = "";
                    MyQueue.GetQueue().TryDequeue(out data);
                    if (!string.IsNullOrEmpty(data))
                    {
                        List<string> tempData = data.Split('-').ToList();
                        //1.扣减库存---禁止状态追踪
                        var sArctile = context.Set<T_SeckillArticle>().AsNoTracking().Where(u => u.id == "300001").FirstOrDefault();
                        sArctile.articleStockNum = sArctile.articleStockNum - 1;
                        context.Update(sArctile);

                        //2. 插入订单信息
                        T_Order tOrder = new T_Order();
                        tOrder.id = Guid.NewGuid().ToString("N");
                        tOrder.userId = tempData[0];
                        tOrder.orderNum = tempData[3];
                        tOrder.articleId = tempData[1];
                        tOrder.orderTotalPrice = Convert.ToDecimal(tempData[2]);
                        tOrder.addTime = DateTime.Now;
                        tOrder.orderStatus = 0;
                        context.Add<T_Order>(tOrder);
                        int count = await context.SaveChangesAsync();

                        //释放一下
                        context.Entry<T_SeckillArticle>(sArctile).State = EntityState.Detached;
                        Console.WriteLine($"执行成功,条数为:{count},当前库存为:{ sArctile.articleStockNum}");
                    }
                    else
                    {
                        Console.WriteLine("暂时没有订单信息,休息一下");
                        await Task.Delay(TimeSpan.FromSeconds(1));
                    }
                }
                catch (Exception ex)
                {

                    Console.WriteLine($"执行失败:{ex.Message}");
                }
            }
        }
    }
复制代码

(1). 1000并发,需要600ms,订单数量插入正确,库存扣减正确。

(2). 2000并发,需要1500ms,订单数量插入正确,库存扣减正确。

 

 

四. Redis缓存+原子性+队列【干掉lock】

1.设计思路

生产者和消费者模式→流量削峰(异步的模式平滑处理请求)

思路同上,缓存和队列改成基于Redis的。

2. 分析

A. 引入Redis缓存和消息队列代替基于内存的缓存和队列,数据可以持久化解决了丢失问题。

B. Redis是单线程的,利用api自身的原子性,从而可以干掉lock锁。

C. 引入进程外的缓存Redis,从而可以把生产者和消费者解耦分离,可以作为两个单独的服务运行。

3. 压测结果

前提:原库存为10万,这里统计2s内可处理的并发数,以90%百分位为例,要求错误率为0。

代码分享:

初始化库存到redis缓存中

复制代码
    /// <summary>
    /// 后台任务-初始化库存到缓存中
    /// </summary>
    public class CacheBackService : BackgroundService
    {
        private IMemoryCache _cache;
        private StackExchange.Redis.IDatabase _redisDb;
        private IConfiguration _Configuration;

        public CacheBackService(IMemoryCache cache,RedisHelp redisHelp, IConfiguration Configuration)
        {
            _cache = cache;
            _redisDb = redisHelp.GetDatabase();
            _Configuration = Configuration;
        }

        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
        {
            // EFCore的上下文默认注入的请求内单例的,而CacheBackService要注册成全局单例的
            // 由于二者的生命周期不同,所以不能相互注入调用,这里手动new一个EF上下文
            var optionsBuilder = new DbContextOptionsBuilder<ESHOPContext>();
            optionsBuilder.UseSqlServer(_Configuration.GetConnectionString("EFStr"));
            ESHOPContext context = new ESHOPContext(optionsBuilder.Options);
            IBaseService _baseService = new BaseService(context);

            //初始化库存信息,连临时写在这个位置,充当服务器启动的时候初始化
            var data = await _baseService.Entities<T_SeckillArticle>().Where(u => u.id == "300001").FirstOrDefaultAsync();
            //Redis缓存
                _redisDb.StringSet($"{data.articleId}-sCount", data.articleStockNum);
        }
    }
复制代码

下单接口

复制代码
        /// <summary>
        /// 04-Redis缓存+队列
        /// </summary>
        /// <param name="userId">用户编号</param>
        /// <param name="arcId">商品编号</param>
        /// <param name="totalPrice">订单总额</param>
        /// <returns></returns>
        public string POrder4(string userId, string arcId, string totalPrice)
        {
            try
            {
                //1. 直接自减1
                int iCount = (int)_redisDb.StringDecrement($"{arcId}-sCount", 1);
                if (iCount >= 0)
                {
                    //2. 将下单信息存到消息队列中
                    var orderNum = Guid.NewGuid().ToString("N");
                    _redisDb.ListLeftPush(arcId, $"{userId}-{arcId}-{totalPrice}-{orderNum}");

                    //3. 把部分订单信息返回给前端
                    return $"下单成功,订单信息为:userId={userId},arcId={arcId},orderNum={orderNum}";
                }
                else
                {
                    //卖完了
                    return "卖完了";
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
复制代码

基于redis队列的消费者

复制代码
            {
                Console.WriteLine("下面开始执行消费业务");
                using (ESHOPContext db = new ESHOPContext())
                {
                    RedisHelp redisHelp = new RedisHelp("localhost:6379");
                    var redisDB = redisHelp.GetDatabase();

                    while (true)
                    {
                        try
                        {
                            var data = (string)redisDB.ListRightPop("200001");
                            if (!string.IsNullOrEmpty(data))
                            {
                                List<string> tempData = data.Split('-').ToList();

                                {
                                    //1.扣减库存 --去掉状态追踪
                                    var sArctile = db.Set<T_SeckillArticle>().AsNoTracking().Where(u => u.id == "300001").FirstOrDefault();
                                    sArctile.articleStockNum = sArctile.articleStockNum - 1;
                                    db.Update(sArctile);

                                    //2. 插入订单信息
                                    T_Order tOrder = new T_Order();
                                    tOrder.id = Guid.NewGuid().ToString("N");
                                    tOrder.userId = tempData[0];
                                    tOrder.orderNum = tempData[3];
                                    tOrder.articleId = tempData[1];
                                    tOrder.orderTotalPrice = Convert.ToDecimal(tempData[2]);
                                    tOrder.addTime = DateTime.Now;
                                    tOrder.orderStatus = 0;
                                    db.Add<T_Order>(tOrder);
                                    int count = db.SaveChanges();

                                    //释放一下--否则报错
                                    db.Entry<T_SeckillArticle>(sArctile).State = EntityState.Detached;
                                    Console.WriteLine($"执行成功,条数为:{count},当前库存为:{ sArctile.articleStockNum}");

                                }

                            }
                            else
                            {
                                Console.WriteLine("暂时没有订单信息,休息一下");
                                Thread.Sleep(1000);
                            }
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine($"执行失败-{ex.Message}");
                        }
                    }
                }
            }
复制代码

(1). 1000并发,需要600ms,订单数量插入正确,库存扣减正确。

(2). 2000并发,需要1560ms,订单数量插入正确,库存扣减正确。

 

 

MS SQL巡检系列——检查数据库上一次DBCC CHECKDB的时间 - 潇湘隐者 - 博客园

mikel阅读(500)

来源: MS SQL巡检系列——检查数据库上一次DBCC CHECKDB的时间 – 潇湘隐者 – 博客园

BCC CHECKDB检查指定数据库中的所有对象的逻辑和物理完整性,具体请参考MSDN文档。我们必须定期对数据库做完整性检查(DBCC CHECKDB),以便能及时发现一些数据库损坏(Corruption)的情况。如果你的数据库长时间没有做DBCC CHECKDB,这样是做是不合理,并且很危险的。那么我们怎么检查数据库上一次做DBCC CHECKDB的时间呢? 可以通过DBCC DBINFO来获取上一次做DBCC CHECKDB时间,DBCC DBINFO (db_name) 显示数据库的结构信息 ,它是一个未公开的命令,官方没有相关文档。适用于SQL SERVER 2005以及以上版本。

DBCC DBINFO (‘YourSQLDba’) WITH TABLERESULTS ;

DBCC DBINFO () WITH TABLERESULTS ;

 

如下所示,你找到Field值为dbi_dbccLastKnownGood的记录,表示这个数据库在2016-05-13 01:30:11.560做了DBCC CHECKDB,如果没有做过DBCC CHECKDB命令,VALUE值为1900-01-01 00:00:00.000

clipboard

下面是巡检检查DBCC CHECKDB情况的脚本(只是巡检脚本的一部分)。此处定义的是至少一周必须做一次DBCC CHECKDB,否则提示那些数据库必须做完整性检查(DBCC CHECKDB)。具体情况,可以根据自身实际情况酌情处理。

CREATE TABLE #Db
  (
    DbName sysname
  )
  CREATE TABLE #Database
  (
    DbName sysname
  );

  INSERT INTO #Db
  SELECT name FROM sys.databases WHERE state_desc='ONLINE' AND database_id !=2;

  INSERT INTO #Database
  SELECT name FROM sys.databases WHERE state_desc='ONLINE' AND database_id !=2;

CREATE TABLE #DBCC_CHECKDB
(
    [DbName]        sysname  NULL   ,
    [ParentObject]  VARCHAR(120),
    [Object]        VARCHAR(120),
    [Field]         VARCHAR(60) ,
    [Value]         VARCHAR(120)
)

DECLARE @DbName   NVARCHAR(512);
DECLARE @SqlText  NVARCHAR(1024);

SET @DbName ='';
WHILE(1=1)

BEGIN
 SELECT TOP 1
      @DbName = DbName
    FROM #Db
    WHERE DbName > @DbName -- next Dbname greater than @dbname
    ORDER BY DbName -- dbName order

  -- exit loop if no more name greater than the last one used
    If @@rowcount = 0 Break

    PRINT @DbName;
SET @SqlText='DBCC DBINFO (''' + LTRIM(RTRIM(@DbName)) + ''') WITH TABLERESULTS '
INSERT INTO #DBCC_CHECKDB([ParentObject],[Object],[Field], [Value])
EXEC(@SqlText);

UPDATE #DBCC_CHECKDB SET DbName = @DbName WHERE DbName IS NULL;

 Delete Db
  From #Db Db WHERE DbName=@DbName;

END

--SELECT * FROM #DBCC_CHECKDB WHERE Field='dbi_dbccLastKnownGood';


SELECT  db.DbName ,
        dc.ParentObject ,
        dc.Object,
        dc.Field ,
        dc.Value ,
        CASE WHEN DATEDIFF(DAY, CAST(dc.Value AS DATE), GETDATE()) > 7
             THEN 'You need do a dbcc checkdb now'
             ELSE 'dbcc checkdb is done during the past seven day'
        END AS CheckStatus
FROM    #Database db
        INNER JOIN #DBCC_CHECKDB dc ON db.DbName = dc.DbName
WHERE   Field = 'dbi_dbccLastKnownGood';

DROP TABLE #Db;
DROP TABLE #Database;
DROP TABLE #DBCC_CHECKDB;

测试效果如下截图所示,定期对服务器做巡检,就能对所有服务器的情况有所了解,当然如果服务器很多的情况下,一台一台去检查也非常麻烦,我们通过MyDBA系统将数据从其它服务器采集过来,然后在报表里面展示,非常的节省时间。

image

MS SQL巡检系列——检查重复索引 - 潇湘隐者 - 博客园

mikel阅读(489)

来源: MS SQL巡检系列——检查重复索引 – 潇湘隐者 – 博客园

前言感想一时兴起,突然想写一个关于MS SQL的巡检系列方面的文章,因为我觉得这方面的知识分享是有价值,也是非常有意义的。一方面,很多经验不足的人,对于巡检有点茫然,不知道要从哪些方面巡检,另外一方面,网上关于MS SQL巡检方面的资料好像也不是特别多。写这个系列只是一个分享,自己的初衷是一个知识梳理、总结提炼过程,有些知识和脚本也不是原创,文章很多地方也是融入了自己的一些想法和见解的,不足和肤浅之处肯定也非常多,抛砖引玉,也希望大家提意见和建议、补充,指正其中的不足之处。Stay Hungry Stay Foolish!

 

SQL Server数据库中,有可能存在重复的索引(Duplicate Indexes),这个不仅影响性能(INSERT、UPDATE、DELETE时带来额外的IO开销,当数据库维护,索引重组时也会带来额外的开销),而且占用空间。数据库存在重复索引(Duplicate Indexes)的原因是多方面的,很多时候、很多事情不是你所能完全掌控的,除非你所管理的数据库非常规范,权限控制、脚本发布非常严格、流程化。暂且不说这些,那么怎么在数据库巡检过程找出这些重复的索引(Duplicate Indexes)呢? 下面分享一个我在Premier Proactive Services中发现一个的脚本(做了一些修改和调整)。

 

我们以AdventureWorks2014数据库为例,如下所示,表[Person].[Address]下有4个索引,如下所示

 

clipboard

 

假设某个二愣子在这个表的字段StateProvinceID上创建了下面重复索引,IX_Address_N1 与IX_Address_StateProvinceID是一个重复索引。


CREATE INDEX IX_Address_N1 ON [Person].[Address](StateProvinceID);

 

那么我们执行下面脚本就能找到这个重复的索引,如下所示

;WITH    IndexColumns
          AS ( SELECT DISTINCT
                      SCHEMA_NAME(o.schema_id)     AS SchemaName    ,
                      OBJECT_NAME(o.object_id)     AS TableName     ,
                      i.name                       AS IndexName     ,
                      o.object_id                  AS [Object_ID]   ,
                      i.index_id                   AS Index_ID      ,
                      i.type_desc                 AS IndexType      ,
                      ( SELECT    CASE key_ordinal
                                    WHEN 0 THEN NULL
                                    ELSE '[' + COL_NAME(k.object_id,
                                                        column_id) + '] '
                                         + CASE WHEN is_descending_key = 1
                                                THEN 'Desc'
                                                ELSE 'Asc'
                                           END
                                  END AS [data()]
                        FROM      sys.index_columns  k WITH(NOLOCK)
                        WHERE     k.object_id = i.object_id
                                  AND k.index_id = i.index_id
                        ORDER BY  key_ordinal ,
                                  column_id
                      FOR
                        XML PATH('')
                      ) AS IndexColumns ,
                        CASE WHEN i.index_id = 1
                             THEN ( SELECT  '[' + name + ']' AS [data()]
                                    FROM    sys.columns (NOLOCK) AS c
                                    WHERE   c.object_id = i.object_id
                                            AND c.column_id NOT IN (
                                            SELECT  column_id
                                            FROM    sys.index_columns (NOLOCK)
                                                    AS kk
                                            WHERE   kk.object_id = i.object_id
                                                    AND kk.index_id = i.index_id )
                                    ORDER BY column_id
                                  FOR
                                    XML PATH('')
                                  )
                             ELSE ( SELECT  '[' + COL_NAME(k.object_id,
                                                           column_id) + ']' AS [data()]
                                    FROM    sys.index_columns k WITH(NOLOCK)
                                    WHERE   k.object_id = i.object_id
                                            AND k.index_id = i.index_id
                                            AND is_included_column = 1
                                            AND k.column_id NOT IN (
                                            SELECT  column_id
                                            FROM    sys.index_columns kk
                                            WHERE   k.object_id = kk.object_id
                                                    AND kk.index_id = 1 )
                                    ORDER BY key_ordinal ,
                                            column_id
                                  FOR
                                    XML PATH('')
                                  )
                        END AS IndexInclude
               FROM     sys.indexes  i WITH(NOLOCK)
                        INNER JOIN sys.objects o WITH(NOLOCK) ON i.object_id = o.object_id
                        INNER JOIN sys.index_columns ic  WITH(NOLOCK ) ON ic.object_id = i.object_id
                                                              AND ic.index_id = i.index_id
                        INNER JOIN sys.columns c WITH(NOLOCK) ON c.object_id = ic.object_id
                                                              AND c.column_id = ic.column_id
               WHERE    o.type = 'U'
                        AND i.index_id <> 0  -- 0 = 堆
                        AND i.type <> 3         -- 3 = XML
                        AND i.type <> 5         -- 5 = 聚集列存储索引(SQL 2014~ SQL 2016)
                        AND i.type <> 6         -- 6 = 非聚集列存储索引(SQL 2014~ SQL 2016)
                        AND i.type <> 7         -- 7 = 非聚集哈希索引(SQL 2014~ SQL 2016)
               GROUP BY o.schema_id ,
                        o.object_id ,
                        i.object_id ,
                        i.name ,
                        i.index_id ,
                        i.type_desc
             ),
        DuplicatesTable
          AS ( SELECT   ic1.SchemaName    ,
                        ic1.TableName     ,
                        ic1.IndexName     ,
                        ic1.[Object_ID]   ,
                        ic2.IndexName AS DuplicateIndexName ,
                        ic1.IndexType   ,
                        CASE WHEN ic1.index_id = 1
                             THEN ic1.IndexColumns + ' (Clustered)'
                             WHEN ic1.IndexInclude = '' THEN ic1.IndexColumns
                             WHEN ic1.IndexInclude IS NULL THEN ic1.IndexColumns
                             ELSE ic1.IndexColumns + ' INCLUDE ' + ic1.IndexInclude
                        END AS IndexCols ,
                        ic1.index_id
               FROM     IndexColumns ic1
                        JOIN IndexColumns ic2 ON ic1.object_id = ic2.object_id
                                                 AND ic1.index_id < ic2.index_id
                                                 AND ic1.IndexColumns = ic2.IndexColumns
                                                 AND ( ISNULL(ic1.IndexInclude, '') = ISNULL(ic2.IndexInclude,
                                                              '')
                                                       OR ic1.index_id = 1
                                                     )
             )
    SELECT  SchemaName ,
            TableName ,
            IndexName ,
            DuplicateIndexName ,
            IndexType,
            IndexCols ,
            Index_ID ,
          Object_ID ,
          0 AS IsXML
    FROM    DuplicatesTable dt
    ORDER BY 1 , 2 ,3

 

clipboard

 

注意,关于重复索引(Duplicate Indexes)表示存在的索引除了名字不一样外, 索引所在字段以及索引字段顺序都是一样的。An index is considered to be a duplicate if it references the same column and ordinal position as another index in the same database。 这个脚本是找出一模一样的索引,如果你创建下面索引,索引字段一样,但是有包含列字段不一样,那么这个脚本会将这个索引视为不一样的索引。有兴趣可以自己试试。

 

CREATE INDEX IX_Address_N2 ON [Person].[Address](StateProvinceID) INCLUDE (City);

 

另外关于XML索引的重复索引,可以使用下面脚本检查。

--Use the below T-SQL script to generate the complete list of duplicate XML indexes in a given database:

;WITH    XMLTable
          AS ( SELECT   OBJECT_NAME(x.object_id) AS TableName ,
                        SCHEMA_NAME(o.schema_id) AS SchemaName ,
                        x.object_id ,
                        x.name ,
                        x.index_id ,
                        x.using_xml_index_id ,
                        x.secondary_type ,
                        CONVERT(NVARCHAR(MAX), x.secondary_type_desc) AS secondary_type_desc ,
                        ic.column_id
               FROM     sys.xml_indexes x ( NOLOCK )
                        JOIN sys.objects o ( NOLOCK ) ON x.object_id = o.object_id
                        JOIN sys.index_columns (NOLOCK) ic ON x.object_id = ic.object_id
                                                              AND x.index_id = ic.index_id
             ),
        DuplicatesXMLTable
          AS ( SELECT   x1.SchemaName ,
                        x1.TableName ,
                        x1.name AS IndexName ,
                        x2.name AS DuplicateIndexName ,
                        x1.secondary_type_desc AS IndexType ,
                        x1.index_id ,
                        x1.object_id ,
                        ROW_NUMBER() OVER ( ORDER BY x1.SchemaName, x1.TableName, x1.name, x2.name ) AS seq1 ,
                        ROW_NUMBER() OVER ( ORDER BY x1.SchemaName DESC, x1.TableName DESC, x1.name DESC, x2.name DESC ) AS seq2 ,
                        NULL AS inc
               FROM     XMLTable x1
                        JOIN XMLTable x2 ON x1.object_id = x2.object_id
                                            AND x1.index_id < x2.index_id
                                            AND x1.using_xml_index_id = x2.using_xml_index_id
                                            AND x1.secondary_type = x2.secondary_type
             )
    SELECT  SchemaName ,
            TableName ,
            IndexName ,
            DuplicateIndexName ,
            IndexType  ,
            Index_ID ,
            [Object_ID] ,
            1 AS IsXML
    FROM    DuplicatesXMLTable dtxml
    ORDER BY 1 ,
             2 ,
             3;

 

在每个库跑一次这个脚本,就能将所有的重复的索引(Duplicate Indexes)全部找出,但是当手头服务器、数据库特别多时,这个工作也是一个体力活,可以将这个常规工作自动化,避免重复劳动,我将这个集成在MyDBA工具里面,只需要点击一下鼠标,就可以帮助我自动处理这些工作。

MS SQL巡检系列——检查外键字段是否缺少索引 - 潇湘隐者 - 博客园

mikel阅读(563)

来源: MS SQL巡检系列——检查外键字段是否缺少索引 – 潇湘隐者 – 博客园

前言感想一时兴起,突然想写一个关于MS SQL的巡检系列方面的文章,因为我觉得这方面的知识分享是有价值,也是非常有意义的。一方面,很多经验不足的人,对于巡检有点茫然,不知道要从哪些方面巡检,另外一方面,网上关于MS SQL巡检方面的资料好像也不是特别多。写这个系列只是一个分享,自己的初衷是一个知识梳理、总结提炼过程,有些知识和脚本也不是原创,文章很多地方融入了自己的一些想法和见解的,不足和肤浅之处肯定也非常多,抛砖引玉,也希望大家提意见和建议、补充,指正其中的不足之处。Stay Hungry Stay Foolish!

 

MS SQL巡检系列——检查重复索引

MS SQL巡检系列——检查外键字段是否缺少索引

MS SQL巡检系列——检查数据库上一次DBCC CHECKDB的时间

 

对于绝大部分情况,外键字段都有必要建立对应的索引(注意,外键约束并不会自动建立索引),关于外键字段为什么要建立索引?下面从几个简单的例子来分析一下。我们先准备测试环境数据。

CREATE TABLE PRIMARY_TB
(
    PRODUCT_CD        VARCHAR(12)      ,
    PRODUCT_DATE    DATE             ,
    PRODUCT_DESC    VARCHAR(120)     ,
    CONSTRAINT PK_PRIMARY_TB  PRIMARY KEY CLUSTERED (PRODUCT_CD)
);


SET NOCOUNT ON;
GO
DECLARE @Index INT=1;


BEGIN TRAN
WHILE @Index <= 3000
BEGIN

    INSERT INTO dbo.PRIMARY_TB
    SELECT 'Prd' + CAST(@Index AS VARCHAR(4)), GETDATE() - CEILING(RAND()*200), 'production description' + CAST(@Index AS VARCHAR(4));

    SET @Index +=1;
END

COMMIT;



CREATE TABLE FK_TB
(
    FK_ID            INT IDENTITY(1,1),
    SALES_REGION    VARCHAR(32),
    SALES_CITY        VARCHAR(32),
    PRODUCT_CD        VARCHAR(12),
    SALIES_SUM        INT,
    CONSTRAINT PK_FK_TB PRIMARY KEY CLUSTERED (FK_ID)
)
GO

ALTER TABLE [dbo].[FK_TB]  WITH CHECK ADD  CONSTRAINT [FK_PRIMARY_TB_PRODUCT_CD] FOREIGN KEY([PRODUCT_CD])
REFERENCES [dbo].[PRIMARY_TB] ([PRODUCT_CD]) ON  DELETE CASCADE;
GO


SET NOCOUNT ON;
GO
DECLARE @Index INT=1;

BEGIN TRAN
WHILE @Index <=1000000
BEGIN
    INSERT INTO FK_TB
    SELECT 'REGION'+CAST(CEILING(RAND()*20) AS VARCHAR(2)), CAST(CEILING(RAND()*300) AS VARCHAR(3)),'Prd'+ CAST(CEILING(RAND()*3000) AS VARCHAR(8)),CEILING(RAND()*100000);

    SET @Index +=1;

END

COMMIT;


UPDATE STATISTICS dbo.PRIMARY_TB WITH FULLSCAN;
UPDATE STATISTICS dbo.FK_TB WITH FULLSCAN;
GO

 

1: 外键字段建立索引,在主表与子表JOIN操作时能提高性能,减少IO操作。

DBCC DROPCLEANBUFFERS;
GO
DBCC FREEPROCCACHE;
GO
SET STATISTICS IO ON;
SET STATISTICS TIME ON;

SELECT  p.PRODUCT_CD ,
        p.PRODUCT_DATE ,
        f.SALES_REGION ,
        f.SALES_CITY ,
        f.SALIES_SUM
FROM    dbo.PRIMARY_TB p
        INNER JOIN dbo.FK_TB f ON p.PRODUCT_CD = f.PRODUCT_CD
WHERE p.PRODUCT_CD ='Prd131';
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

 

如下截图所示,如果外键字段缺少索引,这两个表关联查询时,子表就会走扫描(此处测试是聚集索引扫描),如果子表非常大(例如此处案例所示),IO开销就比较大。

clipboard

clipboard

 

我们对外键约束字段PRODUCT_CD建立下面非聚集索引IDX_FK_TB,然后对比两者的执行计划和IO开销

CREATE INDEX IDX_FK_TB ON dbo.FK_TB(PRODUCT_CD);

DBCC DROPCLEANBUFFERS;
GO
DBCC FREEPROCCACHE;
GO

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT  p.PRODUCT_CD ,
        p.PRODUCT_DATE ,
        f.SALES_REGION ,
        f.SALES_CITY ,
        f.SALIES_SUM
FROM    dbo.PRIMARY_TB p
        INNER JOIN dbo.FK_TB f ON p.PRODUCT_CD = f.PRODUCT_CD
WHERE p.PRODUCT_CD ='Prd131'

SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

clipboard

clipboard

 

你会发现执行计划从原来的聚集索引扫描(Clustered Index Scan)变成了索引查找(Index Seek),IO的减少也是非常明显的。因为这里仅仅是测试数据,复杂的生产环境,性能的提升有可能比这更加明显。

 

2:如果外键约束为CASCADE(on update/delete)时,则当修改被引用行(referenced row)时,所有引用行(referencing rows )也必须修改(更新或级联删除)。外键列上的索引能减小锁的粒度和范围,从而提高效率和性能。如下所示:

 

我们先看看缺少索引的情况。

DROP INDEX IDX_FK_TB ON dbo.FK_TB;



DBCC DROPCLEANBUFFERS;
GO
DBCC FREEPROCCACHE;
GO

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

DELETE FROM dbo.PRIMARY_TB WHERE PRODUCT_CD IN ('Prd132','Prd133')
GO
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

clipboard

clipboard

CREATE INDEX IDX_FK_TB ON dbo.FK_TB(PRODUCT_CD);

GO

DBCC DROPCLEANBUFFERS;

GO

DBCC FREEPROCCACHE;

GO

SET STATISTICS IO ON;

SET STATISTICS TIME ON;

DELETE FROM dbo.PRIMARY_TB WHERE PRODUCT_CD IN ('Prd134','Prd135')

GO

SET STATISTICS IO OFF;

SET STATISTICS TIME OFF;

clipboard

clipboard

 

3:如果外键关系为NO ACTION(on update/delete)时,那么被引用的行不能被删除,因为这个操作会导致引用行变成“孤立”。删除之前,数据库会为了有效地查找、定位行,外键列上创建索引也非常有帮助。

 

上面肤浅的构造了简单案例,并对比外键约束字段拥有索引和缺少索引时,SQL的执行计划和IO的差异,那么接下来,我们进入正题,巡检的时候,我们必须通过脚本找到数据库里面外键字段没有索引的相关表和信息,并生成对应的创建索引的脚本。如下所示。

/*
One or more tables found, with foreign key constraint defined but no supporting indexes created on the foreign key columns.

SQL Server doesnt put an index on foreign key columns by default and indexing foreign key fields in referencing tables is not required.

Foreign key columns usage must evaluated to determine whether or not indexing this column will help up increase the current
workloads performance by enhancing join performance, reducing table locking (for full table scans) while cascading updates and
deletes, etc.

*/

;
WITH    FKTable
          AS ( SELECT   SCHEMA_NAME(po.schema_id) AS 'parent_schema_name' ,
                        OBJECT_NAME(fkc.parent_object_id) AS 'parent_table_name' ,
                        OBJECT_NAME(constraint_object_id) AS 'constraint_name' ,
                        SCHEMA_NAME(ro.schema_id) AS 'referenced_schema' ,
                        OBJECT_NAME(referenced_object_id) AS 'referenced_table_name' ,
                        ( SELECT    '[' + COL_NAME(k.parent_object_id,
                                                   parent_column_id) + ']' AS [data()]
                          FROM      sys.foreign_key_columns (NOLOCK) AS k
                                    INNER JOIN sys.foreign_keys  (NOLOCK) ON k.constraint_object_id = object_id
                                                              AND k.constraint_object_id = fkc.constraint_object_id
                          ORDER BY  constraint_column_id
                        FOR
                          XML PATH('')
                        ) AS 'parent_colums' ,
                        ( SELECT    '[' + COL_NAME(k.referenced_object_id,
                                                   referenced_column_id) + ']' AS [data()]
                          FROM      sys.foreign_key_columns (NOLOCK) AS k
                                    INNER JOIN sys.foreign_keys  (NOLOCK) ON k.constraint_object_id = object_id
                                                              AND k.constraint_object_id = fkc.constraint_object_id
                          ORDER BY  constraint_column_id
                        FOR
                          XML PATH('')
                        ) AS 'referenced_columns'
               FROM     sys.foreign_key_columns fkc ( NOLOCK )
                        INNER JOIN sys.objects po ( NOLOCK ) ON fkc.parent_object_id = po.object_id
                        INNER JOIN sys.objects ro ( NOLOCK ) ON fkc.referenced_object_id = ro.object_id
               WHERE    po.type = 'U'
                        AND ro.type = 'U'
               GROUP BY po.schema_id ,
                        ro.schema_id ,
                        fkc.parent_object_id ,
                        constraint_object_id ,
                        referenced_object_id
             ),

        /* Index Columns */
        IndexColumnsTable
          AS ( SELECT   SCHEMA_NAME(o.schema_id) AS 'schema_name' ,
                        OBJECT_NAME(o.object_id) AS TableName ,
                        ( SELECT    CASE key_ordinal
                                      WHEN 0 THEN NULL
                                      ELSE '[' + COL_NAME(k.object_id,
                                                          column_id) + ']'
                                    END AS [data()]
                          FROM      sys.index_columns (NOLOCK) AS k
                          WHERE     k.object_id = i.object_id
                                    AND k.index_id = i.index_id
                          ORDER BY  key_ordinal ,
                                    column_id
                        FOR
                          XML PATH('')
                        ) AS cols
               FROM     sys.indexes (NOLOCK) AS i
                        INNER JOIN sys.objects o ( NOLOCK ) ON i.object_id = o.object_id
                        INNER JOIN sys.index_columns ic ( NOLOCK ) ON ic.object_id = i.object_id
                                                              AND ic.index_id = i.index_id
                        INNER JOIN sys.columns c ( NOLOCK ) ON c.object_id = ic.object_id
                                                              AND c.column_id = ic.column_id
               WHERE    o.type = 'U'
                        AND i.index_id > 0
               GROUP BY o.schema_id ,
                        o.object_id ,
                        i.object_id ,
                        i.name ,
                        i.index_id ,
                        i.type
             ),
        FKWithoutIndexTable
          AS ( SELECT   fk.parent_schema_name AS SchemaName ,
                        fk.parent_table_name AS TableName ,
                        fk.referenced_schema AS ReferencedSchemaName ,
                        fk.referenced_table_name AS ReferencedTableName ,
                        fk.constraint_name AS ConstraintName ,
                        fk.referenced_columns AS Referenced_Columns ,
                        fk.parent_colums AS Parent_Columns
               FROM     FKTable fk
               WHERE    NOT EXISTS ( SELECT 1
                                     FROM   IndexColumnsTable ict
                                     WHERE  fk.parent_schema_name = ict.schema_name
                                            AND fk.parent_table_name = ict.TableName
                                            AND fk.parent_colums = LEFT(ict.cols,
                                                              LEN(fk.parent_colums)) )
             )
    SELECT  @@SERVERNAME AS InstanceName ,
            DB_NAME() AS DatabaseName ,
            SchemaName ,
            TableName ,
            Parent_Columns ,
            ReferencedSchemaName ,
            ReferencedTableName ,
            Referenced_Columns ,
            ConstraintName
    INTO    #ForeignKeyWithOutIndex
    FROM    FKWithoutIndexTable
    ORDER BY DatabaseName ,
            SchemaName ,
            TableName;




--输出临时表数据
SELECT  *
FROM    #ForeignKeyWithOutIndex;


--生成外键字段缺少的索引,请抽查、检验,确认后批量执行
SELECT  'CREATE INDEX IX_' + LTRIM(RTRIM(TableName)) + '_'
        + SUBSTRING(Parent_Columns, 2, LEN(Parent_Columns) - 2) + '  ON '
        + LTRIM(RTRIM(SchemaName)) + '.' + LTRIM(RTRIM(TableName)) + '('
        + Parent_Columns + ');'
FROM    #ForeignKeyWithOutIndex;


--删除临时表
DROP TABLE #ForeignKeyWithOutIndex;

在创建这些索引前最好检查、确认一下,外键字段创建索引能提高性能,但是肯定也要特殊的场景和上下文不适合,所以最好根据实际情况决定。索引创建之后,通过监控工具监控一下数据库性能、等待事件的变化。

 

参考资料:

http://stackoverflow.com/questions/3650690/should-every-sql-server-foreign-key-have-a-matching-index

http://sqlblog.com/blogs/greg_low/archive/2008/07/29/indexing-foreign-keys-should-sql-server-do-that-automatically.aspx

http://www.sqlskills.com/blogs/kimberly/when-did-sql-server-stop-putting-indexes-on-foreign-key-columns/