65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 CatFactory 脚手架 Dapper

starIconstarIconstarIconstarIconstarIcon

5.00/5 (15投票s)

2017 年 11 月 2 日

CPOL

12分钟阅读

viewsIcon

54655

使用 CatFactory 脚手架 Dapper

引言

什么是 CatFactory?

CatFactory 是一个使用 C# 构建的 .NET Core 的代码生成引擎。

它是如何工作的?

CatFactory 的核心思想是导入现有的 SQL Server 实例数据库,然后为目标技术生成代码。

我们也可以用内存数据库替换 SQL Server 实例中的数据库。

导入现有数据库的流程是:

  1. 创建数据库工厂
  2. 导入数据库
  3. 创建项目实例(Entity Framework Core, Dapper 等)
  4. 构建功能(每个 schema 一个功能)
  5. 生成代码对象,这些方法会读取数据库中的所有对象并创建代码生成器的实例

目前支持以下技术:

此包是子包的核心,附加包以这种命名约定创建:CatFactory.PackageName

  • CatFactory.SqlServer
  • CatFactory.NetCore
  • CatFactory.EntityFrameworkCore
  • CatFactory.AspNetCore
  • CatFactory.Dapper

CatFactory 背后的概念

数据库类型映射

我不喜欢的一个地方是使用魔术字符串来获取 SQL 数据类型到 CLR 类型的等效映射。经过一番研究,解决类型等效性的更“花哨”的方法是有一个类,可以知道 SQL 数据类型和 CLR 类型之间的等效性。

使用这个 表格 作为参考,现在 CatFactory 有一个名为 DatabaseTypeMap 的类。Database 类包含一个名为 Mappings 的属性,其中包含所有映射。因此,对于 SQL Server 包,Import 功能会填充此属性。

类定义

namespace CatFactory.Mapping
{
    public class DatabaseTypeMap
    {
        public string DatabaseType { get; set; }
        
        public bool AllowsLengthInDeclaration { get; set; }
        
        public bool AllowsPrecInDeclaration { get; set; }
        
        public bool AllowsScaleInDeclaration { get; set; }
        
        public string ClrFullNameType { get; set; }
        
        public bool HasClrFullNameType { get; }
        
        public string ClrAliasType { get; set; }
        
        public bool HasClrAliasType { get; }
        
        public bool AllowClrNullable { get; set; }
        
        public DbType DbTypeEnum { get; set; }
        
        public bool IsUserDefined { get; set; }
        
        public string ParentDatabaseType { get; set; }
        
        public string Collation { get; set; }
    }
}

代码示例

// Get mappings
var mappings = database.DatabaseTypeMaps;

// Resolve CLR type
var mapsForString = mappings.Where(item => item.GetClrType() == typeof(string)).ToList();

// Resolve SQL Server type
var mapForVarchar = mappings.FirstOrDefault(item => item.DatabaseType == "varchar");

项目选择

项目选择是应用匹配模式的对象的设置的限制。

GlobalSelection 是项目的默认选择,包含默认的设置实例。

模式

模式 范围
Sales.OrderHeader 应用于名为 Sales.OrderHeader 的特定对象
Sales.* 应用于 Sales schema 内的所有对象
*.OrderHeader 应用于所有名为 OrderHeader 的对象,而不考虑 schema
*.* 应用于所有对象,这是全局选择

代码示例

// Apply settings for Project
project.GlobalSelection(settings =>
{
    settings.ForceOverwrite = true;
    settings.AuditEntity = new AuditEntity
        ("CreationUser", "CreationDateTime", "LastUpdateUser", "LastUpdateDateTime");
    settings.ConcurrencyToken = "Timestamp";
});

// Apply settings for specific object
project.Selection("Sales.OrderHeader", settings =>
{
    settings.EntitiesWithDataContracts = true;
});

用于代码生成的事件处理程序

为了在代码生成方面提供更灵活的方式,CatFactory 中有两个委托,一个用于在代码生成之前执行操作,另一个用于处理代码生成之后的操作。

代码示例

// Add event handlers to before and after of scaffold

project.ScaffoldingDefinition += (source, args) =>
{
    // Add code to perform operations with code builder instance before to create code file
};

project.ScaffoldedDefinition += (source, args) =>
{
    // Add code to perform operations after of create code file
};

文档对象模型

现在大多数数据库都有一个文档对象模型,这个模型提供了一种简单的方式来了解数据库和对象的结构。

SQL Server 中,有 sys.tablessys.viewsys.columns 等视图,其中包含关于这些对象的所有信息。

此外,还有像 sp_helpdm_exec_describe_first_result_set_for_object 这样的存储过程和函数,它们可以检索关于 数据库对象 和执行对象结果的列的信息。

CatFactory 中,可以通过 DbConnection 类的扩展方法访问此模型。

using (var connection = new SqlConnection("server=(local);database=OnlineStore;integrated security=yes;"))
{
	connection.Open();

	// Retrieve all tables defined in database
	var tables = connection.GetSysTables().ToList();	
}    

这些方法可用于 SQL Server 文档对象模型

  • GetSysSchemas
  • GetSysTypes
  • GetSysTables
  • GetSysViews
  • GetSysColumns
  • GetSysSequences

导入包 (Import Bag)

基于 ASP MVCViewBag 概念,这个概念允许为不同的数据库提供程序添加特定数据。

ImportBag 是一个动态属性,CatFactory 引擎将特定信息保存在此属性中,对于 SQL Server,它保存 扩展属性标量函数表函数存储过程序列

对于 Postgre SQL,它保存 序列,在未来的版本中,它将保存与数据库对象相关的其他信息,例如 存储过程*。

工作坊

Dapper 的一个让我不满意的地方是所有查询的定义都写在字符串或字符串构建器里……我更喜欢有一个构建查询的对象,这样可以减少代码行数,但我不知道这个概念是否会破坏 Dapper 的理念,我真的很想了解这一点,因为生成高质量的代码在 CatFactory 中至关重要,所以我不希望添加一个会破坏 ORM 背后主要概念的实现……

无论如何,我花了一些时间研究如何为存储库解决查询构建问题,但没有找到任何允许从对象定义构建查询的对象。有一些框架提供了 CRUD 功能,但像 LINQ 这样的东西我不知道是否存在。当然,如果我在这方面有误,请在下面的评论中告诉我。

所以,我将提供一个查询构建的草稿,花点时间告诉我您的反馈,然后请回答 2 个问题:

  1. 这个实现是否会破坏 Dapper 的概念?
  2. 您如何看待 Dapper 中为实体提供元数据?

查询构建草稿

全选

var query = QueryBuilder
    .Select<Shipper>();

// Output:
// select [ShipperID], [CompanyName], [Phone] from [dbo].[Shipper]

按主键选择

var query = QueryBuilder
    .Select<Shipper>()
    .Where("ShipperID", QueryOperator.Equals, 1);

// Output:
// select [ShipperID], [CompanyName], [Phone] from [dbo].[Shipper] where [ShipperID] = 1

Insert

var query = QueryBuilder
    .Insert<Shipper>(identity: "ShipperID");

// Output:
// insert into [dbo].[Shipper] ([CompanyName], [Phone]) values (@companyName, @phone)
// select @shipperID = @@identity

更新

var query = QueryBuilder
    .Update<Shipper>(key: new string[] { "ShipperID" });

// Output:
// update [dbo].[Shipper] set [CompanyName] = @companyName, _
//        [Phone] = @phone where [ShipperID] = @shipperID

删除

var query = QueryBuilder
    .Delete<Shipper>(key: new string[] { "ShipperID" });

// Output:
// delete from [dbo].[Shipper] where [ShipperID] = @shipperID

按条件选择

// Search by
var query = QueryBuilder
    .Select<Shipper>()
    .Where("CompanyName", QueryOperator.Like, "%a%")
    .And("Phone", QueryOperator.Like, "%a%");

// Output:
// select [ShipperID], [CompanyName], [Phone] from [Shipper] _
// where [CompanyName] like '%a%' and [Phone] like '%a%'

Shipper 是这个示例中的一个实体,我发现了此解决方案的以下问题:

  • 没有 schema 的信息(例如 dbo, Production, Purchasing, Sales)
  • 无法知道名称为“Order Details”的表是否映射到名为 OrderDetail 的实体

以上几点可以通过表和实体(C# 类)的任何信息来解决,比如元数据,我们可以有一个名为 IEntity 的接口,像这样:

public interface IEntity
{
	Table ToTable();
}

然后创建一个名为 Shipper 的类并实现该接口:

public class Shipper : IEntity
{
 public int? ShipperID { get; set; }
 
 public string CompanyName { get; set; }
 
 public string Phone { get; set; }
 
 public Table ToTable()
  => new Table
  {
   Schema = "dbo",
   Name = "Shipper",
   Identity = new Identity("ShipperID", 1, 1),
   PrimaryKey = new PrimaryKey("ShipperID")
   Columns = new List<Column>
   {
    new Column
    {
     Name = "ShipperID",
     Type = "int"
    },
    new Column
    {
     Name = "CompanyName",
     Type = "varchar",
     Lenght = 50
    },
    new Column
    {
     Name = "Phone",
     Type = "varchar",
     Length = 25
    }
   }
  };
 }
}

这样,我们就可以拥有所有实体的“元数据”,并动态获取这些定义来构建查询,从而减少存储库中的代码行数。

TableColumnsIdentityPrimaryKey 的定义已存在于 CatFactory 中,因此我们可以为此目的重用这些定义。 :)

请告诉我您对此实现的看法,有道理吗?

根据开发者的反馈以及为了提供更好的用户体验,我正在进行一些改进,以获得一种更简洁的方式来使用 CatFactory

与数据库协同工作

// Import from existing database
var database = SqlServerDatabaseFactory.Import("YourConnectionStringHere");

// Read all tables
foreach (var table in database.Tables)
{
    // Check primary key on table's definition
    if (table.PrimaryKey == null)
    {
        continue;
    }
    
    // Check identity on table's definition
    if (table.Identity != null)
    {
        var identityName = table.Identity.Name;
    }
    
    // Read all columns
    foreach (var column in table.Columns)
    {
        // Get equivalent CLR type from column type
        var clrType = database.ResolveType(column).GetClrType();
    }
}

软件包

  • CatFactory
  • CatFactory.SqlServer
  • CatFactory.NetCore
  • CatFactory.EntityFrameworkCore
  • CatFactory.AspNetCore
  • CatFactory.Dapper
  • CatFactory.TypeScript

您可以在 NuGet Gallery 上查看 CatFactory 包的下载统计数据。

背景

生成代码是软件开发中的常见任务,大多数开发人员一生中都会编写一次“代码生成器”。

使用 Entity Framework 6.x 时,我使用过 EF 向导,它是一个很棒的工具,尽管存在一些限制,例如:

  • 不支持 Fluent API 的代码生成
  • 不支持存储库的代码生成
  • 不支持单元测试的代码生成
  • 自定义代码生成非常复杂,有时甚至不可能

使用 Entity Framework Core 时,我通过命令行从现有数据库生成代码,EF Core 团队提供了很棒的命令行工具,但仍然存在上述相同的限制。

因此,CatFactory 旨在解决这些限制,并提供一种简单的 Code First 方式来生成 Entity Framework Core 代码。

CatFactory 的早期版本中,使用 StringBuilder 来生成类或接口代码,但几年前,生成类或接口定义的代码方式发生了变化,CatFactory 允许以一种简单清晰的方式定义类或接口的结构,然后使用 CodeBuilder 的实例来生成 C# 代码。

让我们开始生成 C# 类代码。

var definition = new CSharpClassDefinition
{
    Namespace = "OnlineStore.DomainDrivenDesign",
    AccessModifier = AccessModifier.Public,
    Name = "StockItem",
    Properties =
    {
        new PropertyDefinition(AccessModifier.Public, "string", "GivenName")
        {
            IsAutomatic = true
        },
        new PropertyDefinition(AccessModifier.Public, "string", "MiddleName")
        {
            IsAutomatic = true
        },
        new PropertyDefinition(AccessModifier.Public, "string", "Surname")
        {
            IsAutomatic = true
        },
        new PropertyDefinition(AccessModifier.Public, "string", "FullName")
        {
            IsReadOnly = true,
            GetBody =
            {
                new CodeLine(" return GivenName + (string.IsNullOrEmpty
                  (MiddleName) ? \"\" : \" \" + MiddleName) + \" \" + Surname)")
            }
        }
    }
};

CSharpCodeBuilder.CreateFiles("C:\\Temp", string.Empty, true, definition);

这是输出的代码:

namespace OnlineStore.DomainDrivenDesign
{
	public class StockItem
	{
		public string GivenName { get; set; }

		public string MiddleName { get; set; }

		public string Surname { get; set; }

		public string FullName
			=> GivenName + (string.IsNullOrEmpty(MiddleName) ? "" : " " + 
                            MiddleName) + " " + Surname;

	}
}

可以使用这些类型来创建对象定义,如类或接口:

  • EventDefinition
  • FieldDefinition
  • ClassConstructorDefinition
  • FinalizerDefinition
  • IndexerDefinition
  • PropertyDefinition
  • MethodDefinition

ClassConstructorDefinitionFinalizerDefinitionIndexerDefinitionPropertyDefinitionMethodDefinition 这样的类型可以有代码块,这些块是 ILine 数组。

ILine 接口允许表示代码块内的代码行,有不同类型的行:

  1. CodeLine
  2. CommentLine
  3. EmptyLine
  4. PreprocessorDirectiveLine
  5. ReturnLine
  6. TodoLine

让我们创建一个带方法的类。

var classDefinition = new CSharpClassDefinition
{
 Namespace = "OnlineStore.BusinessLayer",
 AccessModifier = AccessModifier.Public,
 Name = "WarehouseService",
 Fields =
 {
  new FieldDefinition("OnlineStoreDbContext", "DbContext")
  {
   IsReadOnly = true
  }
 },
 Constructors =
 {
  new ClassConstructorDefinition
  {
   AccessModifier = AccessModifier.Public,
   Parameters =
   {
    new ParameterDefinition("OnlineStoreDbContext", "dbContext")
   },
   Lines =
   {
    new CodeLine("DbContext = dbContext;")
   }
  }
 },
 Methods =
 {
  new MethodDefinition
  {
   AccessModifier = AccessModifier.Public,
   Type = "IListResponse<StockItem>",
   Name = "GetStockItems",
   Lines =
   {
    new TodoLine(" Add filters"),
    new CodeLine("return DbContext.StockItems.ToList();")
   }
  }
 }
};

CSharpCodeBuilder.CreateFiles("C:\\Temp", string.Empty, true, definition);

这是输出的代码:

namespace OnlineStore.BusinessLayer
{
 public class WarehouseService
 {
  private readonly OnlineStoreDbContext DbContext;

  public WarehouseService(OnlineStoreDbContext dbContext)
  {
   DbContext = dbContext;
  }

  public IListResponse<StockItem> GetStockItems()
  {
   // todo:  Add filters
   return DbContext.StockItems.ToList();
  }
 }
}

现在,让我们将一个接口重构为一个类。

var interfaceDefinition = classDefinition.RefactInterface();

CSharpCodeBuilder.CreateFiles(@"C:\Temp", string.Empty, true, interfaceDefinition);

这是输出的代码:

public interface IWarehouseService
{
	IListResponse<StockItem> GetStockItems();
}

我知道有些开发者会拒绝这个设计,声称生成一个只有 4 个属性的简单类需要写很多代码,但请记住,CatFactory 的方式看起来像是对定义的“清晰”转录。

CatFactory.NetCore 使用 CatFactory 的模型来生成 C# 代码,那么问题是:CatFactory.Dapper 包是什么?

它是一个允许使用 CatFactory 提供的代码生成引擎来生成 Dapper 代码的包。

必备组件

技能

  • OOP
  • AOP
  • ORM
  • C#
  • 设计模式(存储库和单元测试)

软件先决条件

  • .NET Core
  • Visual Studio 2017 或 VS Code
  • 可访问现有的 SQL Server 实例

Using the Code

请按照以下步骤使用 CatFactory 搭建 Dapper

步骤 01 - 创建示例数据库

查看示例数据库以了解架构中的每个组件。在此数据库中,有 4 个 schema:DboHumanResourcesWarehouseSales

每个 schema 代表商店公司的一个部门,请牢记这一点,因为所有代码的设计都遵循这一方面;目前,此代码仅为 ProductionSales schema 实现功能。

所有表都有一个包含一列的主键,并包含用于创建、上次更新和并发令牌的列。

架构 名称
dbo 变更日志
dbo 变更日志排除
dbo 国家
dbo 国家货币
dbo 货币
dbo EventLog(事件日志)
人力资源 员工
人力资源 员工地址
人力资源 员工邮箱
销售 客户
销售 订单明细
销售 订单头
销售 订单状态
销售 支付方式
销售 发货人
仓库 Location
仓库 产品
仓库 产品类别
仓库 产品库存

您可以在此链接找到数据库的脚本:GitHub 上的在线商店数据库脚本

请记住:这只是一个示例数据库,仅用于概念演示。

步骤 02 - 创建项目

创建一个 .NET Core 的控制台应用程序,在某些情况下,您可以将一个项目添加到现有的解决方案中,但要有一个名称或后缀来表明这是一个用于代码生成的项目,例如:OnLineStore.CatFactory.Dapper

为您的项目添加以下包:

名称 版本 描述
CatFactory.SqlServer 1.0.0-beta-sun-build32 提供 SQL Server 数据库的导入功能
CatFactory.Dapper 1.0.0-beta-sun-build33 为 Dapper 提供代码生成

保存所有更改并生成项目。

步骤 03 - 添加代码以生成

将此代码添加到 Main 方法中:

// Create database factory
var databaseFactory = new SqlServerDatabaseFactory
{
 DatabaseImportSettings = new DatabaseImportSettings
 {
  ConnectionString = "server=(local);database=OnlineStore;integrated security=yes;",
  ImportScalarFunctions = true,
  ImportTableFunctions = true,
  ImportStoredProcedures = true,
  Exclusions =
  {
   "dbo.sysdiagrams",
   "dbo.fn_diagramobjects"
  }
 }
};

// Import database
var database = databaseFactory.Import();

// Create instance of Dapper Project
var project = new DapperProject
{
 Name = "OnlineStore.Core",
 Database = database,
 OutputDirectory = @"C:\Projects\OnlineStore.Core"
};

// Apply settings for project
project.GlobalSelection(settings =>
{
 settings.ForceOverwrite = true;
 settings.UpdateExclusions = new List<string> 
            { "CreationUser", "CreationDateTime", "Timestamp" };
 settings.InsertExclusions = new List<string> 
            { "LastUpdateUser", "LastUpdateDateTime", "Timestamp" };
});

project.Selection("Sales.OrderHeader", 
                   settings => settings.AddPagingForGetAllOperation = true);

// Build features for project, group all entities by schema into a feature
project.BuildFeatures();

// Add event handlers to before and after of scaffold

project.ScaffoldingDefinition += (source, args) =>
{
 // Add code to perform operations with code builder instance before to create code file
};

project.ScaffoldedDefinition += (source, args) =>
{
 // Add code to perform operations after of create code file
};

// Scaffolding =^^=
project
 .ScaffoldEntityLayer()
 .ScaffoldDataLayer();

如果您想了解更多关于使用 CatFactory.SqlServer 包导入数据库的信息,请查看 wiki:CatFactory.SqlServer Wiki

方法的说明

DapperProject 实例的扩展方法
名称 描述
ScaffoldEntityLayer 从表、视图、标量函数和表函数生成实体
ScaffoldDataLayer 生成契约和存储库

设置 CatFactory 以支持 Dapper

此外,还有 DapperProject 实例的设置,我们将仔细查看这些设置:

名称 默认值 描述
ForceOverwrite false 指示代码生成器是否应覆盖现有文件
SimplifyDataTypes false 指示代码生成器是否应将 CLR 类型更改为本机类型(例如,Int32 => int
UseAutomaticPropertiesForEntities true 指示实体类是否使用自动属性,如果值为 false,则实体将包含 private 字段
EnableDataBindings false 实现 INotifyPropertyChanged 属性并为类定义添加带有字段的属性
UseStringBuilderForQueries true 指示查询是否将使用 StringBuilder 构建
InsertExclusions   设置在 insert 方法中要排除的列列表
UpdateExclusions   设置在 update 方法中要排除的列列表
AddPagingForGetAllOperation false GetAll 方法添加 SQL 代码以支持分页

此外,我们还可以更改命名空间,在项目实例中设置输出命名空间的值:

命名空间 默认值 描述
Entity Layer EntityLayer 获取或设置实体层的命名空间
数据层 DataLayer 获取或设置数据层的命名空间
合同 合同 获取或设置数据层中契约的命名空间
存储库 存储库 获取或设置数据层中存储库的命名空间

步骤 04 - 创建控制台项目

显然,我们添加这段简单的代码来测试输出的代码是否没有错误。如果我们生成的代码在 Web API 项目中使用,我们需要添加依赖注入和其他东西,请参阅本文末尾的链接列表了解更多信息。

现在我们可以转到输出目录创建一个 .NET Core 控制台项目,并根据您的选择添加代码。

全部获取

// Create connection for SQL Server
using (var connection = new SqlConnection("server=(local);
       database=OnlineStore;integrated security=yes;"))
{
 // Create repository instance
 var repository = new SalesRepository(connection);
 
 // Retrieve orders
 var orders = await repository.GetOrderHeadersAsync();
}

按主键获取

// Create connection for SQL Server
using (var connection = new SqlConnection("server=(local);
       database=OnlineStore;integrated security=yes;"))
{
 // Create repository instance
 var repository = new SalesRepository(connection);
 
 // Get entity by id
 var entity = await repository.GetOrderHeaderAsync(new Order(1));
}

按唯一约束获取(如果实体有唯一约束)

// Create connection for SQL Server
using (var connection = new SqlConnection("server=(local);
       database=OnlineStore;integrated security=yes;"))
{
 // Create repository instance
 var repository = new SalesRepository(connection);
 
 // Get entity by id
 var entity = await repository.GetProductByProductNameAsync
              (new Product { ProductName = "The King of Fighters XIV"});
}

Add:

// Create connection for SQL Server
using (var connection = new SqlConnection("server=(local);
       database=OnlineStore;integrated security=yes;"))
{
 // Create repository instance
 var repository = new SalesRepository(connection);
 
 // Create instance for entity
 var entity = new OrderHeader();
  
 // Set values for properties
 // e.g. entity.Total = 29.99m;
  
 // Add entity in database
 await repository.AddOrderHeaderAsync(entity);
}

更新:

// Create connection for SQL Server
using (var connection = new SqlConnection("server=(local);
       database=OnlineStore;integrated security=yes;"))
{
 // Create repository instance
 var repository = new SalesRepository(connection);
 
 // Retrieve entity by id
 var entity = await repository.GetOrderHeaderAsync(new OrderHeader(1));
  
 // Set values for properties
 // e.g. entity.Total = 29.99m;
  
 // Add entity in database
 await repository.UpdateOrderHeaderAsync(entity);
}

移除:

// Create connection for SQL Server
using (var connection = new SqlConnection("server=(local);
       database=OnlineStore;integrated security=yes;"))
{
 // Create repository instance
 var repository = new SalesRepository(connection);
 
 // Retrieve entity by id instance for entity
 var entity = await repository.GetOrderHeaderAsync(new OrderHeader(1));
  
 // Add entity in database
 await repository.RemoveOrderHeaderAsync(entity);
}

表函数:

// Create connection for SQL Server
using (var connection = new SqlConnection("server=(local);
       database=OnlineStore;integrated security=yes;"))
{
 // Create repository instance
 var repository = new SalesRepository(connection);
 
 // Retrieve contacts for customer
 var customerContacts = await repository.GetUfnGetCustomerContactsAsync(1);
}

上面的代码示例不包含错误处理,这些只是示例,用于展示如何使用生成的代码。

名称 版本 描述
Dapper 最新版本 提供数据库操作的辅助方法

所有代码如何协同工作?

我们需要创建连接选项,然后创建存储库实例,最后调用存储库实例中的方法。

对于这种架构实现,我们正在使用 .NET 命名约定:类、接口和方法使用 PascalCase;参数使用 camelCase。

生成代码的命名空间

  1. EntityLayer
  2. DataLayer
  3. DataLayer\Contracts
  4. DataLayer\Repositories

EntityLayer 中,我们将放置所有实体。在此上下文中,实体意味着一个表示数据库中的表或视图的类,有时实体也被称为 POCO(Plain Old Common language runtime Object),意思是只有一个属性的类,没有方法或其他东西(事件)。

DataLayer 中,我们将放置 DbContextAppSettings,因为它们是 DataLayer 的通用类。

DataLayer\Contracts 中,我们将放置所有表示操作目录的接口。我们专注于 schema,并将为每个 schema 创建一个接口,以及为默认 schema(dbo)创建 Store 契约。

DataLayer\Repositories 中,我们将放置 Contracts 定义的实现。

我们可以查看关于企业 Entity Framework Core 的链接,并理解本指南允许我们生成所有这些代码以减少编写代码的时间。

代码审查

我们将审查为某个实体生成的一些代码,以理解这种设计。

OrderHeader 类的代码

using System;

namespace OnlineStore.Core.EntityLayer.Sales
{
 public class OrderHeader : IEntity
 {
  public OrderHeader()
  {
  }

  public OrderHeader(long? orderHeaderID)
  {
   OrderHeaderID = orderHeaderID;
  }

  public long? OrderHeaderID { get; set; }

  public short? OrderStatusID { get; set; }

  public int? CustomerID { get; set; }

  public int? EmployeeID { get; set; }

  public int? ShipperID { get; set; }

  public DateTime? OrderDate { get; set; }

  public decimal? Total { get; set; }

  public string CurrencyID { get; set; }

  public Guid? PaymentMethodID { get; set; }

  public int? DetailsCount { get; set; }

  public long? ReferenceOrderID { get; set; }

  public string Comments { get; set; }

  public string CreationUser { get; set; }

  public DateTime? CreationDateTime { get; set; }

  public string LastUpdateUser { get; set; }

  public DateTime? LastUpdateDateTime { get; set; }

  public byte[] Timestamp { get; set; }
 }
}

IHumanResourcesRepository 接口的代码

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using OnlineStore.Core.EntityLayer;
using OnlineStore.Core.DataLayer.Contracts;
using OnlineStore.Core.EntityLayer.HumanResources;
using OnlineStore.Core.EntityLayer.Sales;
using OnlineStore.Core.EntityLayer.Warehouse;

namespace OnlineStore.Core.DataLayer.Contracts
{
 public interface IHumanResourcesRepository : IRepository
 {
  Task<IEnumerable<Employee>> GetEmployeesAsync();

  Task<Employee> GetEmployeeAsync(Employee entity);

  Task<Int32> AddEmployeeAsync(Employee entity);

  Task<Int32> UpdateEmployeeAsync(Employee entity);

  Task<Int32> RemoveEmployeeAsync(Employee entity);

  Task<IEnumerable<EmployeeAddress>> GetEmployeeAddressesAsync(Int32? employeeID = null);

  Task<EmployeeAddress> GetEmployeeAddressAsync(EmployeeAddress entity);

  Task<Int32> AddEmployeeAddressAsync(EmployeeAddress entity);

  Task<Int32> UpdateEmployeeAddressAsync(EmployeeAddress entity);

  Task<Int32> RemoveEmployeeAddressAsync(EmployeeAddress entity);

  Task<IEnumerable<EmployeeEmail>> GetEmployeeEmailsAsync(Int32? employeeID = null);

  Task<EmployeeEmail> GetEmployeeEmailAsync(EmployeeEmail entity);

  Task<Int32> AddEmployeeEmailAsync(EmployeeEmail entity);

  Task<Int32> UpdateEmployeeEmailAsync(EmployeeEmail entity);

  Task<Int32> RemoveEmployeeEmailAsync(EmployeeEmail entity);

  Task<IEnumerable<EmployeeInfo>> GetEmployeeInfosAsync(Int32? employeeID = null);
 }
}

ISalesRepository 接口的代码

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using OnlineStore.Core.EntityLayer;
using OnlineStore.Core.DataLayer.Contracts;
using OnlineStore.Core.EntityLayer.HumanResources;
using OnlineStore.Core.EntityLayer.Sales;
using OnlineStore.Core.EntityLayer.Warehouse;

namespace OnlineStore.Core.DataLayer.Contracts
{
 public interface ISalesRepository : IRepository
 {
  Task<IEnumerable<Customer>> GetCustomersAsync(int pageSize = 10, int pageNumber = 1);

  Task<Customer> GetCustomerAsync(Customer entity);

  Task<Int32> AddCustomerAsync(Customer entity);

  Task<Int32> UpdateCustomerAsync(Customer entity);

  Task<Int32> RemoveCustomerAsync(Customer entity);

  Task<IEnumerable<CustomerAddress>> GetCustomerAddressesAsync
      (int pageSize = 10, int pageNumber = 1, Int32? customerID = null);

  Task<CustomerAddress> GetCustomerAddressAsync(CustomerAddress entity);

  Task<Int32> AddCustomerAddressAsync(CustomerAddress entity);

  Task<Int32> UpdateCustomerAddressAsync(CustomerAddress entity);

  Task<Int32> RemoveCustomerAddressAsync(CustomerAddress entity);

  Task<IEnumerable<CustomerEmail>> GetCustomerEmailsAsync
      (int pageSize = 10, int pageNumber = 1, Int32? customerID = null);

  Task<CustomerEmail> GetCustomerEmailAsync(CustomerEmail entity);

  Task<Int32> AddCustomerEmailAsync(CustomerEmail entity);

  Task<Int32> UpdateCustomerEmailAsync(CustomerEmail entity);

  Task<Int32> RemoveCustomerEmailAsync(CustomerEmail entity);

  Task<IEnumerable<OrderDetail>> GetOrderDetailsAsync
        (int pageSize = 10, int pageNumber = 1, Int64? orderHeaderID = null, 
         Int32? productID = null);

  Task<OrderDetail> GetOrderDetailAsync(OrderDetail entity);

  Task<OrderDetail> GetOrderDetailByOrderHeaderIDAndProductIDAsync(OrderDetail entity);

  Task<Int32> AddOrderDetailAsync(OrderDetail entity);

  Task<Int32> UpdateOrderDetailAsync(OrderDetail entity);

  Task<Int32> RemoveOrderDetailAsync(OrderDetail entity);

  Task<IEnumerable<OrderHeader>> GetOrderHeadersAsync
       (int pageSize = 10, int pageNumber = 1, String currencyID = null, 
        Int32? customerID = null, Int32? employeeID = null, 
        Int16? orderStatusID = null, Guid? paymentMethodID = null, Int32? shipperID = null);

  Task<OrderHeader> GetOrderHeaderAsync(OrderHeader entity);

  Task<Int32> AddOrderHeaderAsync(OrderHeader entity);

  Task<Int32> UpdateOrderHeaderAsync(OrderHeader entity);

  Task<Int32> RemoveOrderHeaderAsync(OrderHeader entity);

  Task<IEnumerable<OrderStatus>> 
       GetOrderStatusesAsync(int pageSize = 10, int pageNumber = 1);

  Task<OrderStatus> GetOrderStatusAsync(OrderStatus entity);

  Task<Int32> AddOrderStatusAsync(OrderStatus entity);

  Task<Int32> UpdateOrderStatusAsync(OrderStatus entity);

  Task<Int32> RemoveOrderStatusAsync(OrderStatus entity);

  Task<IEnumerable<PaymentMethod>> 
          GetPaymentMethodsAsync(int pageSize = 10, int pageNumber = 1);

  Task<PaymentMethod> GetPaymentMethodAsync(PaymentMethod entity);

  Task<Int32> AddPaymentMethodAsync(PaymentMethod entity);

  Task<Int32> UpdatePaymentMethodAsync(PaymentMethod entity);

  Task<Int32> RemovePaymentMethodAsync(PaymentMethod entity);

  Task<IEnumerable<Shipper>> GetShippersAsync(int pageSize = 10, int pageNumber = 1);

  Task<Shipper> GetShipperAsync(Shipper entity);

  Task<Int32> AddShipperAsync(Shipper entity);

  Task<Int32> UpdateShipperAsync(Shipper entity);

  Task<Int32> RemoveShipperAsync(Shipper entity);

  Task<IEnumerable<OrderSummary>> 
          GetOrderSummariesAsync(int pageSize = 10, int pageNumber = 1, 
          Int64? orderHeaderID = null, Int16? orderStatusID = null, 
          Int32? customerID = null, Int32? employeeID = null, Int32? shipperID = null);

  Task<IEnumerable<UfnGetCustomerContactResult>> 
               GetUfnGetCustomerContactsAsync(Int32? customerID);
 }
}

Repository 类的代码

using System.Data;
using OnlineStore.Core.EntityLayer;

namespace OnlineStore.Core.DataLayer.Contracts
{
 public class Repository
 {
  public Repository(IDbConnection connection)
  {
   Connection = connection;
  }

  protected IDbConnection Connection { get; }

 }
}

HumanResourcesRepository 的代码

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using OnlineStore.Core.EntityLayer;
using OnlineStore.Core.DataLayer.Contracts;
using OnlineStore.Core.EntityLayer.HumanResources;
using OnlineStore.Core.EntityLayer.Sales;
using OnlineStore.Core.EntityLayer.Warehouse;

namespace OnlineStore.Core.DataLayer.Repositories
{
 public class HumanResourcesRepository : Repository, IHumanResourcesRepository
 {
  public HumanResourcesRepository(IDbConnection connection)
   : base(connection)
  {
  }
  
  // Another operations

  public async Task<IEnumerable<EmployeeInfo>> 
            GetEmployeeInfosAsync(Int32? employeeID = null)
  {
   // Create string builder for query
   var query = new StringBuilder();
   
   // Create sql statement
   query.Append(" select ");
   query.Append("  [EmployeeID], ");
   query.Append("  [EmployeeName], ");
   query.Append("  [EmployeeAddresses], ");
   query.Append("  [EmployeeEmails] ");
   query.Append(" from ");
   query.Append("  [HumanResources].[EmployeeInfo] ");
   query.Append(" where ");
   query.Append("  (@employeeID is null or [EmployeeID] = @employeeID) and ");
   
   // Create parameters collection
   var parameters = new DynamicParameters();
   
   // Add parameters to collection
   parameters.Add("@employeeID", employeeID);
   
   // Retrieve result from database and convert to typed list
   return await Connection.QueryAsync<EmployeeInfo>(query.ToString());
  }
 }
}

SalesRepository 类的代码

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using OnlineStore.Core.EntityLayer;
using OnlineStore.Core.DataLayer.Contracts;
using OnlineStore.Core.EntityLayer.HumanResources;
using OnlineStore.Core.EntityLayer.Sales;
using OnlineStore.Core.EntityLayer.Warehouse;

namespace OnlineStore.Core.DataLayer.Repositories
{
 public class SalesRepository : Repository, ISalesRepository
 {
  public SalesRepository(IDbConnection connection)
   : base(connection)
  {
  }

  // Another operations

  public async Task<IEnumerable<OrderHeader>> 
           GetOrderHeadersAsync(int pageSize = 10, int pageNumber = 1, 
           String currencyID = null, Int32? customerID = null, 
           Int32? employeeID = null, Int16? orderStatusID = null, 
           Guid? paymentMethodID = null, Int32? shipperID = null)
  {
   // Create string builder for query
   var query = new StringBuilder();
   
   // Create sql statement
   query.Append(" select ");
   query.Append("  [OrderHeaderID], ");
   query.Append("  [OrderStatusID], ");
   query.Append("  [CustomerID], ");
   query.Append("  [EmployeeID], ");
   query.Append("  [ShipperID], ");
   query.Append("  [OrderDate], ");
   query.Append("  [Total], ");
   query.Append("  [CurrencyID], ");
   query.Append("  [PaymentMethodID], ");
   query.Append("  [DetailsCount], ");
   query.Append("  [ReferenceOrderID], ");
   query.Append("  [Comments], ");
   query.Append("  [CreationUser], ");
   query.Append("  [CreationDateTime], ");
   query.Append("  [LastUpdateUser], ");
   query.Append("  [LastUpdateDateTime], ");
   query.Append("  [Timestamp] ");
   query.Append(" from ");
   query.Append("  [Sales].[OrderHeader] ");
   query.Append(" where ");
   query.Append("  (@currencyID is null or [CurrencyID] = @currencyID) and ");
   query.Append("  (@customerID is null or [CustomerID] = @customerID) and ");
   query.Append("  (@employeeID is null or [EmployeeID] = @employeeID) and ");
   query.Append("  (@orderStatusID is null or [OrderStatusID] = @orderStatusID) and ");
   query.Append("  (@paymentMethodID is null _
                    or [PaymentMethodID] = @paymentMethodID) and ");
   query.Append("  (@shipperID is null or [ShipperID] = @shipperID)  ");
   query.Append(" order by ");
   query.Append(" [OrderHeaderID] ");
   query.Append(" offset @pageSize * (@pageNumber - 1) rows ");
   query.Append(" fetch next @pageSize rows only ");
   
   // Create parameters collection
   var parameters = new DynamicParameters();
   
   // Add parameters to collection
   parameters.Add("@pageSize", pageSize);
   parameters.Add("@pageNumber", pageNumber);
   parameters.Add("@currencyID", currencyID);
   parameters.Add("@customerID", customerID);
   parameters.Add("@employeeID", employeeID);
   parameters.Add("@orderStatusID", orderStatusID);
   parameters.Add("@paymentMethodID", paymentMethodID);
   parameters.Add("@shipperID", shipperID);
   
   // Retrieve result from database and convert to typed list
   return await Connection.QueryAsync<OrderHeader>
            (new CommandDefinition(query.ToString(), parameters));
  }

  public async Task<OrderHeader> GetOrderHeaderAsync(OrderHeader entity)
  {
   // Create string builder for query
   var query = new StringBuilder();
   
   // Create sql statement
   query.Append(" select ");
   query.Append("  [OrderHeaderID], ");
   query.Append("  [OrderStatusID], ");
   query.Append("  [CustomerID], ");
   query.Append("  [EmployeeID], ");
   query.Append("  [ShipperID], ");
   query.Append("  [OrderDate], ");
   query.Append("  [Total], ");
   query.Append("  [CurrencyID], ");
   query.Append("  [PaymentMethodID], ");
   query.Append("  [DetailsCount], ");
   query.Append("  [ReferenceOrderID], ");
   query.Append("  [Comments], ");
   query.Append("  [CreationUser], ");
   query.Append("  [CreationDateTime], ");
   query.Append("  [LastUpdateUser], ");
   query.Append("  [LastUpdateDateTime], ");
   query.Append("  [Timestamp] ");
   query.Append(" from ");
   query.Append("  [Sales].[OrderHeader] ");
   query.Append(" where ");
   query.Append("  [OrderHeaderID] = @orderHeaderID ");
   
   // Create parameters collection
   var parameters = new DynamicParameters();
   
   // Add parameters to collection
   parameters.Add("orderHeaderID", entity.OrderHeaderID);
   
   // Retrieve result from database and convert to entity class
   return await Connection.QueryFirstOrDefaultAsync<OrderHeader>
                 (query.ToString(), parameters);
  }

  public async Task<Int32> AddOrderHeaderAsync(OrderHeader entity)
  {
   // Create string builder for query
   var query = new StringBuilder();
   
   // Create sql statement
   query.Append(" insert into ");
   query.Append("  [Sales].[OrderHeader] ");
   query.Append("  ( ");
   query.Append("   [OrderStatusID], ");
   query.Append("   [CustomerID], ");
   query.Append("   [EmployeeID], ");
   query.Append("   [ShipperID], ");
   query.Append("   [OrderDate], ");
   query.Append("   [Total], ");
   query.Append("   [CurrencyID], ");
   query.Append("   [PaymentMethodID], ");
   query.Append("   [DetailsCount], ");
   query.Append("   [ReferenceOrderID], ");
   query.Append("   [Comments], ");
   query.Append("   [CreationUser], ");
   query.Append("   [CreationDateTime] ");
   query.Append("  ) ");
   query.Append(" values ");
   query.Append(" ( ");
   query.Append("  @orderStatusID, ");
   query.Append("  @customerID, ");
   query.Append("  @employeeID, ");
   query.Append("  @shipperID, ");
   query.Append("  @orderDate, ");
   query.Append("  @total, ");
   query.Append("  @currencyID, ");
   query.Append("  @paymentMethodID, ");
   query.Append("  @detailsCount, ");
   query.Append("  @referenceOrderID, ");
   query.Append("  @comments, ");
   query.Append("  @creationUser, ");
   query.Append("  @creationDateTime ");
   query.Append(" ) ");
   query.Append("  select @orderHeaderID = @@identity ");
   
   // Create parameters collection
   var parameters = new DynamicParameters();
   
   // Add parameters to collection
   parameters.Add("orderStatusID", entity.OrderStatusID);
   parameters.Add("customerID", entity.CustomerID);
   parameters.Add("employeeID", entity.EmployeeID);
   parameters.Add("shipperID", entity.ShipperID);
   parameters.Add("orderDate", entity.OrderDate);
   parameters.Add("total", entity.Total);
   parameters.Add("currencyID", entity.CurrencyID);
   parameters.Add("paymentMethodID", entity.PaymentMethodID);
   parameters.Add("detailsCount", entity.DetailsCount);
   parameters.Add("referenceOrderID", entity.ReferenceOrderID);
   parameters.Add("comments", entity.Comments);
   parameters.Add("creationUser", entity.CreationUser);
   parameters.Add("creationDateTime", entity.CreationDateTime);
   parameters.Add("orderHeaderID", 
                   dbType: DbType.Int64, direction: ParameterDirection.Output);
   
   // Execute query in database
   var affectedRows = await Connection.ExecuteAsync
                      (new CommandDefinition(query.ToString(), parameters));
   
   // Retrieve value for output parameters
   entity.OrderHeaderID = parameters.Get<Int64?>("orderHeaderID");
   
   return affectedRows;
  }

  public async Task<Int32> UpdateOrderHeaderAsync(OrderHeader entity)
  {
   // Create string builder for query
   var query = new StringBuilder();
   
   // Create sql statement
   query.Append(" update ");
   query.Append("  [Sales].[OrderHeader] ");
   query.Append(" set ");
   query.Append("  [OrderStatusID] = @orderStatusID, ");
   query.Append("  [CustomerID] = @customerID, ");
   query.Append("  [EmployeeID] = @employeeID, ");
   query.Append("  [ShipperID] = @shipperID, ");
   query.Append("  [OrderDate] = @orderDate, ");
   query.Append("  [Total] = @total, ");
   query.Append("  [CurrencyID] = @currencyID, ");
   query.Append("  [PaymentMethodID] = @paymentMethodID, ");
   query.Append("  [DetailsCount] = @detailsCount, ");
   query.Append("  [ReferenceOrderID] = @referenceOrderID, ");
   query.Append("  [Comments] = @comments, ");
   query.Append("  [LastUpdateUser] = @lastUpdateUser, ");
   query.Append("  [LastUpdateDateTime] = @lastUpdateDateTime ");
   query.Append(" where ");
   query.Append("  [OrderHeaderID] = @orderHeaderID ");
   
   // Create parameters collection
   var parameters = new DynamicParameters();
   
   // Add parameters to collection
   parameters.Add("orderStatusID", entity.OrderStatusID);
   parameters.Add("customerID", entity.CustomerID);
   parameters.Add("employeeID", entity.EmployeeID);
   parameters.Add("shipperID", entity.ShipperID);
   parameters.Add("orderDate", entity.OrderDate);
   parameters.Add("total", entity.Total);
   parameters.Add("currencyID", entity.CurrencyID);
   parameters.Add("paymentMethodID", entity.PaymentMethodID);
   parameters.Add("detailsCount", entity.DetailsCount);
   parameters.Add("referenceOrderID", entity.ReferenceOrderID);
   parameters.Add("comments", entity.Comments);
   parameters.Add("lastUpdateUser", entity.LastUpdateUser);
   parameters.Add("lastUpdateDateTime", entity.LastUpdateDateTime);
   parameters.Add("orderHeaderID", entity.OrderHeaderID);
   
   // Execute query in database
   return await Connection.ExecuteAsync
           (new CommandDefinition(query.ToString(), parameters));
  }

  public async Task<Int32> RemoveOrderHeaderAsync(OrderHeader entity)
  {
   // Create string builder for query
   var query = new StringBuilder();
   
   // Create sql statement
   query.Append(" delete from ");
   query.Append("  [Sales].[OrderHeader] ");
   query.Append(" where ");
   query.Append("  [OrderHeaderID] = @orderHeaderID ");
   
   // Create parameters collection
   var parameters = new DynamicParameters();
   
   // Add parameters to collection
   parameters.Add("orderHeaderID", entity.OrderHeaderID);
   
   // Execute query in database
   return await Connection.ExecuteAsync
          (new CommandDefinition(query.ToString(), parameters));
  }

  public async Task<IEnumerable<OrderSummary>> 
               GetOrderSummariesAsync(int pageSize = 10, int pageNumber = 1, 
               Int64? orderHeaderID = null, Int16? orderStatusID = null, 
               Int32? customerID = null, Int32? employeeID = null, Int32? shipperID = null)
  {
   // Create string builder for query
   var query = new StringBuilder();
   
   // Create sql statement
   query.Append(" select ");
   query.Append("  [OrderHeaderID], ");
   query.Append("  [OrderStatusID], ");
   query.Append("  [CustomerID], ");
   query.Append("  [CustomerName], ");
   query.Append("  [EmployeeID], ");
   query.Append("  [EmployeeName], ");
   query.Append("  [ShipperID], ");
   query.Append("  [ShipperName], ");
   query.Append("  [OrderDate], ");
   query.Append("  [Total], ");
   query.Append("  [CurrencyName], ");
   query.Append("  [PaymentMethodName], ");
   query.Append("  [DetailsCount] ");
   query.Append(" from ");
   query.Append("  [Sales].[OrderSummary] ");
   query.Append(" where ");
   query.Append("  (@orderHeaderID is null or [OrderHeaderID] = @orderHeaderID) and ");
   query.Append("  (@orderStatusID is null or [OrderStatusID] = @orderStatusID) and ");
   query.Append("  (@customerID is null or [CustomerID] = @customerID) and ");
   query.Append("  (@employeeID is null or [EmployeeID] = @employeeID) and ");
   query.Append("  (@shipperID is null or [ShipperID] = @shipperID) and ");
   query.Append(" order by ");
   query.Append(" [OrderHeaderID], _
                  [OrderStatusID], [CustomerID], [EmployeeID], [ShipperID] ");
   query.Append(" offset @pageSize * (@pageNumber - 1) rows ");
   query.Append(" fetch next @pageSize rows only ");
   
   // Create parameters collection
   var parameters = new DynamicParameters();
   
   // Add parameters to collection
   parameters.Add("@orderHeaderID", orderHeaderID);
   parameters.Add("@orderStatusID", orderStatusID);
   parameters.Add("@customerID", customerID);
   parameters.Add("@employeeID", employeeID);
   parameters.Add("@shipperID", shipperID);
   
   // Retrieve result from database and convert to typed list
   return await Connection.QueryAsync<OrderSummary>(query.ToString());
  }

  public async Task<IEnumerable<UfnGetCustomerContactResult>> 
                     GetUfnGetCustomerContactsAsync(Int32? customerID)
  {
   // Create string builder for query
   var query = new StringBuilder();
   
   // Create sql statement
   query.Append(" select ");
   query.Append("  [CustomerID], ");
   query.Append("  [AddressLine1], ");
   query.Append("  [AddressLine2], ");
   query.Append("  [City], ");
   query.Append("  [State], ");
   query.Append("  [ZipCode], ");
   query.Append("  [PhoneNumber] ");
   query.Append(" from ");
   query.Append("  [Sales].[ufnGetCustomerContact](@customerID) ");
   
   // Create parameters collection
   var parameters = new DynamicParameters();
   
   // Add parameters to collection
   parameters.Add("@customerID", customerID);
   
   // Retrieve result from database and convert to typed list
   return await Connection.QueryAsync
             <UfnGetCustomerContactResult>(query.ToString(), parameters);
  }
 }
}

关注点

  • CatFactory 没有用于 nuget 的命令行,因为在我看来,允许设置所有设置的值将是一个大麻烦,因为我们有大量的 DapperProjectSettings 设置。我认为目前,创建一个控制台项目来生成代码,然后开发人员将生成的文件移动到现有项目并进行代码重构(如果适用)更简单。
  • CatFactory 现在没有 UI,因为在这个项目开始时,.NET Core 没有标准的 UI,但我们正在为 CatFactory 开发 UI,也许我们会选择 Angular =^^=
  • 现在我们专注于 Entity Framework Core 和 Dapper,但在未来,将会有 Web API、单元测试和其他东西。 :)
  • 我们正在进行持续更新,以更好地帮助用户。

相关链接

代码改进

  • 在按主键获取方法中添加实体的子项(Order -> OrderDetail
  • 处理更新和删除操作的并发
  • 还有其他问题吗?请在评论中告诉我您的反馈 :)

Bug?

如果您遇到 CatFactory 包的任何异常,请使用以下链接:

我将不胜感激您的反馈,以改进 CatFactory

"CatFactory" 概念的来源图片 =^^=

Behind CatFactory's Concept

历史

  • 2017 年 11 月 1 日:初始版本
  • 2017 年 11 月 27 日:添加了琐事和研讨会
  • 2018 年 2 月 8 日:在研讨会中添加了项目选择、数据库类型映射和事件处理程序
  • 2018 年 5 月 2 日:将包版本升级到 beta
  • 2018 年 7 月 11 日:代码生成更新
  • 2018 年 9 月 25 日:更新了代码示例(CRUD)和 Dapper 项目设置
  • 2018 年 10 月 11 日:添加了标量函数和重构文章
  • 2018 年 10 月 25 日:添加了包列表
  • 2018 年 12 月 23 日:数据库更新
  • 2019 年 5 月 12 日:扩展了代码生成引擎
© . All rights reserved.