POCO生成器

目录

介绍

POCO生成器

POCO选项

POCO

导航属性

类名

EF代码优先批注

导出到文件

其他按钮

命令行

类型映射

表和外键

筛选结果

具有许多结果集的存储过程

模式

表格和视图

用户定义表类型(TVP)

存储过程和表值函数

主键和外键

导航属性

一对多关系

一对一关系

多对多关系


2024/08/01
这是我第二次从头开始重写这个项目,这将是最后一次。我在2023年手头有一点空闲时间,所以我再次重新审视了这个项目。新版本将POCO Generator与其UI分离,提供了一个用于其他项目的类库,并增加了对MySQL的支持。您可以在 GitHub上找到POCO Generator v3。出于遗留目的,我将保留这篇文章。

如果您下载了源代码,但编译失败,并且收到有关ILMerge的错误,则需要取消阻止ILMergeWindows对来自其他计算机的.exe文件采取安全措施并阻止它们,因此当您尝试编译代码时,Windows会阻止ILMerge运行,并且整个过程都失败了。转到packages\ILMerge.2.14.1208\tools,右键单击ILMerge.exe并打开属性窗口。点击取消屏蔽。编译。

介绍

有很多方法可以从数据库生成POCO类。困难的方法是手写它们。这可能适用于入门/一两个类方案,但不适用于生产环境。有代码生成工具,如 CodeSmith。该工具可检测对数据库的更改并生成相应的POCO类。Visual Studio支持 T4(文本模板转换工具包)等脚本工具。我选择创建的解决方案是一个以可视化为中心的独立应用程序,即POCO生成器,它遍历SQL Server,并从各种数据对象生成POCOPOCO生成器可以处理五种类型的数据库对象:

  • 视图
  • 存储过程
  • 表值函数
  • 用户定义表类型(TVP)

POCO生成器还可以检测表的主键、外键、唯一键和索引。

POCO生成器

SQL Server树列出了该实例上的所有数据库,每个数据库都列出了其数据对象——表、视图、过程、函数和TVP。树上的复选框用于选取要导出到文件的特定对象。窗口的右上角显示当前生成的POCO,具体取决于树中选择的内容。底部的面板允许您操作POCO的外观并处理将它们导出到文件的处理。当您更改这些选项时,POCO面板将刷新,您将立即看到POCO的外观。

   

POCO选项

POCO

POCO部分管理POCO的结构。

  • 属性/数据成员——通常,POCO是使用属性构造的,但此选项提供了改用数据成员的选项。
  • 虚拟属性——向属性添加虚拟修饰符
  • 覆盖属性——向属性添加覆盖修饰符
  • 部分类——向类添加部分修饰符
  • 可为空的结构类型——所有struct类型都将变为可为null(int?DateTime?),即使它们在数据库中不可为null。
  • 注释&不带null ——针对每个属性的原始SQL Server类型以及它是否可为null的注释。没有null 将删除可为null的注释。
  • Using——在POCO的开头添加using语句。
  • 命名空间——使用指定的命名空间包装POCO。
  • 继承——添加以逗号分隔的继承基类和接口列表。
  • 列默认值——根据SQL Server中的列默认值添加属性初始化。无法正确处理的默认值将被注释。
  • 成员之间的新行——如果POCO变大,尤其是使用EF批注时,此选项将在POCO属性之间添加行。

导航属性

导航属性通过解耦的POCO表示的外键连接它们。

  • 导航属性——如有必要,添加导航属性和构造函数初始化。
  • 虚拟——向导航属性添加虚拟修饰符。
  • 覆盖——向导航属性添加替代修饰符。
  • 显示多对多联接表——在多对多关系中,该join表默认处于隐藏状态。选中此选项后,将强制呈现该join表。
  • 注释——原始SQL Server外键的注释。
  • ListICollectionIEnumerable——导航属性的类型(当它是POCO的集合时)。

类名

默认情况下,POCO类的名称是数据对象的名称,无论它是否是C#有效名称。类名部分操作该名称。

  • 单数——将名称从复数更改为单数。仅适用于表格、视图和TVP。我试图在这里尽我所能,使用英语语法的单一规则,但显然它不是万无一失的。
  • Include DB——添加数据库名称。
  • 数据库分隔符——在数据库名称后添加指定的分隔符。
  • 包括架构——添加架构名称。
  • 忽略dbo架构——如果架构名称为“dbo”,则不添加架构名称。
  • 架构分隔符 ——在架构名称后添加指定的分隔符。
  • 单词分隔符——在类名中的单词之间添加指定的分隔符。单词被定义为下划线之间的文本或骆驼形的文本。

类名EmployeeDepartmentHistory中有3个单词,EmployeeDepartmentHistory。类名Product_Category2个单词,Product&Category

  • 驼峰式大小写,大写,小写——更改类名的大小写。
  • 替换,使用,忽略大小写——对类名执行搜索和替换。
  • 固定名称——忽略前面的所有选项,并将类的名称设置为指定的固定名称。
  • 前缀和后缀——将前缀和后缀文本添加到类名中。

EF代码优先批注

“EF批注部分将各种EF属性添加到POCO类及其属性中。仅适用于表。您可以在此MSDN代码优先数据批注中阅读有关EF批注的详细信息。

  • EF——添加EF主属性。
    • Table属性。[Table("Production.Product")]
    • 主键属性上的Key属性。[Key]
    • Column复合主键属性的属性,其中Order值设置为复合主键中键的顺序。[Column(Order = 1)]
    • string属性上的MaxLength属性。[MaxLength(50)]
    • timestamp属性上的Timestamp属性。[Timestamp]
    • DatabaseGenerated属性在Identity和Computed属性上。[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  • Column——添加具有NameTypeName值的Column属性。[Column(Name = "ProductID", TypeName = "int")]
  • Required——为不可为null的属性添加Required属性。 [Required]
  • ErrorMessage是必需的——就像Required,但也添加了错误消息。 [Required(ErrorMessage = "Product ID is required")]
  • ConcurrencyCheck——在TimestampRowVersion属性上添加ConcurrencyCheck属性。[ConcurrencyCheck]
  • StringLength——在string属性上添加StringLength属性。此属性与数据库无关,这与MaxLength不同。它用作用户输入验证。[StringLength(50)]
  • Display——添加Display属性。[Display(Name = "Product ID")]
  • Description——将Description属性从SQL Server扩展属性表(MS_Description)添加到表和列中。[Description("Description from MS_Description")]
  • ComplexType——根据SQL列名称中的第一个下划线,将属性分组到一个ComplexTypeComplexType_Column [ComplexType]
  • IndexEF6——为属性参与的每个索引添加Index属性。如果Index是唯一的或聚簇的,它还会设置Index相应的属性。EF不会生成聚集索引,但可以通过SSMS手动添加。适用于EF6及更高版本。[Index("IX_ProductName", IsUnique = true)]
  • ForeignKey&InverseProperty——为导航属性添加ForeignKey属性。当两个POCO之间存在多个导航属性时添加InverseProperty属性。[ForeignKey("ProductID")] [InverseProperty("Product")]

导出到文件

将一个或多个POCO导出到一个或多个文件。

  • 文件夹——指定要导出到的文件夹。
  • 追加到文件——如果要将多个POCO导出到单个文件中,则很有用。如果未选中此选项,POCO生成器会将每个POCO导出到不同的文件。
  • 导出按钮——如果SQL Server树上有选中的复选框,则POCO生成器将仅导出这些复选框。否则,它将导出当前选定的POCO。

其他按钮

  • 复制——将当前选定的POCO复制到剪贴板。
  • 命令行——当前选定选项和选中的数据库对象的命令行。
  • 类型映射——SQL Server到.NET类型映射的弹出窗口。

命令行

命令行弹出窗口反映了当前选择的选项和在服务器树中选中的数据库对象。您也可以在编辑器中编辑命令行,但每次打开弹出窗口时都会重新生成命令行。帮助窗口描述命令行开关(短开关和长开关)和执行返回代码。您也可以将其保存到批处理文件中。

类型映射

您可以在此MSDN SQL Server数据类型映射中阅读有关类型映射的详细信息。

表和外键

右键单击一个表,上下文菜单将为您提供几个选项来检查通过外键连接到该表的任何其他表。

第一个选项(FK This -> To)检查表通过外键引用的所有表。外键在表中。第二个选项(FK From -> This)检查通过外键引用该表的所有表。外键位于其他表中。第三种选择是前两种选择的组合。递归选项执行前三个选项的作用,但将继续递归检查连接到连接到表的表的表,依此类推。

筛选结果

您可以通过右键单击组并从上下文菜单中选择筛选来筛选每个组(视图等)中的结果。在筛选器弹出窗口中,选择要包含或排除的名称和架构。

具有许多结果集的存储过程

无法确定存储过程是否返回多个结果集。在检索存储过程的架构的过程中,仅返回第一个结果集。在第一个结果集之后,无法访问任何结果集的架构。

解决方案比其他任何事情都更像是一种黑客。在存储过程中,标记第一个select查询并更改存储过程。然后,转到UI并右键单击存储过程。单击上下文菜单中的刷新。出现新的POCO后,复制或导出它以供进一步使用。继续此过程,直到最后一个结果集。完成后,撤消备注并还原存储过程。

模式

检索SQL Server数据对象架构的过程主要通过DbConnection类中的GetSchema方法完成。SqlConnection继承自的DbConnection类有几种GetSchema方法,它们完全按照其名称所暗示的那样。它们从指定的数据源返回架构信息。你可以传递给GetSchema方法,你正在寻找的对象类型和限制列表通常用于筛选数据库名称,模式名称和对象名称。可以在这些MSDN页上找到对象类型和限制的完整列表:架构集合架构限制

表格和视图

表和视图的架构类型均为Tables。对于表,将string BASE TABLE放在最后一个限制上,这是表类型限制。对于视图,请在表类型限制上加上string VIEW

表:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    DataTable allTables = connection.GetSchema("Tables", 
        new string[] { database_name, null, null, "BASE TABLE" });
    DataTable specificTable = connection.GetSchema("Tables", 
        new string[] { database_name, schema_name, table_name, "BASE TABLE" });
}

和视图:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    DataTable allViews = connection.GetSchema("Tables", 
        new string[] { database_name, null, null, "VIEW" });
    DataTable specificView = connection.GetSchema("Tables", 
        new string[] { database_name, schema_name, view_name, "VIEW" });
}

用户定义表类型(TVP

TVP架构无法通过GetSchema方法检索,或者至少无法可靠检索。获取TVP架构需要在SQL Server端进行一些查询。第一个查询获取数据库上的所有TVP

select 
    tvp_schema = ss.name, 
    tvp_name = stt.name, 
    stt.type_table_object_id 
from sys.table_types stt 
inner join sys.schemas ss on stt.schema_id = ss.schema_id

对于每个TVP,我们都会得到它的列列表。@tvp_id参数是上一个查询中的type_table_object_id列。 

select 
    sc.*, 
    data_type = st.name 
from sys.columns sc 
inner join sys.types st on sc.system_type_id = st.system_type_id and sc.user_type_id = st.user_type_id
where sc.object_id = @tvp_id

存储过程和表值函数

存储过程和函数的架构类型均为Procedures。对于存储过程,将string PROCEDURE放在最后一个限制上,这是例程类型限制。对于函数,将stringFUNCTION放在例程类型限制上。

存储过程:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    DataTable allProcedures = connection.GetSchema("Procedures", 
        new string[] { database_name, null, null, "PROCEDURE" });
    DataTable specificProcedure = connection.GetSchema("Procedures", 
        new string[] { database_name, schema_name, procedure_name, "PROCEDURE" });
}

和函数:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    DataTable allFunctions = connection.GetSchema("Procedures", 
        new string[] { database_name, null, null, "FUNCTION" });
    DataTable specificFunction = connection.GetSchema("Procedures", 
        new string[] { database_name, schema_name, function_name, "FUNCTION" });
}

对于每个例程,我们需要获取其参数。架构类型为ProcedureParameters

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    DataTable routineParameters = connection.GetSchema("ProcedureParameters", 
        new string[] { database_name, routine_schema, routine_name, null });
}

此时,我们可以过滤掉任何不是Table-valued函数的东西,这意味着我们需要删除标量函数。标量函数有一个返回参数,它是函数的结果,这就是我们找到它们的方式。

一旦我们有了例程参数,我们将为每个参数构建一个空SqlParameter参数。空SqlParameter是一个将DBNull.Value设置为其值的参数。对于TVP参数,我们将构建一个SqlDbType.Structured类型的参数,并将一个空的DataTable作为其值。

这是一个关于如何构建SqlParameter的非常简短的代码片段。

SqlParameter sqlParameter = new SqlParameter();

// name
sqlParameter.ParameterName = parameter_name;

// empty value
sqlParameter.Value = DBNull.Value;

// type
switch (data_type)
{
    case "bigint": sqlParameter.SqlDbType = SqlDbType.BigInt; break;
    case "binary": sqlParameter.SqlDbType = SqlDbType.VarBinary; break;
    ....
    case "varchar": sqlParameter.SqlDbType = SqlDbType.VarChar; break;
    case "xml": sqlParameter.SqlDbType = SqlDbType.Xml; break;
}

// size for string type
// character_maximum_length comes from the parameter schem
if (data_type == "binary" || data_type == "char" || 
data_type == "nchar" || data_type == "nvarchar" || 
data_type == "varbinary" || data_type == "varchar")
{
    if (character_maximum_length == -1 || character_maximum_length > 0)
        sqlParameter.Size = character_maximum_length;
}

// direction
if (parameter_mode == "IN")
    sqlParameter.Direction = ParameterDirection.Input;
else if (parameter_mode == "INOUT")
    sqlParameter.Direction = ParameterDirection.InputOutput;
else if (parameter_mode == "OUT")
    sqlParameter.Direction = ParameterDirection.Output;

现在,我们已准备好获取例程的列。当涉及到例程时,我们将使用SqlDataReader.GetSchemaTable()方法获取带有CommandBehavior.SchemaOnly标志的例程模式。 

对于存储过程,我们可以使用CommandType.StoredProcedure

using (SqlConnection connection = new SqlConnection(connectionString))
{
    using (SqlCommand command = new SqlCommand())
    {
        command.Connection = connection;
        command.CommandText = string.Format("[{0}].[{1}]", routine_schema, routine_name);
        command.CommandType = CommandType.StoredProcedure;

        // for each routine parameter, build it and add it to command.Parameters

        using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SchemaOnly))
        {
            DataTable schemaTable = reader.GetSchemaTable();
        }
    }
}

对于Table-valued函数,我们需要构造一个从函数中选择所有列的查询。

using (SqlConnection connection = new SqlConnection(connectionString))
{
    using (SqlCommand command = new SqlCommand())
    {
        command.Connection = connection;
        command.CommandType = CommandType.Text;

        command.CommandText = string.Format("select * from [{0}].[{1}](", routine_schema, routine_name);
        
        // for each routine parameter, build it and add it 
        // to command.Parameters and add its name to command.CommandText
        
        command.CommandText += ")";

        using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SchemaOnly))
        {
            DataTable schemaTable = reader.GetSchemaTable();
        }
    }
}

主键和外键

我使用 SQL 查询来检索有关主键和外键的信息。该GetSchema方法不够详细。

下面给出的是主键的查询。

select 
    Name = kc.name,
    Schema_Name = ss.name,
    Table_Name = object_name(kc.parent_object_id),
    Ordinal = ic.key_ordinal,
    Column_Name = c.name,
    Is_Descending = ic.is_descending_key,
    Is_Identity = c.is_identity,
    Is_Computed = c.is_computed
from sys.key_constraints kc
inner join sys.index_columns ic 
    on kc.parent_object_id = ic.object_id and kc.unique_index_id = ic.index_id and kc.type = 'PK'
inner join sys.columns c on ic.object_id = c.object_id and ic.column_id = c.column_id
inner join sys.schemas ss on kc.schema_id = ss.schema_id
order by Schema_Name, Table_Name, Ordinal

下面给出的是外键查询。

select 
    Name = f.name,
    Foreign_Schema = ssf.name,
    Foreign_Table = object_name(f.parent_object_id),
    Foreign_Column = col_name(fc.parent_object_id, fc.parent_column_id),
    Primary_Schema = ssp.name,
    Primary_Table = object_name (f.referenced_object_id),
    Primary_Column = col_name(fc.referenced_object_id, fc.referenced_column_id),
    Ordinal = fc.constraint_column_id
from sys.foreign_keys f
inner join sys.foreign_key_columns fc on f.object_id = fc.constraint_object_id
inner join sys.schemas ssf on f.schema_id = ssf.schema_id
inner join sys.tables st on f.referenced_object_id = st.object_id
inner join sys.schemas ssp on st.schema_id = ssp.schema_id
order by Foreign_Schema, Foreign_Table, Ordinal

导航属性

导航属性定义POCO之间的关系,是数据库表之间外键的反映。有3种类型的关系:一对多、一对一、多对多。

我将SQL文件Keys.sql附加到源代码作为解决方案项。当在任何数据库中执行时,它将输出数据库的主键、唯一键和外键。对于外键,它还将输出它们的关系类型和其他一些有用的属性。

延伸阅读:实体关系一对多关系一对一关系多对多关系

一对多关系

没有任何特殊约束的单个外键是两个表之间一对多关系的数据库实现。

在此示例中,外键为从Product.ProductModelIDProductModel.ProductModelIDProductModelIDProductModel表的主键。此外键定义ProductProductModel之间的一对多关系。Product POCO具一个指向ProductModel的单一导航属性,而ProductModel POCO具有Product的集合导航属性。

public class Product
{
    public int ProductID { get; set; } // primary key
    public int? ProductModelID { get; set; } // foreign key

    public virtual ProductModel ProductModel { get; set; }
}

public class ProductModel
{
    public ProductModel()
    {
        this.Products = new HashSet<Product>();
    }

    public int ProductModelID { get; set; } // primary key

    public virtual ICollection<Product> Products { get; set; }
}

一对一关系

一对一关系的数据库实现是指一个表的主键也是另一个表的主键的外键。POCO生成器无法识别一对一关系的唯一键/唯一索引数据库实现。一对一关系的SQL Server实现在技术上是101

在此示例中,外键为从Employee.BusinessEntityIDPerson.BusinessEntityIDPerson.BusinessEntityIDPerson的主键,Employee.BusinessEntityID既是Employee的主键,Person的主键的外键。

public class Employee
{
    public int BusinessEntityID { get; set; } // primary key, foreign key

    public virtual Person Person { get; set; }
}

public class Person
{
    public int BusinessEntityID { get; set; } // primary key

    public virtual Employee Employee { get; set; }
}

多对多关系

多对多关系是指两个或多个实体对关系中的所有其他实体具有多个引用。多对多关系的数据库实现是一个join表,或者直观地说是一个中间的表,它是参与该关系的所有表的所有主键的构造。join表中的每个主键也是另一个相应表中相应主键的外键。多对多关系中的表不直接相互引用,而是遍历join表,因此表在中间

如果join表的列数多于其他主键的外键数(例如,创建时间列),则POCO生成器会将此关系视为join表与关系中其他每个表之间的一对多关系。当选中显示多对多连接表选项时,这也将生效。

在此示例中,一个产品可以位于多个仓库中,每个仓库存储许多不同的产品。join表为WarehouseProductsWarehouseProducts的所有列都是主键,每列都是产品主键或仓库主键的外键。

public class Product
{
    public Product()
    {
        this.Warehouses = new HashSet<Warehouse>();
    }

    public int ProductID { get; set; } // primary key

    public virtual ICollection<Warehouse> Warehouses { get; set; }
}

// this poco is not rendered. only for illustration
public class WarehouseProducts
{
    public int ProductID { get; set; } // primary key, foreign key
    public int WarehouseID { get; set; } // primary key, foreign key
}

public class Warehouse
{
    public Warehouse()
    {
        this.Products = new HashSet<Product>();
    }

    public int WarehouseID { get; set; } // primary key

    public virtual ICollection<Product> Products { get; set; }
}

https://www.codeproject.com/Articles/892233/POCO-Generator

Poco库本身不提供真随机数生成的功能,但是可以通过Poco库中的Crypto::RandomBuffer类来生成伪随机数。如果需要生成真随机数,可以使用第三方硬件设备或服务来获取真随机数,并将其作为种子传递给Crypto::RandomBuffer类来生成伪随机数序列。例如,可以使用硬件随机数生成器,如Intel的RDRAND指令或ARM的NEON指令,或者使用在线真随机数生成服务,如RANDOM.ORG。以下是一个使用RANDOM.ORG服务生成真随机数的示例代码: ```c++ #include <Poco/Crypto/RandomBuffer.h> #include <Poco/Net/HTTPSClientSession.h> #include <Poco/URI.h> #include <iostream> int main() { Poco::Net::HTTPSClientSession session("www.random.org"); Poco::URI uri("/integers/?num=1&min=1&max=1000000000&col=1&base=10&format=plain&rnd=new"); session.sendRequest(Poco::Net::HTTPRequest(Poco::Net::HTTPRequest::HTTP_GET, uri.toString())); Poco::Net::HTTPResponse response; std::istream& rs = session.receiveResponse(response); unsigned char buffer[4]; rs.read(reinterpret_cast<char*>(buffer), 4); std::cout << "Random number: " << *reinterpret_cast<int*>(buffer) << std::endl; return 0; } ``` 该示例代码使用HTTPSClientSession类与RANDOM.ORG服务通信,请求生成一个1到1000000000之间的整数,并将其作为4字节的随机数种子传递给Crypto::RandomBuffer类,然后使用Crypto::RandomBuffer类生成一个随机数。这个随机数是真随机数,因为它是由RANDOM.ORG服务生成的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值