Dapper, Ef core, Freesql 插入大量数据性能比较(二)

上一篇文章中,我们比较出单表插入9999行数据,Freesql > Dapper > EfCore。在本文中,我们来看看级联插入

构建9999行数据

List<Entity> datas = new List<Entity>();
for (int i = 0; i < 9999; i++)
{
    var item = new Entity
    {
        No = i + 1,
        Col1 = Guid.NewGuid().ToString("N"),
        Col2 = Guid.NewGuid().ToString("N"),
        Col3 = Guid.NewGuid().ToString("N"),
        Col4 = Guid.NewGuid().ToString("N"),
        Col5 = Guid.NewGuid().ToString("N"),
        Col6 = Guid.NewGuid().ToString("N"),
        Col7 = Guid.NewGuid().ToString("N"),
        Col8 = Guid.NewGuid().ToString("N"),
        Col9 = Guid.NewGuid().ToString("N"),
        Col10 = Guid.NewGuid().ToString("N"),
    };
    item.EntitySubs.Add(new EntitySub
    {
        Col1 = Guid.NewGuid().ToString("N"),
        Col2 = Guid.NewGuid().ToString("N"),
        Col3 = Guid.NewGuid().ToString("N"),
        Col4 = Guid.NewGuid().ToString("N"),
        Col5 = Guid.NewGuid().ToString("N"),
        Col6 = Guid.NewGuid().ToString("N"),
        Col7 = Guid.NewGuid().ToString("N"),
        Col8 = Guid.NewGuid().ToString("N"),
        Col9 = Guid.NewGuid().ToString("N"),
        Col10 = Guid.NewGuid().ToString("N"),
    });
    item.EntitySubs.Add(new EntitySub
    {
        Col1 = Guid.NewGuid().ToString("N"),
        Col2 = Guid.NewGuid().ToString("N"),
        Col3 = Guid.NewGuid().ToString("N"),
        Col4 = Guid.NewGuid().ToString("N"),
        Col5 = Guid.NewGuid().ToString("N"),
        Col6 = Guid.NewGuid().ToString("N"),
        Col7 = Guid.NewGuid().ToString("N"),
        Col8 = Guid.NewGuid().ToString("N"),
        Col9 = Guid.NewGuid().ToString("N"),
        Col10 = Guid.NewGuid().ToString("N"),
    });
    item.EntitySubs.Add(new EntitySub
    {
        Col1 = Guid.NewGuid().ToString("N"),
        Col2 = Guid.NewGuid().ToString("N"),
        Col3 = Guid.NewGuid().ToString("N"),
        Col4 = Guid.NewGuid().ToString("N"),
        Col5 = Guid.NewGuid().ToString("N"),
        Col6 = Guid.NewGuid().ToString("N"),
        Col7 = Guid.NewGuid().ToString("N"),
        Col8 = Guid.NewGuid().ToString("N"),
        Col9 = Guid.NewGuid().ToString("N"),
        Col10 = Guid.NewGuid().ToString("N"),
    });
    datas.Add(item);
}

Dapper:

static void AddDataByDapperCascade(List<Entity> datas)
{
    #region 数据格式转换
    var dataTemporarys = new List<EntityTemporary>();
    var dataSubTemporarys = new List<EntitySubTemporary>();

    for (int i = 0, length = datas.Count; i < length; i++)
    {
        var item = datas[i];
        var newItem = new EntityTemporary
        {
            No = item.No,
            Col1 = item.Col1,
            Col2 = item.Col2,
            Col3 = item.Col3,
            Col4 = item.Col4,
            Col5 = item.Col5,
            Col6 = item.Col6,
            Col7 = item.Col7,
            Col8 = item.Col8,
            Col9 = item.Col9,
            Col10 = item.Col10,
            Position = i + 1
        };
        dataTemporarys.Add(newItem);
        dataSubTemporarys.AddRange(item.EntitySubs.Select(x => new EntitySubTemporary
        {
            Col1 = x.Col1,
            Col2 = x.Col2,
            Col3 = x.Col3,
            Col4 = x.Col4,
            Col5 = x.Col5,
            Col6 = x.Col6,
            Col7 = x.Col7,
            Col8 = x.Col8,
            Col9 = x.Col9,
            Col10 = x.Col10,
            Position = i + 1
        }));
    }
    #endregion

    Stopwatch sw = new Stopwatch();
    sw.Start();
    using (var conn = new SqlConnection(connString))
    {
        conn.Open();
        string createTable = @"create table #EntityTemp ([No] int, Col1 varchar(50), Col2 varchar(50), Col3 varchar(50), Col4 varchar(50), Col5 varchar(50), Col6 varchar(50), Col7 varchar(50), Col8 varchar(50), Col9 varchar(50), Col10 varchar(50), Position int);";
        string createTable2 = @"create table #EntitySubTemp (Col1 varchar(50), Col2 varchar(50), Col3 varchar(50), Col4 varchar(50), Col5 varchar(50), Col6 varchar(50), Col7 varchar(50), Col8 varchar(50), Col9 varchar(50), Col10 varchar(50), Position int);";
        string insertTable = "INSERT INTO #EntityTemp ([No], Col1, Col2, Col3, Col4, Col5, Col6, Col7, Col8, Col9, Col10, Position) VALUES (@No, @Col1, @Col2, @Col3, @Col4, @Col5, @Col6, @Col7, @Col8, @Col9, @Col10, @Position)";
        string insertTable2 = "INSERT INTO #EntitySubTemp (Col1, Col2, Col3, Col4, Col5, Col6, Col7, Col8, Col9, Col10, Position) VALUES (@Col1, @Col2, @Col3, @Col4, @Col5, @Col6, @Col7, @Col8, @Col9, @Col10, @Position)";
        string saveSql = @"DECLARE @inserted0 TABLE ([Id] int, [Position] [int]);  
MERGE into TestAddSortByDapper t
USING #EntityTemp AS s ON 1=0  WHEN NOT MATCHED THEN  
INSERT([No], [Col1], [Col2], [Col3], [Col4], [Col5], [Col6], [Col7], [Col8], [Col9], [Col10])
VALUES(s.[No], s.[Col1], s.[Col2], s.[Col3], s.[Col4], s.[Col5], s.[Col6], s.[Col7], s.[Col8], s.[Col9], s.[Col10])
OUTPUT INSERTED.[Id], s.Position INTO @inserted0;
INSERT INTO TestAddSortByDapperSub ([Id2], [Col1], [Col2], [Col3], [Col4], [Col5], [Col6], [Col7], [Col8], [Col9], [Col10])
SELECT i.id AS id2, s.Col1, s.Col2, s.Col3, s.Col4, s.Col5, s.Col6, s.Col7, s.Col8, s.Col9, s.Col10 
FROM #EntitySubTemp s
INNER JOIN @inserted0 i ON s.[Position] = i.[Position];";
        conn.Execute(createTable + "  \r\n   " + createTable2);
        conn.Execute(insertTable, dataTemporarys);
        conn.Execute(insertTable2, dataSubTemporarys);
        conn.Execute(saveSql);
    }
    sw.Stop();
    Console.WriteLine($"通过 Dapper和临时表进行insert操作 毫时{sw.ElapsedMilliseconds}");
}

 执行结果总结

 

数据库执行结果也和我们sql代码一样,dapper也是用insert into table() values () 的方法一行行加代码,执行时间大概在6-7秒。

EfCore:

 由于efcore本身就支持级联增加,所有代码比较简单

public class TestContext : DbContext
{
    public DbSet<Entity> Entity { get; set; }
    public DbSet<EntitySub> EntitySub { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(connString);
    }
}
[Table("TestAddSortByEfCore")]
public class Entity 
{
    public int Id { get; set; }
    public int No { get; set; }
    public string Col1 { get; set; }
    public string Col2 { get; set; }
    public string Col3 { get; set; }
    public string Col4 { get; set; }
    public string Col5 { get; set; }
    public string Col6 { get; set; }
    public string Col7 { get; set; }
    public string Col8 { get; set; }
    public string Col9 { get; set; }
    public string Col10 { get; set; }

    [ForeignKey("Id2")]
    public virtual ICollection<EntitySub> EntitySubs { get; set; } = new HashSet<EntitySub>();
}

[Table("TestAddSortByEfCoreSub")]
public class EntitySub 
{ 
    public int Id { get; set; }
    public int Id2 { get; set; }
    public string Col1 { get; set; }
    public string Col2 { get; set; }
    public string Col3 { get; set; }
    public string Col4 { get; set; }
    public string Col5 { get; set; }
    public string Col6 { get; set; }
    public string Col7 { get; set; }
    public string Col8 { get; set; }
    public string Col9 { get; set; }
    public string Col10 { get; set; }
}
static void AddDataByEfCoreCascade(List<Entity> datas)
{
int r1 = 0;
    Stopwatch sw = new Stopwatch();
    sw.Start();
    using (var db = new TestContext())
    {
        db.Entity.AddRange(datas);
        r1 = db.SaveChanges();
    }
    sw.Stop();
    Console.WriteLine($"通过 EfCore 导入数据{r1}行 毫时{sw.ElapsedMilliseconds}");
}

执行结果总结

 

-- 数据库实际执行语句
(@p0 nvarchar(4000),...,@p461 int)
SET NOCOUNT ON;  
DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]);  
MERGE [TestAddSortByEfCore] 
USING (  
    VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10, 0),...,(@p451, @p452, @p453, @p454, @p455, @p456, @p457, @p458, @p459, @p460, @p461, 41)
) AS i ([Col1], [Col10], [Col2], [Col3], [Col4], [Col5], [Col6], [Col7], [Col8], [Col9], [No], _Position) ON 1=0  
WHEN NOT MATCHED THEN  
    INSERT ([Col1], [Col10], [Col2], [Col3], [Col4], [Col5], [Col6], [Col7], [Col8], [Col9], [No])  
    VALUES (i.[Col1], i.[Col10], i.[Col2], i.[Col3], i.[Col4], i.[Col5], i.[Col6], i.[Col7], i.[Col8], i.[Col9], i.[No])  
    OUTPUT INSERTED.[Id], i._Position  INTO @inserted0;    
SELECT [t].[Id] FROM [TestAddSortByEfCore] t  INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])  ORDER BY [i].[_Position]; 
(
@p11 nvarchar(4000),...,@p471 nvarchar(4000),@p472 int) SET NOCOUNT ON; DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]); MERGE [TestAddSortByEfCoreSub] USING ( VALUES (@p11, @p12, @p13, @p14, @p15, @p16, @p17, @p18, @p19, @p20, @p21, 0),...,(@p462, @p463, @p464, @p465, @p466, @p467, @p468, @p469, @p470, @p471, @p472, 41) ) AS i ([Col1], [Col10], [Col2], [Col3], [Col4], [Col5], [Col6], [Col7], [Col8], [Col9], [Id2], _Position) ON 1=0 WHEN NOT MATCHED THEN INSERT ([Col1], [Col10], [Col2], [Col3], [Col4], [Col5], [Col6], [Col7], [Col8], [Col9], [Id2]) VALUES (i.[Col1], i.[Col10], i.[Col2], i.[Col3], i.[Col4], i.[Col5], i.[Col6], i.[Col7], i.[Col8], i.[Col9], i.[Id2]) OUTPUT INSERTED.[Id], i._Position INTO @inserted0; SELECT [t].[Id] FROM [TestAddSortByEfCoreSub] t INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id]) ORDER BY [i].[_Position];

从结果我们可以看到,efcore使用的是Merge方式执行,总耗时14-15秒,这个在性能上是不能接受的。

ADO.Net BulkCopy:

static void AddDataByBulkCopyTemporary2(List<Entity> datas)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    int r = 0;

    using (SqlConnection cn = new SqlConnection(connString))
    {
        cn.Open();

        DataTable entityTable = new DataTable();
        entityTable.Columns.Add("No");
        entityTable.Columns.Add("Col1");
        entityTable.Columns.Add("Col2");
        entityTable.Columns.Add("Col3");
        entityTable.Columns.Add("Col4");
        entityTable.Columns.Add("Col5");
        entityTable.Columns.Add("Col6");
        entityTable.Columns.Add("Col7");
        entityTable.Columns.Add("Col8");
        entityTable.Columns.Add("Col9");
        entityTable.Columns.Add("Col10");
        entityTable.Columns.Add("Position");

        DataTable entitySubTable = new DataTable();
        entitySubTable.Columns.Add("Col1");
        entitySubTable.Columns.Add("Col2");
        entitySubTable.Columns.Add("Col3");
        entitySubTable.Columns.Add("Col4");
        entitySubTable.Columns.Add("Col5");
        entitySubTable.Columns.Add("Col6");
        entitySubTable.Columns.Add("Col7");
        entitySubTable.Columns.Add("Col8");
        entitySubTable.Columns.Add("Col9");
        entitySubTable.Columns.Add("Col10");
        entitySubTable.Columns.Add("Position");
        for (int i = 0; i < datas.Count; i++)
        {
            var item = datas[i];
            DataRow dr = entityTable.NewRow();
            dr[0] = item.No;
            dr[1] = item.Col1;
            dr[2] = item.Col2;
            dr[3] = item.Col3;
            dr[4] = item.Col4;
            dr[5] = item.Col5;
            dr[6] = item.Col6;
            dr[7] = item.Col7;
            dr[8] = item.Col8;
            dr[9] = item.Col9;
            dr[10] = item.Col10;
            dr[11] = i + 1;
            entityTable.Rows.Add(dr);
            foreach (var sub in item.EntitySubs)
            {
                var subDr = entitySubTable.NewRow();
                subDr[0] = sub.Col1;
                subDr[1] = sub.Col2;
                subDr[2] = sub.Col3;
                subDr[3] = sub.Col4;
                subDr[4] = sub.Col5;
                subDr[5] = sub.Col6;
                subDr[6] = sub.Col7;
                subDr[7] = sub.Col8;
                subDr[8] = sub.Col9;
                subDr[9] = sub.Col10;
                subDr[10] = i + 1;
                entitySubTable.Rows.Add(subDr);
            }
        }

        string createTable = @"create table #EntityTemp ([No] int, Col1 varchar(50), Col2 varchar(50), Col3 varchar(50), Col4 varchar(50), Col5 varchar(50), Col6 varchar(50), Col7 varchar(50), Col8 varchar(50), Col9 varchar(50), Col10 varchar(50), Position int);";
        string createTable2 = @"create table #EntitySubTemp (Col1 varchar(50), Col2 varchar(50), Col3 varchar(50), Col4 varchar(50), Col5 varchar(50), Col6 varchar(50), Col7 varchar(50), Col8 varchar(50), Col9 varchar(50), Col10 varchar(50), Position int);";
        cn.Execute(createTable);
        cn.Execute(createTable2);
        using (SqlBulkCopy sqlBulkCopy = new SqlBulkCopy(cn))
        {
            sqlBulkCopy.BatchSize = entityTable.Rows.Count;
            sqlBulkCopy.BulkCopyTimeout = 1800;
            sqlBulkCopy.DestinationTableName = "#EntityTemp";

            sqlBulkCopy.ColumnMappings.Add("No", "No");
            sqlBulkCopy.ColumnMappings.Add("Col1", "Col1");
            sqlBulkCopy.ColumnMappings.Add("Col2", "Col2");
            sqlBulkCopy.ColumnMappings.Add("Col3", "Col3");
            sqlBulkCopy.ColumnMappings.Add("Col4", "Col4");
            sqlBulkCopy.ColumnMappings.Add("Col5", "Col5");
            sqlBulkCopy.ColumnMappings.Add("Col6", "Col6");
            sqlBulkCopy.ColumnMappings.Add("Col7", "Col7");
            sqlBulkCopy.ColumnMappings.Add("Col8", "Col8");
            sqlBulkCopy.ColumnMappings.Add("Col9", "Col9");
            sqlBulkCopy.ColumnMappings.Add("Col10", "Col10");
            sqlBulkCopy.ColumnMappings.Add("Position", "Position");
            sqlBulkCopy.WriteToServer(entityTable);
        }
        using (SqlBulkCopy sqlBulkCopy2 = new SqlBulkCopy(cn))
        {
            sqlBulkCopy2.BatchSize = entitySubTable.Rows.Count;
            sqlBulkCopy2.BulkCopyTimeout = 1800;
            sqlBulkCopy2.DestinationTableName = "#EntitySubTemp";

            sqlBulkCopy2.ColumnMappings.Add("Col1", "Col1");
            sqlBulkCopy2.ColumnMappings.Add("Col2", "Col2");
            sqlBulkCopy2.ColumnMappings.Add("Col3", "Col3");
            sqlBulkCopy2.ColumnMappings.Add("Col4", "Col4");
            sqlBulkCopy2.ColumnMappings.Add("Col5", "Col5");
            sqlBulkCopy2.ColumnMappings.Add("Col6", "Col6");
            sqlBulkCopy2.ColumnMappings.Add("Col7", "Col7");
            sqlBulkCopy2.ColumnMappings.Add("Col8", "Col8");
            sqlBulkCopy2.ColumnMappings.Add("Col9", "Col9");
            sqlBulkCopy2.ColumnMappings.Add("Col10", "Col10");
            sqlBulkCopy2.ColumnMappings.Add("Position", "Position");
            sqlBulkCopy2.WriteToServer(entitySubTable);
        }

        string sql = @"DECLARE @inserted0 TABLE ([Id] int, [Position] [int]);  
MERGE into TestAddSortByBulkCopy t
USING #EntityTemp AS s ON 1=0  WHEN NOT MATCHED THEN  
INSERT ([Col1], [Col2], [Col3], [Col4], [Col5], [Col6], [Col7], [Col8], [Col9], [Col10], [No])  
VALUES (s.[Col1], s.[Col2], s.[Col3], s.[Col4], s.[Col5], s.[Col6], s.[Col7], s.[Col8], s.[Col9], s.[Col10], s.[No])  
OUTPUT INSERTED.[Id], s.Position  INTO @inserted0;
insert into TestAddSortByBulkCopySub ([Id2], [Col1], [Col2], [Col3], [Col4], [Col5], [Col6], [Col7], [Col8], [Col9], [Col10])
select i.id AS id2, s.Col1, s.Col2, s.Col3, s.Col4, s.Col5, s.Col6, s.Col7, s.Col8, s.Col9, s.Col10 
from #EntitySubTemp s
    inner join @inserted0 i on s.[Position] = i.[Position] ";
        r = cn.Execute(sql);
    }
    sw.Stop();
    Console.WriteLine($"通过 BulkCopy 导入数据{r}行  毫时{sw.ElapsedMilliseconds}");
}

执行结果总结

 

 两篇文章进行总结,dapper采用insert into table() values () 方式一行行加数据,但性能上还是挺不错的;efcore当数据大于两行则采用Merge方式,性能上略低于dapper,级联上性能比较差了,最严重的问题是批量插入顺序不对;freesql采用insert into table() values (), (), ()一次性增加多行,单表查询性能是最差的,估计是代码上问题而不是sql语句问题;Bulkcopy的性能是最好的,毕竟他是ADO.net针对大量数据而设计的。

原文地址:https://www.cnblogs.com/Cxiaoao/p/14655535.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


在开发中,有时候生成验证码的场景目前还是存在的,本篇演示不依赖第三方组件,生成随机验证码图片。 先添加验证码接口 public interface ICaptcha { /// &lt;summary&gt; /// 生成随机验证码 /// &lt;/summary&gt; /// &lt;para
后端技术 .net code 官方文档 https://docs.microsoft.com/zh-cn/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.1 上传方式:multipart/form-data 其他参数:Name,Versio
在上一篇文章中,我们比较出单表插入9999行数据,Freesql &gt;&#160;Dapper &gt; EfCore。在本文中,我们来看看级联插入 构建9999行数据 List&lt;Entity&gt; datas = new List&lt;Entity&gt;(); for (int i
需求:导入9999行数据时Dapper, Ef core, Freesql&#160;谁的性能更优,是如何执行的,级联增加谁性能更佳。 确认方法:sql server&#160;的 sys.dm_exec_query_stats SELECT TOP 1000 (select [text] from
资料整理 1.sp-api介绍:https://developer.amazonservices.com/ 2.github文档:https://github.com/amzn/selling-partner-api-docs 3.github代码:https://github.com/amzn/s
最近时间在整SM2算法,在网上看到不少代码,基本都是使用BouncyCastle库,现在这个版本算比较好的拿来分享给大家。 首先引入包&#160;Portable.BouncyCastle 完整代码见Gitee:https://gitee.com/Karl_Albright/CryptoHelper
在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示了一个完整的事件派发、订阅和处理的流程。这种实现太简单了,百十行代码就展示了一个基本工作原理。然而,要将这样的解决方案运用到实际生产环境,还有很长的路要走。今天,我们就研究一下在事件处理器中,对象生命周期的管理问题。事实上,不仅仅是在事件处理器
上文已经介绍了Identity Service的实现过程。今天我们继续,实现一个简单的Weather API和一个基于Ocelot的API网关。 回顾 《Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(一)》 Weather API Weather
最近我为我自己的应用开发框架Apworks设计了一套案例应用程序,并以Apache 2.0开源,开源地址是:https://github.com/daxnet/apworks-examples,目的是为了让大家更为方便地学习和使用.NET Core、最新的前端开发框架Angular,以及Apwork
HAL(Hypertext Application Language,超文本应用语言)是一种RESTful API的数据格式风格,为RESTful API的设计提供了接口规范,同时也降低了客户端与服务端接口的耦合度。很多当今流行的RESTful API开发框架,包括Spring REST,也都默认支
在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅、通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件总线的实现。接下来对于事件驱动型架构的讨论,就需要结合一个实际的架构案例来进行分析。在领域驱动设计的讨论范畴,CQRS架构本身就是事件驱动的,因此,
HAL,全称为Hypertext Application Language,它是一种简单的数据格式,它能以一种简单、统一的形式,在API中引入超链接特性,使得API的可发现性(discoverable)更强,并具有自描述的特点。使用了HAL的API会更容易地被第三方开源库所调用,并且使用起来也很方便
何时使用领域驱动设计?其实当你的应用程序架构设计是面向业务的时候,你已经开始使用领域驱动设计了。领域驱动设计既不是架构风格(Architecture Style),也不是架构模式(Architecture Pattern),它也不是一种软件开发方法论,所以,是否应该使用领域驱动设计,以及什么时候使用
《在ASP.NET Core中使用Apworks快速开发数据服务》一文中,我介绍了如何使用Apworks框架的数据服务来快速构建用于查询和管理数据模型的RESTful API,通过该文的介绍,你会看到,使用Apworks框架开发数据服务是何等简单快捷,提供的功能也非常多,比如对Hypermedia的
在上一讲中,我们已经完成了一个完整的案例,在这个案例中,我们可以通过Angular单页面应用(SPA)进行登录,然后通过后端的Ocelot API网关整合IdentityServer4完成身份认证。在本讲中,我们会讨论在当前这种架构的应用程序中,如何完成用户授权。 回顾 《Angular SPA基于
Keycloak是一个功能强大的开源身份和访问管理系统,提供了一整套解决方案,包括用户认证、单点登录(SSO)、身份联合、用户注册、用户管理、角色映射、多因素认证和访问控制等。它广泛应用于企业和云服务,可以简化和统一不同应用程序和服务的安全管理,支持自托管或云部署,适用于需要安全、灵活且易于扩展的用
3月7日,微软发布了Visual Studio 2017 RTM,与之一起发布的还有.NET Core Runtime 1.1.0以及.NET Core SDK 1.0.0,尽管这些并不是最新版,但也已经从preview版本升级到了正式版。所以,在安装Visual Studio 2017时如果启用了
在上文中,我介绍了如何在Ocelot中使用自定义的中间件来修改下游服务的response body。今天,我们再扩展一下设计,让我们自己设计的中间件变得更为通用,使其能够应用在不同的Route上。比如,我们可以设计一个通用的替换response body的中间件,然后将其应用在多个Route上。 O
不少关注我博客的朋友都知道我在2009年左右开发过一个名为Apworks的企业级应用程序开发框架,旨在为分布式企业系统软件开发提供面向领域驱动(DDD)的框架级别的解决方案,并对多种系统架构风格提供支持。这个框架的开发和维护我坚持了很久,一直到2015年,我都一直在不停地重构这个项目。目前这个项目在
好吧,这个题目我也想了很久,不知道如何用最简单的几个字来概括这篇文章,原本打算取名《Angular单页面应用基于Ocelot API网关与IdentityServer4ʺSP.NET Identity实现身份认证与授权》,然而如你所见,这样的名字实在是太长了。所以,我不得不缩写“单页面应用”几个字