使用 CatFactory 脚手架 Entity Framework Core 2。
使用 CatFactory 脚手架 Entity Framework Core 2。
引言
它是如何工作的?
CatFactory 的核心理念是导入现有的 SQL Server 实例数据库,然后搭建目标技术。
我们也可以用内存数据库替换 SQL Server 实例中的数据库。
导入现有数据库的流程是:
- 创建数据库工厂
- 导入数据库
- 创建项目实例(Entity Framework Core、Dapper 等)
- 构建功能(每个 schema 一个功能)
- 搭建对象,这些方法读取数据库中的所有对象并为代码生成器创建实例。
目前支持以下技术:
此包是子包的核心,已通过此命名约定创建其他包:CatFactory.PackageName
。
CatFactory.SqlServer
CatFactory.PostgreSql
CatFactory.NetCore
CatFactory.EntityFrameworkCore
CatFactory.AspNetCore
CatFactory.Dapper
CatFactory.TypeScript
CatFactory 背后的概念
- 数据库类型映射
- 项目选择
- 用于搭建的事件处理程序
- 数据库对象模型
- 导入包
更多信息请阅读:CatFactory 背后的概念。
工作坊
Dapper 令我不太喜欢的一点是,所有的查询定义都以字符串或字符串生成器的形式存在……我更喜欢有一个可以生成查询的对象,从而减少代码行数,但我不确定这个概念是否违背了 Dapper 的理念,我真的很想知道这一点,因为搭建高质量的代码是 CatFactory 的基础,所以我不希望添加一个破坏 ORM 主要概念的实现……
不过,我花了一些时间研究如何解决存储库的查询构建问题,还没有找到任何对象可以从对象的定义中构建查询,有一些框架提供 CRUD 功能,但类似 LINQ 的东西我不知道有没有。当然,如果我在这点上有误,请在下面的评论中告诉我。
因此,我将提供一个查询构建草案,请您花时间给我反馈,然后回答两个问题:
- 此实现是否违背了 Dapper 的概念?
- 您认为在 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
是此示例的实体,我在此解决方案中发现了一些问题:
- 没有模式信息(例如
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",
Length = 50
},
new Column
{
Name = "Phone",
Type = "varchar",
Length = 25
}
}
};
}
}
这样,我们就可以拥有所有实体的“元数据”,并动态地获取这些定义来构建查询,从而减少存储库中的代码行数。
Table
、Columns
、Identity
和 PrimaryKey
的定义已存在于 **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` 接口允许表示代码块内的代码行,有不同的行类型:
CodeLine
CommentLine
EmptyLine
PreprocessorDirectiveLine
ReturnLine
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:Dbo
、HumanResources
、Warehouse
和 Sales
。
每个 schema 代表商店公司的一个部门,请记住这一点,因为所有代码都遵循这一方面进行设计;目前,此代码仅实现 Production
和 Sales
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 日:添加了领域驱动设计搭建。