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

使用 CatFactory 脚手架 Entity Framework Core 2。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (42投票s)

2016年12月12日

CPOL

10分钟阅读

viewsIcon

158953

使用 CatFactory 脚手架 Entity Framework Core 2。

引言

它是如何工作的?

CatFactory 的核心理念是导入现有的 SQL Server 实例数据库,然后搭建目标技术。

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

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

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

目前支持以下技术:

此包是子包的核心,已通过此命名约定创建其他包:CatFactory.PackageName

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

CatFactory 背后的概念

  • 数据库类型映射
  • 项目选择
  • 用于搭建的事件处理程序
  • 数据库对象模型
  • 导入包

更多信息请阅读:CatFactory 背后的概念

工作坊

Dapper 令我不太喜欢的一点是,所有的查询定义都以字符串或字符串生成器的形式存在……我更喜欢有一个可以生成查询的对象,从而减少代码行数,但我不确定这个概念是否违背了 Dapper 的理念,我真的很想知道这一点,因为搭建高质量的代码是 CatFactory 的基础,所以我不希望添加一个破坏 ORM 主要概念的实现……

不过,我花了一些时间研究如何解决存储库的查询构建问题,还没有找到任何对象可以从对象的定义中构建查询,有一些框架提供 CRUD 功能,但类似 LINQ 的东西我不知道有没有。当然,如果我在这点上有误,请在下面的评论中告诉我。

因此,我将提供一个查询构建草案,请您花时间给我反馈,然后回答两个问题:

  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 是此示例的实体,我在此解决方案中发现了一些问题:

  • 没有模式信息(例如 dboProductionPurchasingSales
  • 没有办法知道一个名为“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",
     Length = 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.PostgreSql
  • 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 旨在解决这些限制,并提供一种简单的 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

像 `ClassConstructorDefinition`、`FinalizerDefinition`、`IndexerDefinition`、`PropertyDefinition` 和 `MethodDefinition` 这样的类型可以有代码块,这些块是 `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 2019 或 VS Code
  • 访问现有的 SQL Server 实例

Using the Code

请按照以下步骤使用 CatFactory 搭建 Entity Framework Core:

步骤 01 - 创建示例数据库

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

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

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

表格

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

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

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

步骤 02 - 创建项目

创建一个 .NET Core 控制台应用程序。在某些情况下,您可以将一个项目添加到现有解决方案中,但需要有一个名称或后缀来指示它是用于搭建的项目,例如:OnLineStore.CatFactory.EntityFrameworkCore

为您的项目添加以下包:

名称 版本 描述
CatFactory.SqlServer 1.0.0-beta-sun-build58 提供 SQL Server 数据库的导入功能
CatFactory.EntityFrameworkCore 1.0.0-beta-sun-build50 提供 Entity Framework Core 的搭建

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

步骤 03 - 添加要搭建的代码

安装项目中的包后,在 `Main` 方法中添加以下代码:

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

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

// Create instance of Entity Framework Core project
var project = EntityFrameworkCoreProject
    .CreateForV2x("OnlineStore.Domain", database, @"C:\Projects\OnlineStore.Domain");

// Apply settings for Entity Framework Core project
project.GlobalSelection(settings =>
{
    settings.ForceOverwrite = true;
    settings.ConcurrencyToken = "Timestamp";
    settings.AuditEntity = new AuditEntity
    {
        CreationUserColumnName = "CreationUser",
        CreationDateTimeColumnName = "CreationDateTime",
        LastUpdateUserColumnName = "LastUpdateUser",
        LastUpdateDateTimeColumnName = "LastUpdateDateTime"
    };

    settings.AddConfigurationForUniquesInFluentAPI = true;
    settings.AddConfigurationForForeignKeysInFluentAPI = true;
    settings.DeclareNavigationProperties = true;
});

project.Selection("Sales.OrderHeader", settings =>
{
    settings.EntitiesWithDataContracts = true;
    settings.AddConfigurationForForeignKeysInFluentAPI = true;
    settings.DeclareNavigationProperties = true;
});

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

// Scaffolding =^^=
project
    .ScaffoldDomain();
项目实例的扩展方法
名称 描述
ScaffoldDomain 使用 **领域驱动设计** 搭建代码

代码审查

我们将审查一个实体的输出代码以理解设计。

OrderHeader 类的代码

using System;
using OnlineStore.Domain.Models;
using OnlineStore.Domain.Models.HumanResources;
using System.Collections.ObjectModel;

namespace OnlineStore.Domain.Models.Sales
{
 public partial class OrderHeader : IAuditEntity
 {
  public OrderHeader()
  {
  }

  public OrderHeader(long? id)
  {
   Id = id;
  }

  public long? Id { 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; }

  public Currency CurrencyFk { get; set; }

  public Customer CustomerFk { get; set; }

  public Employee EmployeeFk { get; set; }

  public OrderStatus OrderStatusFk { get; set; }

  public PaymentMethod PaymentMethodFk { get; set; }

  public Shipper ShipperFk { get; set; }

  public Collection<OrderDetail> OrderDetailList { get; set; }

 }
}

OnlineStoreDbContext 类的代码

using System;
using Microsoft.EntityFrameworkCore;
using OnlineStore.Domain.Models;
using OnlineStore.Domain.Configurations;
using OnlineStore.Domain.Models.HumanResources;
using OnlineStore.Domain.Models.Sales;
using OnlineStore.Domain.Models.Warehouse;
using OnlineStore.Domain.Configurations.HumanResources;
using OnlineStore.Domain.Configurations.Sales;
using OnlineStore.Domain.Configurations.Warehouse;

namespace OnlineStore.Domain
{
 public class OnlineStoreDbContext : DbContext
 {
  public OnlineStoreDbContext(DbContextOptions<OnlineStoreDbContext> options)
   : base(options)
  {
  }

  public DbSet<ChangeLog> ChangeLog { get; set; }

  public DbSet<ChangeLogExclusion> ChangeLogExclusion { get; set; }

  public DbSet<Country> Country { get; set; }

  public DbSet<CountryCurrency> CountryCurrency { get; set; }

  public DbSet<Currency> Currency { get; set; }

  public DbSet<EventLog> EventLog { get; set; }

  public DbSet<Employee> Employee { get; set; }

  public DbSet<EmployeeAddress> EmployeeAddress { get; set; }

  public DbSet<EmployeeEmail> EmployeeEmail { get; set; }

  public DbSet<Customer> Customer { get; set; }

  public DbSet<CustomerAddress> CustomerAddress { get; set; }

  public DbSet<CustomerEmail> CustomerEmail { get; set; }

  public DbSet<OrderDetail> OrderDetail { get; set; }

  public DbSet<OrderHeader> OrderHeader { get; set; }

  public DbSet<OrderStatus> OrderStatus { get; set; }

  public DbSet<PaymentMethod> PaymentMethod { get; set; }

  public DbSet<Shipper> Shipper { get; set; }

  public DbSet<Location> Location { get; set; }

  public DbSet<Product> Product { get; set; }

  public DbSet<ProductCategory> ProductCategory { get; set; }

  public DbSet<ProductInventory> ProductInventory { get; set; }

  public DbSet<ProductUnitPriceHistory> ProductUnitPriceHistory { get; set; }

  public DbSet<EmployeeInfo> EmployeeInfo { get; set; }

  public DbSet<OrderSummary> OrderSummary { get; set; }

  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
   // Apply all configurations for tables
   
   // Schema 'dbo'
   modelBuilder
    .ApplyConfiguration(new ChangeLogConfiguration())
    .ApplyConfiguration(new ChangeLogExclusionConfiguration())
    .ApplyConfiguration(new CountryConfiguration())
    .ApplyConfiguration(new CountryCurrencyConfiguration())
    .ApplyConfiguration(new CurrencyConfiguration())
    .ApplyConfiguration(new EventLogConfiguration())
   ;
   
   // Schema 'HumanResources'
   modelBuilder
    .ApplyConfiguration(new EmployeeConfiguration())
    .ApplyConfiguration(new EmployeeAddressConfiguration())
    .ApplyConfiguration(new EmployeeEmailConfiguration())
   ;
   
   // Schema 'Sales'
   modelBuilder
    .ApplyConfiguration(new CustomerConfiguration())
    .ApplyConfiguration(new CustomerAddressConfiguration())
    .ApplyConfiguration(new CustomerEmailConfiguration())
    .ApplyConfiguration(new OrderDetailConfiguration())
    .ApplyConfiguration(new OrderHeaderConfiguration())
    .ApplyConfiguration(new OrderStatusConfiguration())
    .ApplyConfiguration(new PaymentMethodConfiguration())
    .ApplyConfiguration(new ShipperConfiguration())
   ;
   
   // Schema 'Warehouse'
   modelBuilder
    .ApplyConfiguration(new LocationConfiguration())
    .ApplyConfiguration(new ProductConfiguration())
    .ApplyConfiguration(new ProductCategoryConfiguration())
    .ApplyConfiguration(new ProductInventoryConfiguration())
    .ApplyConfiguration(new ProductUnitPriceHistoryConfiguration())
   ;
   
   // Apply all configurations for views
   
   // Schema 'HumanResources'
   modelBuilder
    .ApplyConfiguration(new EmployeeInfoConfiguration())
   ;
   
   // Schema 'Sales'
   modelBuilder
    .ApplyConfiguration(new OrderSummaryConfiguration())
   ;
   
   base.OnModelCreating(modelBuilder);
  }
 }
}

OrderHeaderConfiguration 类的代码

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OnlineStore.Domain.Models.Sales;

namespace OnlineStore.Domain.Configurations.Sales
{
 internal class OrderHeaderConfiguration : IEntityTypeConfiguration<OrderHeader>
 {
  public void Configure(EntityTypeBuilder<OrderHeader> builder)
  {
   // Set configuration for entity
   builder.ToTable("OrderHeader", "Sales");
   
   // Set key for entity
   builder.HasKey(p => p.Id);
   
   // Set identity for entity (auto increment)
   builder.Property(p => p.Id).UseSqlServerIdentityColumn();
   
   // Set configuration for columns
   builder
    .Property(p => p.Id)
    .HasColumnName("ID")
    .HasColumnType("bigint")
    .IsRequired()
    ;
   
   builder
    .Property(p => p.OrderStatusID)
    .HasColumnType("smallint")
    .IsRequired()
    ;
   
   builder
    .Property(p => p.CustomerID)
    .HasColumnType("int")
    .IsRequired()
    ;
   
   builder
    .Property(p => p.EmployeeID)
    .HasColumnType("int")
    ;
   
   builder
    .Property(p => p.ShipperID)
    .HasColumnType("int")
    ;
   
   builder
    .Property(p => p.OrderDate)
    .HasColumnType("datetime")
    .IsRequired()
    ;
   
   builder
    .Property(p => p.Total)
    .HasColumnType("decimal(12, 4)")
    .IsRequired()
    ;
   
   builder
    .Property(p => p.CurrencyID)
    .HasColumnType("varchar")
    .HasMaxLength(10)
    ;
   
   builder
    .Property(p => p.PaymentMethodID)
    .HasColumnType("uniqueidentifier")
    ;
   
   builder
    .Property(p => p.DetailsCount)
    .HasColumnType("int")
    .IsRequired()
    ;
   
   builder
    .Property(p => p.ReferenceOrderID)
    .HasColumnType("bigint")
    ;
   
   builder
    .Property(p => p.Comments)
    .HasColumnType("varchar(max)")
    ;
   
   builder
    .Property(p => p.CreationUser)
    .HasColumnType("varchar")
    .HasMaxLength(25)
    .IsRequired()
    ;
   
   builder
    .Property(p => p.CreationDateTime)
    .HasColumnType("datetime")
    .IsRequired()
    ;
   
   builder
    .Property(p => p.LastUpdateUser)
    .HasColumnType("varchar")
    .HasMaxLength(25)
    ;
   
   builder
    .Property(p => p.LastUpdateDateTime)
    .HasColumnType("datetime")
    ;
   
   builder
    .Property(p => p.Timestamp)
    .HasColumnType("timestamp(8)")
    ;
   
   // Set concurrency token for entity
   builder
    .Property(p => p.Timestamp)
    .ValueGeneratedOnAddOrUpdate()
    .IsConcurrencyToken();
   
   // Add configuration for foreign keys
   
   builder
    .HasOne(p => p.CurrencyFk)
    .WithMany(b => b.OrderHeaderList)
    .HasForeignKey(p => p.CurrencyID)
    .HasConstraintName("FK_Sales_OrderHeader_Currency");
   
   builder
    .HasOne(p => p.CustomerFk)
    .WithMany(b => b.OrderHeaderList)
    .HasForeignKey(p => p.CustomerID)
    .HasConstraintName("FK_Sales_OrderHeader_Customer");
   
   builder
    .HasOne(p => p.EmployeeFk)
    .WithMany(b => b.OrderHeaderList)
    .HasForeignKey(p => p.EmployeeID)
    .HasConstraintName("FK_Sales_OrderHeader_Employee");
   
   builder
    .HasOne(p => p.OrderStatusFk)
    .WithMany(b => b.OrderHeaderList)
    .HasForeignKey(p => p.OrderStatusID)
    .HasConstraintName("FK_Sales_OrderHeader_OrderStatus");
   
   builder
    .HasOne(p => p.PaymentMethodFk)
    .WithMany(b => b.OrderHeaderList)
    .HasForeignKey(p => p.PaymentMethodID)
    .HasConstraintName("FK_Sales_OrderHeader_PaymentMethod");
   
   builder
    .HasOne(p => p.ShipperFk)
    .WithMany(b => b.OrderHeaderList)
    .HasForeignKey(p => p.ShipperID)
    .HasConstraintName("FK_Sales_OrderHeader_Shipper");
   
  }
 }
}

OnlineStoreDbContextSalesQueryExtensions 类的代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using OnlineStore.Domain.Models;
using OnlineStore.Domain.Models.Sales;
using OnlineStore.Domain.QueryModels;
using OnlineStore.Domain.Models.HumanResources;

namespace OnlineStore.Domain
{
 public static class OnlineStoreDbContextSalesQueryExtensions
 {
  public static IQueryable<Customer> GetCustomers(this OnlineStoreDbContext dbContext)
  {
   // Get query from DbSet
   var query = dbContext.Customer.AsQueryable();
   
   return query;
  }

  public static async Task<Customer> GetCustomerAsync
         (this OnlineStoreDbContext dbContext, Customer entity)
  {
   return await dbContext.Customer.FirstOrDefaultAsync(item => item.Id == entity.Id);
  }

  public static IQueryable<CustomerAddress> GetCustomerAddresses
         (this OnlineStoreDbContext dbContext, int? customerID = null)
  {
   // Get query from DbSet
   var query = dbContext.CustomerAddress.AsQueryable();
   
   // Filter by: 'CustomerID'
   if (customerID.HasValue)
    query = query.Where(item => item.CustomerID == customerID);
   
   return query;
  }

  public static async Task<CustomerAddress> GetCustomerAddressAsync
         (this OnlineStoreDbContext dbContext, CustomerAddress entity)
  {
   return await dbContext.CustomerAddress.FirstOrDefaultAsync(item => item.Id == entity.Id);
  }

  public static IQueryable<CustomerEmail> GetCustomerEmails
         (this OnlineStoreDbContext dbContext, int? customerID = null)
  {
   // Get query from DbSet
   var query = dbContext.CustomerEmail.AsQueryable();
   
   // Filter by: 'CustomerID'
   if (customerID.HasValue)
    query = query.Where(item => item.CustomerID == customerID);
   
   return query;
  }

  public static async Task<CustomerEmail> GetCustomerEmailAsync
         (this OnlineStoreDbContext dbContext, CustomerEmail entity)
  {
   return await dbContext.CustomerEmail.FirstOrDefaultAsync(item => item.Id == entity.Id);
  }

  public static IQueryable<OrderDetail> GetOrderDetails
  (this OnlineStoreDbContext dbContext, long? orderHeaderID = null, int? productID = null)
  {
   // Get query from DbSet
   var query = dbContext.OrderDetail.AsQueryable();
   
   // Filter by: 'OrderHeaderID'
   if (orderHeaderID.HasValue)
    query = query.Where(item => item.OrderHeaderID == orderHeaderID);
   
   // Filter by: 'ProductID'
   if (productID.HasValue)
    query = query.Where(item => item.ProductID == productID);
   
   return query;
  }

  public static async Task<OrderDetail> GetOrderDetailAsync
         (this OnlineStoreDbContext dbContext, OrderDetail entity)
  {
   return await dbContext.OrderDetail.FirstOrDefaultAsync(item => item.Id == entity.Id);
  }

  public static async Task<OrderDetail> GetOrderDetailByOrderHeaderIDAndProductIDAsync
         (this OnlineStoreDbContext dbContext, OrderDetail entity)
  {
   return await dbContext.OrderDetail.FirstOrDefaultAsync
          (item => item.OrderHeaderID == entity.OrderHeaderID && 
           item.ProductID == entity.ProductID);
  }

  public static IQueryable<OrderHeaderQueryModel> GetOrderHeaders
         (this OnlineStoreDbContext dbContext, 
          string currencyID = null, int? customerID = null, 
          int? employeeID = null, short? orderStatusID = null, 
          Guid? paymentMethodID = null, int? shipperID = null)
  {
   // Get query from DbSet
   var query = from orderHeader in dbContext.OrderHeader
    join currencyJoin in dbContext.Currency on orderHeader.CurrencyID 
         equals currencyJoin.Id into currencyTemp
     from currency in currencyTemp.DefaultIfEmpty()
    join customer in dbContext.Customer on orderHeader.CustomerID equals customer.Id
    join employeeJoin in dbContext.Employee on orderHeader.EmployeeID 
         equals employeeJoin.Id into employeeTemp
     from employee in employeeTemp.DefaultIfEmpty()
    join orderStatus in dbContext.OrderStatus 
         on orderHeader.OrderStatusID equals orderStatus.Id
    join paymentMethodJoin in dbContext.PaymentMethod on 
         orderHeader.PaymentMethodID equals paymentMethodJoin.Id into paymentMethodTemp
     from paymentMethod in paymentMethodTemp.DefaultIfEmpty()
    join shipperJoin in dbContext.Shipper on orderHeader.ShipperID equals 
         shipperJoin.Id into shipperTemp
     from shipper in shipperTemp.DefaultIfEmpty()
    select new OrderHeaderQueryModel
    {
     Id = orderHeader.Id,
     OrderStatusID = orderHeader.OrderStatusID,
     CustomerID = orderHeader.CustomerID,
     EmployeeID = orderHeader.EmployeeID,
     ShipperID = orderHeader.ShipperID,
     OrderDate = orderHeader.OrderDate,
     Total = orderHeader.Total,
     CurrencyID = orderHeader.CurrencyID,
     PaymentMethodID = orderHeader.PaymentMethodID,
     DetailsCount = orderHeader.DetailsCount,
     ReferenceOrderID = orderHeader.ReferenceOrderID,
     Comments = orderHeader.Comments,
     CreationUser = orderHeader.CreationUser,
     CreationDateTime = orderHeader.CreationDateTime,
     LastUpdateUser = orderHeader.LastUpdateUser,
     LastUpdateDateTime = orderHeader.LastUpdateDateTime,
     Timestamp = orderHeader.Timestamp,
     CurrencyCurrencyName = currency == null ? string.Empty : currency.CurrencyName,
     CurrencyCurrencySymbol = currency == null ? string.Empty : currency.CurrencySymbol,
     CustomerCompanyName = customer == null ? string.Empty : customer.CompanyName,
     CustomerContactName = customer == null ? string.Empty : customer.ContactName,
     EmployeeFirstName = employee == null ? string.Empty : employee.FirstName,
     EmployeeMiddleName = employee == null ? string.Empty : employee.MiddleName,
     EmployeeLastName = employee == null ? string.Empty : employee.LastName,
     EmployeeBirthDate = employee == null ? default(DateTime?) : employee.BirthDate,
     OrderStatusDescription = orderStatus == null ? string.Empty : orderStatus.Description,
     PaymentMethodPaymentMethodName = paymentMethod == null ? 
                                      string.Empty : paymentMethod.PaymentMethodName,
     PaymentMethodPaymentMethodDescription = paymentMethod == null ? 
                  string.Empty : paymentMethod.PaymentMethodDescription,
     ShipperCompanyName = shipper == null ? string.Empty : shipper.CompanyName,
     ShipperContactName = shipper == null ? string.Empty : shipper.ContactName,
    };
   
   // Filter by: 'CurrencyID'
   if (!string.IsNullOrEmpty(currencyID))
    query = query.Where(item => item.CurrencyID == currencyID);
   
   // Filter by: 'CustomerID'
   if (customerID.HasValue)
    query = query.Where(item => item.CustomerID == customerID);
   
   // Filter by: 'EmployeeID'
   if (employeeID.HasValue)
    query = query.Where(item => item.EmployeeID == employeeID);
   
   // Filter by: 'OrderStatusID'
   if (orderStatusID.HasValue)
    query = query.Where(item => item.OrderStatusID == orderStatusID);
   
   // Filter by: 'PaymentMethodID'
   if (paymentMethodID != null)
    query = query.Where(item => item.PaymentMethodID == paymentMethodID);
   
   // Filter by: 'ShipperID'
   if (shipperID.HasValue)
    query = query.Where(item => item.ShipperID == shipperID);
   
   return query;
  }

  public static async Task<OrderHeader> GetOrderHeaderAsync
         (this OnlineStoreDbContext dbContext, OrderHeader entity)
  {
   return await dbContext.OrderHeader
    .Include(p => p.CurrencyFk)
    .Include(p => p.CustomerFk)
    .Include(p => p.EmployeeFk)
    .Include(p => p.OrderStatusFk)
    .Include(p => p.PaymentMethodFk)
    .Include(p => p.ShipperFk)
    .FirstOrDefaultAsync(item => item.Id == entity.Id);
  }

  public static IQueryable<OrderStatus> GetOrderStatuses(this OnlineStoreDbContext dbContext)
  {
   // Get query from DbSet
   var query = dbContext.OrderStatus.AsQueryable();
   
   return query;
  }

  public static async Task<OrderStatus> GetOrderStatusAsync
         (this OnlineStoreDbContext dbContext, OrderStatus entity)
  {
   return await dbContext.OrderStatus.FirstOrDefaultAsync(item => item.Id == entity.Id);
  }

  public static IQueryable<PaymentMethod> 
         GetPaymentMethods(this OnlineStoreDbContext dbContext)
  {
   // Get query from DbSet
   var query = dbContext.PaymentMethod.AsQueryable();
   
   return query;
  }

  public static async Task<PaymentMethod> GetPaymentMethodAsync
         (this OnlineStoreDbContext dbContext, PaymentMethod entity)
  {
   return await dbContext.PaymentMethod.FirstOrDefaultAsync(item => item.Id == entity.Id);
  }

  public static IQueryable<Shipper> GetShippers(this OnlineStoreDbContext dbContext)
  {
   // Get query from DbSet
   var query = dbContext.Shipper.AsQueryable();
   
   return query;
  }

  public static async Task<Shipper> GetShipperAsync
         (this OnlineStoreDbContext dbContext, Shipper entity)
  {
   return await dbContext.Shipper.FirstOrDefaultAsync(item => item.Id == entity.Id);
  }
 }
}

设置中的 AuditEntity 设置了审计的列,此版本的 CatFactory 支持创建和最后更新的审计。

ConcurrencyToken 设置了并发的列,此值将用于实体映射。

请记住,之前的设置是关于列的,我们需要使用列名而不是属性名。

EntitiesWithDataContracts 表示实体将使用 LINQ 进行 join 搭建,这意味着 CatFactory 引擎会读取所有外键并创建一个数据协定来检索信息。请查看 `OnlineStoreDbContextSalesQueryExtensions` 类中的 GetOrderHeaders 扩展方法,其中有一个带有数据协定的 LINQ 查询,而不是像 GetShippers 方法那样的 lambda 表达式。

请查看所有扩展方法,它们是异步操作。

为 Entity Framework Core 设置 CatFactory

此外,Entity Framework Core 项目实例还有更多设置,我们将查看这些设置:

名称 默认值 描述
ForceOverwrite false 指示代码生成器是否必须覆盖已存在的文件。
SimplifyDataTypes true 指示代码生成器是否必须将 CLR 类型更改为本机类型(例如,Int32 => int)。
UseAutomaticPropertiesForEntities true 指示实体类是否使用自动属性,如果值为 false,实体将包含 private 字段。
EnableDataBindings false 实现 INotifyPropertyChanged 属性,并为类定义添加带有字段的属性。
UseDataAnnotations false 指示 EF Core 中的映射是否使用数据注解。
DeclareDbSetPropertiesInDbContext false 指示 DbContext 类定义是否必须包含 DbSet 的声明。
DeclareNavigationPropertiesAsVirtual false 指示导航属性是否必须声明为 virtual
NavigationPropertyEnumerableNamespace System.Collections。
ObjectModel
设置导航属性类型的命名空间。
NavigationPropertyEnumerableType Collection 设置集合导航属性的类型。
ConcurrencyToken   设置代表并发令牌的列名。
EntityInterfaceName IEntity 设置实体接口的名称。
AuditEntity   设置审计列的名称:创建和最后更新(用户名和日期)。
EntitiesWithDataContracts false 指示实体是否应在存储库中搭建为数据传输对象。
BackingFields   设置用于字段而非属性的列名。
InsertExclusions   设置要为插入操作忽略的列名。
UpdateExclusions   设置要为更新操作忽略的列名。

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

命名空间 默认值 描述
实体层 EntityLayer 获取或设置实体层的命名空间。
数据层 DataLayer 获取或设置数据层的命名空间。
配置 配置 获取或设置数据层中配置的命名空间。
合同 合同 获取或设置数据层中协定的命名空间。
数据协定 DataContracts 获取或设置数据层中数据协定的命名空间。
存储库 存储库 获取或设置数据层中存储库的命名空间。

关注点

  • **CatFactory** 没有用于 nuget 的命令行,因为在我看来,设置 `EntityFrameworkCoreProjectSettings` 的所有设置会带来很多麻烦,因为我们有很多设置。我认为目前最简单的方法是创建一个控制台项目来生成代码,然后开发人员将生成的代码文件移到现有项目中,并在需要时进行重构。
  • **CatFactory** 目前没有 UI,因为在此项目开始时,.NET Core 没有标准的 UI。但我们正在为 CatFactory 开发 UI,也许我们会选择 Angular =^^=。
  • 现在我们专注于 Entity Framework Core 和 Dapper,但在未来,还将有 Web API、单元测试等。 :)
  • **CatFactory** 有一个 Dapper 包,目前还没有相关的文章,但使用方法与 Entity Framework Core 类似;您可以从 nuget 安装 `CatFactory.Dapper` 包。
  • 我们正在进行持续更新,为用户提供更好的帮助。

相关链接

代码改进

  • 添加了对表函数的支持。
  • 添加了对存储过程的支持。
  • 允许覆盖项目的命名约定。
  • 为输出文件添加了作者信息。

Bug?

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

历史

  • 2016年12月12日:初始版本
  • 2016 年 12 月 16 日:添加了数据库脚本。
  • 2017 年 1 月 30 日:添加了异步操作。
  • 2017 年 3 月 12 日:添加了审计实体、并发令牌和带数据协定的实体。
  • 2017 年 6 月 4 日:添加了后备字段、数据绑定和 MEF 用于加载实体映射。
  • 2017 年 9 月 19 日:文章章节变更。
  • 2017 年 10 月 31 日:更新到 alpha 3 版本。
  • 2017 年 11 月 21 日:添加了 Trivia。
  • 2018 年 1 月 18 日:添加了 Workshop。
  • 2018 年 4 月 30 日:将包更新到 beta 版本。
  • 2018 年 5 月 16 日:添加了对 EF Core 2 的支持。
  • 2020 年 8 月 31 日:添加了领域驱动设计搭建。
© . All rights reserved.