DbSharp——.NET6上的DAL生成器工具

目录

消息

概括

如何使用DbSharp?

测试优先开发

单一职责原则

StringBuilder与TextWriter

CSharpSourceCodeGenerator

VisualBasicSourceCodeGenerator

设计存储过程类

从数据库中读取模式

CreateCommand

SetResultSet

SetOutputParameterValue

CreateCommand

SetResultSet

SetOutputParameterValue

枚举支持

CreateCommand

SetResultSet

SetOutputParameterValue

用户定义类型

CreateCommand

来自数据库的多个结果集

异步API

CRUD操作的设计API

设计表,记录类

从数据库中获取架构

记录类特性和实现

表类特性和实现

Indentity、RowGuid、Timestamp列

深入了解DatabaseContext、DatabaseFactory、Database类

结论


从GitHub下载最新的源代码

消息

2021-12-31我将所有源代码更新为.NET6

.NET6上的DbSharp支持DateOnlyTimeOnlyDateTime类型的数据库。

DbSharp删除了对Microsoft.Data.SqlTypes的支持,因为它们在.NET6上不受支持

您可以下载DbSharpApplicationXXXXXXXX.zip 

https://github.com/higty/higlabo/tree/master/Net6/Tools/DbSharp/Compiled

请下载最新版本的.zip文件。这些天我继续修复一些错误。

概括

本文通过创建DAL生成器(不是ORM ...)来轻松调用存储过程,向您介绍OOPTestFirstSRP。我将向您展示实现。作为起点,请知道我使用Visual Studio 2022SQL Server

DbSharp是一个DAL生成器。它生成一个StoredProcedure客户端类,使您能够轻松调用存储过程。DbSharp还创建了使您能够进行CRUD操作的TableRecord类。

我们向GitHubNuget发布了.NET6版本。

您可以使用除DbSharp之外的其他ORM库(NHibernateEntityFrameworkDataObject.net),但如果您喜欢DbSharp,则可以免费使用。

如何使用DbSharp

您可以 从以下链接下载.NET6上的 DbSharpApplication

示例代码:

以下是使用DbSharp的列表:

  • 设置示例数据库
  • 启动DbSharpApplication.exe
  • 选择DatabaseType(SqlServer, MySql)并输入连接字符串
  • 导入StoredProcedureUserDefinedTypeTable菜单栏->编辑->导入XXX
  • 生成C#代码。菜单栏->编辑->生成C#代码
  • 通过Visual Studio创建类库项目,添加生成的文件并编译成DLL
  • 在您的应用程序中使用DLL

在示例项目(DbSharpSample.sln)中,您可以看到解决方案文件包括:

  • HigLaboSample.Data.MySql.CSharp(带有生成的文件的ClassLibrary
  • HigLaboSample.Data.SqlServer.CSharp(带有生成的文件的ClassLibrary
  • HigLaboSampleApp.MultiDatabase(一些Console应用程序......
  • HigLaboSampleApp.MySql(用于MySql
  • HigLaboSampleApp.SqlServer(用于SqlServer

这些项目是通过以下步骤创建的:

首先,我通过Management Studio创建了一个DbSharpSample数据库,并对这个数据库执行脚本DbSharp\HigLaboSample.Data.SqlServer.CSharp\DbSharp_SqlServer.sql

然后启动DbSharpApplication.exe并选择目标数据库到SqlServer

选择菜单->编辑->管理连接

添加数据库的连接字符串。

选择菜单->编辑->读取数据库方案

连接按钮,确保选择所有对象,然后按执行按钮。

TableStoredProcedure,UserDefinedTableType被导入。您可以通过单击Table选项卡看到导入的Table

您可以通过单击StoredProcedure选项卡看到导入的StoredProcedure

您可以通过单击UserDefinedTableType选项卡看到导入的UserDefinedTableType

导入表时,每1Table生成5StoredProcedure用于CRUD操作。

  • SelectAll
  • SelectByPrimaryKey
  • Insert
  • Update
  • Delete

您可以通过单StoredProcedure击选项卡来查看这些StoredProcedure

您可以管理Enum类型到列。设置EnumName=MyEnumAllDataTypeTableEnumColumn, NotNullEnumColumn

您可以确认EnumName是否设置为自动生成StroedProcedure

您可以看Usp_SelectMultiTable返回多个ResultSets到向客户端。

您可以将这些类名称从ResultSetX更改为您自己的名称。 

通过MenuBar -> File -> Save将这些模式保存为文件。您可以通过MenuBar -> File -> Open File加载文件。

现在,您可以通过MenuBar -> Edit -> Generate C# Code从这些模式生成C#源代码。

  • 设置输出C#源代码文件的路径
  • 生成文件的RootNamespace
  • DatabaseKey

我稍后会解释DatabaseKey。按下执行按钮,在输出目录路径中生成C#文件。

创建新的类库项目,并添加这些生成的文件:

像这样定义MyEnum

public enum MyEnum
{
    Default,
    Value1,
    Value2,
}

并添加MyEnum到类库:

添加对HigLabo.CoreHigLabo.Data,HigLabo.DbSharp的引用。如果使用SqlGeometrySqlGeographyHierarchyId,则必须添加Microsoft.SqlServer.Types

并编译它。创建HigLaboSample.Data.SqlServer.CSharp.dll

创建名为HigLaboSampleApp.SqlServer的新控制台应用程序,添加引用HigLabo.CoreHigLabo.DataHigLabo.DbSharpMicrosoft.SqlServer.Types(可选...), HigLaboSample.Data.SqlServer.CSharp

现在你可以像这样执行:

var ss = Environment.GetCommandLineArgs();
String connectionString = ss[1];
DatabaseFactory.Current.SetCreateDatabaseMethod
     ("DbSharpSample", () => new SqlServerDatabase(connectionString));

AllDataTypeTable t = new AllDataTypeTable();
var r = new AllDataTypeTable.Record();
r.PrimaryKeyColumn = 11;
r.IntColumn = 2;
//Set properties...
var x1 = t.Insert(r);

您可以通过DbSharpSample.sln阅读后面的部分以了解DbSharp

测试优先开发

本节通过为生成的C#代码创建测试用例来解释测试优先开发。本节的目标是生成一个示例类,如下所示:

public class Person
{
    private Int32 _Age = 0;

    public String Name { get; set; }
    public Int32 Age
    {
        get { return _Age; }
    }
    public List<Person> Children { get; private set; }

    public Person(String name)
    {
        this.Children = new List<Person>();
        this.Name = name;
    }
    public void ShowName()
    {
        Console.WriteLine(this.Name);
    }
    public void AddAge(Int32 value)
    {
        _Age += value;
    }
}

我像下面的元素一样划分这个类,并为每个元素创建生成器:

  • TypeName
  • Field
  • AccessModifier
  • MethodAccessModifier
  • FieldModifier
  • ConstructorModifier
  • Constructor
  • MethodModifier
  • MethodParameter
  • Method
  • PropertyBody
  • Property
  • ClassModifier
  • Class
  • InterfaceProperty
  • InterfaceMethod
  • Interface
  • Namespace
  • SourceCode

TypeName涵盖诸如Int32,Person,List<String>,Dictionary<String, List<Person>>之类的Type名称。起初,我创建了一个关于TypeName类的测试用例(参见HigLabo.CodeGenerator.Version0项目)。

[TestClass]
public class TypeNameTest
{
    [TestMethod]
    public void TypeNameWithoutGenericTypes()
    {
        var tp = new TypeName("Int32");
        Assert.AreEqual("Int32", tp.Write());
    }
    [TestMethod]
    public void TypeNameWithGenericTypes()
    {
        var tp = new TypeName("Func");
        tp.GenericTypes.Add(new TypeName("String"));
        tp.GenericTypes.Add(new TypeName("Int32"));
        Assert.AreEqual("Func<String, Int32>", tp.Write());
    }
    [TestMethod]
    public void TypeNameWithNestedGenericTypes()
    {
        var tp = new TypeName("Func");
        tp.GenericTypes.Add(new TypeName("String"));
        var tp1 = new TypeName("Action");
        tp1.GenericTypes.Add(new TypeName("String"));
        tp1.GenericTypes.Add(new TypeName("Int32"));
        tp.GenericTypes.Add(tp1);
        Assert.AreEqual("Func<String, Action<String, Int32>>", tp.Write());
    }
}

我创建了TypeName类来管理类型信息并生成源代码以通过测试用例。

public class TypeName
{
    public String Name { get; set; }
    public List<TypeName> GenericTypes { get; private set; }
    public TypeName(String name)
    {
        this.Name = name;
        this.GenericTypes = new List<TypeName>();
    }
    public String Write()
    {
        StringBuilder sb = new StringBuilder();

        sb.Append(this.Name);
        if (this.GenericTypes.Count > 0)
        {
            sb.Append("<");
            for (int i = 0; i < this.GenericTypes.Count; i++)
            {
                var tp = this.GenericTypes[i];
                sb.Append(tp.Write());
                if (i < this.GenericTypes.Count - 1)
                {
                    sb.Append(", ");
                }
            }
            sb.Append(">");
        }
        return sb.ToString();
    }
}

并运行测试。您可以确认所有测试均已通过。我创建了另一个元素,您可以看到如何实现和测试它。

单一职责原则

本节解释单一职责原则,以实现针对多语言支持的高效工作。我也计划实现一个VB版本。但是我在上一节的类设计中发现了一个设计问题。要添加一个VB版本,我将更改TypeName类,如下所示:

public class TypeName
{
    public String Name { get; set; }
    public List<TypeName> GenericTypes { get; private set; }
    public TypeName(String name)
    {
        this.Name = name;
        this.GenericTypes = new List<TypeName>();
    }
    public String WriteCSharp()
    {
        //..method body
    }
    public String WriteVB()
    {
        //..method body
    }
}

如果您计划添加JavaF#或其他语言,请将WriteXXX方法添加到TypeName类中。这会导致一些问题。C#程序员和VB程序员必须针对同一个文件工作。如果每个程序员都针对不同的文件工作以避免版本控制问题,可能会更好。想象一下,一个程序员在美国工作,另一个在日本工作,并且由于某种原因(不同的公司、恶劣的环境等),他们没有通过同一个TFS共享源代码。所以,我重新设计了TypeName类以仅管理信息数据和SourceCodeGenerator类只处理生成源代码文本和格式设置。如果不分,TypeName类会有一个关于格式设置的属性(例如,插入换行符自动属性getter)。

重新设计后,两个类将负责单一功能。我为它创建了一个测试用例(也许为C#VB划分一个测试用例类会更好,但我没有时间去做)。

[TestClass]
public class TypeNameTest
{
    [TestMethod]
    public void TypeNameWithoutGenericTypes()
    {
        var tp = new TypeName("Int32");
        {
            var g = new CSharpSourceCodeGenerator();
            Assert.AreEqual("Int32", g.Write(tp));
        }
        {
            var g = new VisualBasicSourceCodeGenerator();
            Assert.AreEqual("Int32", g.Write(tp));
        }
    }
    [TestMethod]
    public void TypeNameWithGenericTypes()
    {
        var tp = new TypeName("Func");
        tp.GenericTypes.Add(new TypeName("String"));
        tp.GenericTypes.Add(new TypeName("Int32"));
        {
            var g = new CSharpSourceCodeGenerator();
            Assert.AreEqual("Func<String, Int32>", g.Write(tp));
        }
        {
            var g = new VisualBasicSourceCodeGenerator();
            Assert.AreEqual("Func(Of String, Int32)", g.Write(tp));
        }
    }
    [TestMethod]
    public void TypeNameWithNestedGenericTypes()
    {
        var tp = new TypeName("Func");
        tp.GenericTypes.Add(new TypeName("String"));
        var tp1 = new TypeName("Action");
        tp1.GenericTypes.Add(new TypeName("String"));
        tp1.GenericTypes.Add(new TypeName("Int32"));
        tp.GenericTypes.Add(tp1);
        {
            var g = new CSharpSourceCodeGenerator();
            Assert.AreEqual("Func<String, Action<String, Int32>>", g.Write(tp));
        }
        {
            var g = new VisualBasicSourceCodeGenerator();
            Assert.AreEqual("Func(Of String, Action(Of String, Int32))", g.Write(tp));
        }
    }
}

所以,我创建了一个CSharpSourceCodeGenerator类来解决上述问题(参见HigLabo.CodeGenerator.Version1项目)。

public class CSharpSourceCodeGenerator
{
    public CSharpSourceCodeGenerator()
    {
    }
    public String Write(TypeName typeName)
    {
        StringBuilder sb = new StringBuilder();

        sb.Append(typeName.Name);
        if (typeName.GenericTypes.Count > 0)
        {
            sb.Append("<");
            for (int i = 0; i < typeName.GenericTypes.Count; i++)
            {
                var tp = typeName.GenericTypes[i];
                sb.Append(this.Write(tp));
                if (i < typeName.GenericTypes.Count - 1)
                {
                    sb.Append(", ");
                }
            }
            sb.Append(">");
        }
        return sb.ToString();
    }
}

VisualBasicSourceCodeGenerator如下所示:

public class VisualBasicSourceCodeGenerator
{
    public String Write(TypeName typeName)
    {
        StringBuilder sb = new StringBuilder();

        sb.Append(typeName.Name);
        if (typeName.GenericTypes.Count > 0)
        {
            sb.Append("(Of ");
            for (int i = 0; i < typeName.GenericTypes.Count; i++)
            {
                var tp = typeName.GenericTypes[i];
                sb.Append(this.Write(tp));
                if (i < typeName.GenericTypes.Count - 1)
                {
                    sb.Append(", ");
                }
            }
            sb.Append(")");
        }
        return sb.ToString();
    }
}

现在TypeName类如下。之前有点简单。

public class TypeName
{
    public String Name { get; set; }
    public List<TypeName> GenericTypes { get; private set; }
    public TypeName(String name)
    {
        this.Name = name;
        this.GenericTypes = new List<TypeName>();
    }
}

现在文件被分离了,两个程序员都可以处理每个文件而没有版本控制的痛苦(或者至少比以前少痛苦)。并且您可以在不更改元数据类(TypeNameField等)的情况下扩展CodeGenerator类(例如格式设置)。如果需要,您可以从元数据类扩展HTML生成器。或者您可以轻松地划分像HigLabo.ClassSchema.dllHigLabo.CSharpCodeGenerator.dllHigLabo.HtmlGenerator.dll这样的DLL

StringBuilderTextWriter

本节说明这TextWriterStringBuilder性能改进要好。在您开始创建其他元素(FieldAccessModifier等)之前,我会更改CSharpSourceCodeGenerator设计。将来,我将从StoredProcedure模式中生成许多文件。

我还必须考虑性能、内存使用和文件I/OStringBuilder优于String,但是一旦调用ToString方法,StringBulider就会将数据分配到堆内存中。如果使用TextWriter,则可以像这样将文本数据目录输出到文件系统。

using (TextWriter writer = File.CreateText("MyFile.txt"))
{
    writer.WriteLine("my data");
}

由于上述原因,我像这样更改测试用例(StringWriter类继承TextWriter类)。

[TestClass]
public class TypeNameTest
{
    [TestMethod]
    public void TypeNameWithoutGenericTypes()
    {
        var tp = new TypeName("Int32");
        Assert.AreEqual("Int32", SourceCodeGenerator.Write(SourceCodeLanguage.CSharp, tp));
        Assert.AreEqual("Int32", SourceCodeGenerator.Write(SourceCodeLanguage.VB, tp));
    }
    [TestMethod]
    public void TypeNameWithGenericTypes()
    {
        var tp = new TypeName("Func");
        tp.GenericTypes.Add(new TypeName("String"));
        tp.GenericTypes.Add(new TypeName("Int32"));

        Assert.AreEqual("Func<String, Int32>",
                         SourceCodeGenerator.Write(SourceCodeLanguage.CSharp, tp));
        Assert.AreEqual("Func(Of String, Int32)",
                         SourceCodeGenerator.Write(SourceCodeLanguage.VB, tp));
    }
    [TestMethod]
    public void TypeNameWithNestedGenericTypes()
    {
        var tp = new TypeName("Func");
        tp.GenericTypes.Add(new TypeName("String"));
        var tp1 = new TypeName("Action");
        tp1.GenericTypes.Add(new TypeName("String"));
        tp1.GenericTypes.Add(new TypeName("Int32"));
        tp.GenericTypes.Add(tp1);

        Assert.AreEqual("Func<String, Action<String, Int32>>"
            , SourceCodeGenerator.Write(SourceCodeLanguage.CSharp, tp));
        Assert.AreEqual("Func(Of String, Action(Of String, Int32))"
            , SourceCodeGenerator.Write(SourceCodeLanguage.VB, tp));
    }
}

并像这样更改CodeGenerator类。

CSharpSourceCodeGenerator

public class CSharpSourceCodeGenerator : SourceCodeGenerator
{
    public override SourceCodeLanguage Language
    {
        get { return SourceCodeLanguage.CSharp; }
    }

    public CSharpSourceCodeGenerator(TextWriter textWriter)
        : base(textWriter)
    {
    }
    public override void Write(TypeName typeName)
    {
        var writer = this.TextWriter;

        writer.Write(typeName.Name);
        if (typeName.GenericTypes.Count > 0)
        {
            writer.Write("<");
            for (int i = 0; i < typeName.GenericTypes.Count; i++)
            {
                var tp = typeName.GenericTypes[i];
                this.Write(tp);
                if (i < typeName.GenericTypes.Count - 1)
                {
                    writer.Write(", ");
                }
            }
            writer.Write(">");
        }
    }
    //Other method...
}

VisualBasicSourceCodeGenerator

public class VisualBasicSourceCodeGenerator: SourceCodeGenerator
{
    public override SourceCodeLanguage Language
    {
        get { return SourceCodeLanguage.VB; }
    }

    public VisualBasicSourceCodeGenerator(TextWriter textWriter)
        : base(textWriter)
    {
    }
    public override void Write(TypeName typeName)
    {
        var writer = this.TextWriter;

        writer.Write(typeName.Name);
        if (typeName.GenericTypes.Count > 0)
        {
            writer.Write("(Of ");
            for (int i = 0; i < typeName.GenericTypes.Count; i++)
            {
                var tp = typeName.GenericTypes[i];
                this.Write(tp);
                if (i < typeName.GenericTypes.Count - 1)
                {
                    writer.Write(", ");
                }
            }
            writer.Write(")");
        }
    }
    //Other method...
}

SourceCodeGenerator abstract基类具有XXXSourceCodeGenerator类的共同属性和方法。

public abstract class SourceCodeGenerator
{
    public String Indent { get; set; }
    public Int32 CurrentIndentLevel { get; set; }
    public TextWriter TextWriter { get; private set; }
    public abstract SourceCodeLanguage Language { get; }

    protected SourceCodeGenerator(TextWriter textWriter)
    {
        this.Indent = "    ";
        this.CurrentIndentLevel = 0;
        this.TextWriter = textWriter;
    }
    public abstract void Write(TypeName typeName);
    public abstract void Write(CodeBlock codeBlock);

    public abstract void Write(AccessModifier modifier);
    public abstract void Write(MethodAccessModifier modifier);
    public abstract void Write(FieldModifier modifier);
    public abstract void Write(Field field);

    public abstract void Write(ConstructorModifier modifier);
    public abstract void Write(Constructor constructor);

    public abstract void Write(MethodModifier modifier);
    public abstract void Write(MethodParameter parameter);
    public abstract void Write(Method method);

    public abstract void Write(PropertyBody propertyBody);
    public abstract void Write(Property property);

    public abstract void Write(ClassModifier modifier);
    public abstract void Write(Class @class);

    public abstract void Write(InterfaceProperty property);
    public abstract void Write(InterfaceMethod method);
    public abstract void Write(Interface @interface);

    public abstract void Write(Namespace @namespace);
    public abstract void Write(SourceCode sourceCode);

    protected void WriteIndent()
    {
        for (int i = 0; i < this.CurrentIndentLevel; i++)
        {
            this.TextWriter.Write(this.Indent);
        }
    }
    protected void WriteLineAndIndent()
    {
        this.WriteLineAndIndent("");
    }
    protected void WriteLineAndIndent(String text)
    {
        this.TextWriter.WriteLine(text);
        for (int i = 0; i < this.CurrentIndentLevel; i++)
        {
            this.TextWriter.Write(this.Indent);
        }
    }

    public void Flush()
    {
        this.TextWriter.Flush();
    }
}

您可以看到这些类HigLabo.CodeGenerator项目的完整C#实现。您还可以确认HigLabo.CodeGenerator.Sample项目生成的C#文本。VB版未完成。如果您是VB程序员,如果有人创建了VB版本的测试用例和代码生成器,我将不胜感激。

设计存储过程类

在本章中,我将向您展示如何使用C#OOP设计存储过程客户端类。有两种类型的存储过程。一个只对数据库执行命令,另一个从数据库中获取数据。我通过这个脚本创建了一个示例表和存储过程。

Create Table MyTaskTable
(TaskId UniqueIdentifier not null
,Title Nvarchar(100) Not Null
,[Priority] Int Not Null
,[State] Nvarchar(10) Not Null
,CreateTime Datetime Not Null
,ScheduleDate Date
,Detail Nvarchar(max) Not Null
,TimestampColumn Timestamp
)

Go

Alter Table [dbo].[MyTaskTable] Add Constraint [PK_MyTaskTable]
Primary Key Clustered (TaskId)

Go

Create Procedure MyTaskTableInsert
(@TaskId UniqueIdentifier
,@Title Nvarchar(100)
,@Priority Int
,@State Nvarchar(10)
,@CreateTime Datetime
,@ScheduleDate Date
,@Detail Nvarchar(max)
) As

Insert Into MyTaskTable
       (TaskId, Title, [Priority], [State], CreateTime, ScheduleDate, Detail)
Values (@TaskId, @Title, @Priority, @State, @CreateTime, @ScheduleDate, @Detail)

Go

Create Procedure MyTaskTableSelectBy_TaskId
(@TaskId UniqueIdentifier
) As

select * from MyTaskTable with(nolock)
where TaskId = @TaskId

Go

您必须创建一些代码来调用存储过程。我想生成一个调用存储过程的代码。我从调用者位置设计这些类,如下所示:

var db = new HigLabo.Data.SqlServerDatabase("connection string");

//Execute stored procedure
var sp = new MyTaskTableInsert();//Same name to stored procedure on database
sp.TaskId = Guid.NewGuid();      //Strongly typed property corresponding to
                                 //stored procedure's parameter
sp.Title = "Post article to CodeProject";
sp.Priority = 2;
sp.State = "Executing";
sp.CreateTime = DateTime.Now;
sp.ScheduleDate = new DateTime(2014, 3, 25);
sp.Detail = "...Draft...";
//Execute MyTaskTableInsert stored procedure on database and get affected record count.
var result = sp.ExecuteNonQuery(db);
//or call like this
//var result1 = db.Execute(sp);

var sp1 = new MyTaskTableSelectBy_TaskId();
sp1.TaskId = sp.TaskId;
var recordList = sp.GetResultSets();        //Get list of POCO objects that represent
                                            //a record of table on database.
foreach(var record in recordList)
{
    //Do something...
}

使用起来看起来很直观。与DataTableDataReader相比,它是强类型的。由于强类型属性,可以通过智能感知获得很大帮助。

您可以像这样对多数据库(具有相同架构)执行存储过程:

var db1 = new HigLabo.Data.SqlServerDatabase("connection string to DB1");
var db2 = new HigLabo.Data.SqlServerDatabase("connection string to DB2");

//Execute stored procedure
var sp = new MyTaskTableInsert();
sp.TaskId = Guid.NewGuid();
sp.Title = "Post article to CodeProject";
sp.Priority = 2;
sp.State = "Executing";
sp.CreateTime = DateTime.Now;
sp.ScheduleDate = new DateTime(2014, 3, 25);
sp.Detail = "...Draft...";

var db1Result1 = sp.ExecuteNonQuery(db1);
var db2Result1 = sp.ExecuteNonQuery(db2);
//Or call like this
var db1Result2 = db1.Execute(sp);
var db2Result2 = db2.Execute(sp);

接下来,我考虑transaction。我设计的Transaction功能是这样的:

var db = new HigLabo.Data.SqlServerDatabase("connection string");
using (TransactionContext tx = new TransactionContext(db))
{
    tx.BeginTransaction(IsolationLevel.ReadCommitted);
    for (int i = 0; i < 3; i++)
    {
        var sp = new MyTaskTableInsert();
        //...Set property of MyTaskTableInsert object
        var result = sp.ExecuteNonQuery(tx);
    }
    tx.CommitTransaction();
}

有时,您可能会对数据库执行包含多个事务的命令。我将设计多事务功能,如下所示:

var db1 = new HigLabo.Data.SqlServerDatabase("connection string to DB1");
var db2 = new HigLabo.Data.SqlServerDatabase("connection string to DB2");

using (TransactionContext tx1 = new TransactionContext(db1)))
{
    using (TransactionContext tx2 = new TransactionContext(db2)))
    {
        tx1.BeginTransaction(IsolationLevel.ReadCommitted);
        tx2.BeginTransaction(IsolationLevel.ReadCommitted);
        for (int i = 0; i < 3; i++)
        {
            var sp = new MyTaskTable1Insert();
            //...Set property of MyTaskTableInsert object
            var result = sp.ExecuteNonQuery(tx1);
        }
        for (int i = 0; i < 3; i++)
        {
            var sp = new MyTaskTable2Insert();
            //...Set property of MyTaskTableInsert object
            var result = sp.ExecuteNonQuery(tx2);
        }
        tx1.CommitTransaction();
        tx2.CommitTransaction();
    }
}

多数据库,Transaction受上述规范支持。这些是数据库访问库的非常基本的功能。

接下来,我考虑使用一个数据库的情况。我经常遇到这种情况,尤其是通过创建小型Web应用程序。然后,我觉得为每个StoredProcedure.ExecuteNonQuery,GetResultSet方法分配Database对象是多余的。所以,我添加了默认数据库功能。您可以通过调用DatabaseFactory对象的SetCreateDatabaseMethod方法来设置默认数据库。ExecuteNonQuery, GetResultSets方法将执行到您指定的SetCreateDatabaseMethod方法的Database。如果您使用一个数据库,这种设计会使事情变得简单。

//Call once on application start what database you use...
DatabaseFactory.Current.SetCreateDatabaseMethod
    ("DbSharpSample", () => new HigLabo.Data.SqlServerDatabase("connection string to db"));

var sp = new MyTaskTableInsert();
//sp.GetDatabaseKey() returns "DbSharpSample".
//You can specify DatabaseKey when you generate code.
//That makes you set different factory method for each database that has different schema.

sp.TaskId = Guid.NewGuid();
//Set other properties...
var result = sp.ExecuteNonQuery();//Executed to db

您可以为每两个具有不同DatabaseKey

默认数据库和事务混合时,事务具有优先级。

//Call once on application start what database you use...
DatabaseFactory.Current.SetCreateDatabaseMethod
    ("DbSharpSample", () => new HigLabo.Data.SqlServerDatabase("connection string to db1"));

var db2 = new HigLabo.Data.SqlServerDatabase("connection string");
var sp1 = new MyTaskTableInsert();
//...Set property of MyTaskTableInsert object
var result1 = sp1.ExecuteNonQuery();//Executed to db1
using (TransactionContext tx = new TransactionContext(db2))
{
    tx.BeginTransaction(IsolationLevel.ReadCommitted);
    var sp2 = new MyTaskTableInsert();
    //...Set property of MyTaskTableInsert object
    var result2 = sp2.ExecuteNonQuery(tx);
    tx.CommitTransaction();
}

好的,这些功能是我首先要实现的。

我设计了类并实现了上述所有功能。类图如下所示:

每个类都有各自的职责。

  • StoredProcedure类具有存储过程的常用操作。这个类的常用操作是GetStoredProcedureNameExecuteNonQueryCreateCommand,SetOutputParameterValue方法。
  • MyTaskTableInsert类将从数据库中的存储过程模式生成。此类具有与存储过程的参数对应的属性。并且这个类有GetStoredProcedureNameCreateCommand, SetOutputParameterValue的具体实现。
  • DatabaseRecordStoredProcedureResultSet类是MyTaskTableSelectBy_TaskId.ResultSet类的基类。MyTaskTableSelectBy_TaskId.ResultSet类是一个POCO类,表示数据库中的表记录。(稍后,我将从表模式创建MyTaskTable.Record类,该类也从DatabaseRecord类继承。)
  • StoredProcedureWithResultSet类继承自StoredProcedure类。该类增加了一些返回结果集的存储过程的常用操作。这个类的常用操作是GetDataTableGetResultSets,EnumerateResultSets方法。
  • StoredProcedureWithResultSet<T>类继承自StoredProcedureWithResultSet类。这个类的常用操作是CreateResultSetSetResultSet方法。该类还添加了强类型方法以提高类型安全性。
  • MyTaskTableSelectBy_TaskId将从数据库中的存储过程模式生成。此类具有与存储过程的参数对应的属性。这个类有GetStoredProcedureNameCreateCommandSetOutputParameterValueCreateResultSet,SetResultSet具体实现。

如您所见,每个类都有各自的职责。首先,我解释了StoredProcedure类和MyTaskTableInsert类。StoredProcedure类如下:

public abstract class StoredProcedure : INotifyPropertyChanged, ITransaction
{
    public abstract DbCommand CreateCommand();
    protected abstract void SetOutputParameterValue(DbCommand command);

    public Int32 ExecuteNonQuery()
    {
        return this.ExecuteNonQuery(this.GetDatabase());
    }

    public Int32 ExecuteNonQuery(Database database)
    {
        if (database == null) throw new ArgumentNullException("database");
        var affectedRecordCount = -1;
        var previousState = database.ConnectionState;

        try
        {
            var cm = CreateCommand();
            affectedRecordCount = database.ExecuteCommand(cm);
            this.SetOutputParameterValue(cm);
        }
        finally
        {
            if (previousState == ConnectionState.Closed &&
              database.ConnectionState == ConnectionState.Open) { database.Close(); }
            if (database.OnTransaction == false) { database.Dispose(); }
        }
        return affectedRecordCount;
    }
    //...abbreviated other elements
}

StoredProcedure类具有生成的存储过程类的所有常见操作。您可以看到ExecuteNonQuery方法已定义。而且你还可以看到CreateCommandSetOutputParameterValue抽象方法被定义了。当我实现一个生成器时,我唯一必须做的就是创建三个元素。

  • 创建存储过程参数对应的属性
  • 创建CreateCommand方法
  • 创建SetOutputParameterValue方法

MyTaskTableInsert类将如下生成:

public partial class MyTaskTableInsert : StoredProcedure
{
    private Guid? _TaskId;
    private String _Title = "";
    //...abbreviated other field

    public Guid? TaskId
    {
        get
        {
            return _TaskId;
        }
        set
        {
            this.SetPropertyValue
                 (ref _TaskId, value, this.GetPropertyChangedEventHandler());
        }
    }
    //...abbreviated other properties

    public override DbCommand CreateCommand()
    {
        var db = new SqlServerDatabase("");
        var cm = db.CreateCommand();
        cm.CommandType = CommandType.StoredProcedure;
        cm.CommandText = "MyTaskTableInsert";

        DbParameter p = null;

        p = db.CreateParameter("@TaskId", SqlDbType.UniqueIdentifier, 0, 0);
        p.SourceColumn = p.ParameterName;
        p.Direction = ParameterDirection.Input;
        p.Size = 16;
        p.Value = this.TaskId;
        cm.Parameters.Add(p);

        //...abbreviated other parameter

        for (int i = 0; i < cm.Parameters.Count; i++)
        {
            if (cm.Parameters[i].Value == null) cm.Parameters[i].Value = DBNull.Value;
        }
        return cm;
    }
    protected override void SetOutputParameterValue(DbCommand command)
    {
        //Set property value from command object if output parameter exists.
    }
    //...abbreviated other elements
}

GetPropertyChangedEventHandler返回在基类(StoredProcedure类)中定义的PropertyChangedEventHandlerSetPropertyValue是在HigLabo.Core.dllINotifyPropertyChangedExtensions类中定义的扩展方法。SetPropertyValue方法的内部是:

public static void SetPropertyValue<T, TProperty>
    (this T obj, ref TProperty field, TProperty value
    , PropertyChangedEventHandler onPropertyChanged,
    [CallerMemberName]  String propertyName = "")
    where T : INotifyPropertyChanged
{
    if (Object.Equals(field, value) == true) return;
    field = value;
    var eh = onPropertyChanged;
    if (eh != null)
    {
        eh(obj, new PropertyChangedEventArgs(propertyName));
    }
}

简单地说,它验证对象相等,设置fieldvalue,并触发PropertyChanged事件。

这些类设计创建最少的工作,因为所有常见的操作都已经在StoredProcedure类中定义。

接下来,我解释返回resultset和其他的StoredProcedureWithResultSet类。这是列表:

  • MyTaskTableSelectBy_TaskId.ResultSet
  • GetDataTable方法
  • GetResultSets方法
  • EnumerateResultSets方法

MyTaskTableSelectBy_TaskId.ResultSet类是表示数据库表记录的POCO对象。要将值映射到强类型POCO对象的属性,有一个限制,即无论何时执行存储过程,结果集都必须具有相同的架构。

这是一个生成的ResultSet类。

public partial class ResultSet : StoredProcedureResultSet
{
    private Guid? _TaskId;
    private String _Title = "";
    //...abbreviated other field

    public Guid? TaskId
    {
        get
        {
            return _TaskId;
        }
        set
        {
            this.SetPropertyValue
              (ref _TaskId, value, this.GetPropertyChangedEventHandler());
        }
    }
    public String Title
    {
        get
        {
            return _Title;
        }
        set
        {
            this.SetPropertyValue
              (ref _Title, value, this.GetPropertyChangedEventHandler());
        }
    }
    //...abbreviated other property

    //...abbreviated other elements
}

我设计了StoredProcedureWithResultSet,StoredProcedureWithResultSet<T>类并实现了上述列表中的所有常见功能。这里有一StoredProcedureWithResultSet类。你可以看到定义了GetDataTableGetResultSetsEnumerateResultSets方法。

public abstract class StoredProcedureWithResultSet : StoredProcedure
{
    protected StoredProcedureWithResultSet()
    {
    }
    protected abstract StoredProcedureResultSet CreateResultSets(IDataReader reader);
    public List<StoredProcedureResultSet> GetResultSets()
    {
        return EnumerateResultSets().ToList();
    }
    public List<StoredProcedureResultSet> GetResultSets(Database database)
    {
        return EnumerateResultSets(database).ToList();
    }
    public IEnumerable<StoredProcedureResultSet> EnumerateResultSets()
    {
        return EnumerateResultSets(this.GetDatabase());
    }
    public IEnumerable<StoredProcedureResultSet> EnumerateResultSets(Database database)
    {
        if (database == null) throw new ArgumentNullException("database");
        DbDataReader dr = null;
        var previousState = database.ConnectionState;

        try
        {
            var resultsets = new List<StoredProcedureResultSet>();
            var cm = CreateCommand();
            dr = database.ExecuteReader(cm);
            while (dr.Read())
            {
                var rs = CreateResultSets(dr);
                resultsets.Add(rs);
                yield return rs;
            }
            dr.Close();
            this.SetOutputParameterValue(cm);
        }
        finally
        {
            if (dr != null) { dr.Dispose(); }
            if (previousState == ConnectionState.Closed &&
                database.ConnectionState == ConnectionState.Open) { database.Close(); }
            if (database.OnTransaction == false) { database.Dispose(); }
        }
    }
    public DataTable GetDataTable()
    {
        return GetDataTable(this.GetDatabase());
    }
    public DataTable GetDataTable(Database database)
    {
        if (database == null) throw new ArgumentNullException("database");
        try
        {
            var cm = CreateCommand();
            var dt = database.GetDataTable(cm);
            return dt;
        }
        finally
        {
            if (database.OnTransaction == false) { database.Dispose(); }
        }
    }
    //...abbreviated other method
}

StoredProcedureWithResultSet<T>就像下面一样。您可以看到定义了更多强类型的方法。

public abstract class StoredProcedureWithResultSet<T> : StoredProcedureWithResultSet
    where T : StoredProcedureResultSet, new()
{
    protected abstract void SetResultSet(T resultSet, IDataReader reader);
    public abstract T CreateResultSet();
    protected override StoredProcedureResultSet CreateResultSets(IDataReader reader)
    {
        var rs = this.CreateResultSet();
        SetResultSet(rs, reader);
        return rs;
    }
    public new List<T> GetResultSets()
    {
        return EnumerateResultSets().ToList();
    }
    public new List<T> GetResultSets(Database database)
    {
        return EnumerateResultSets(database).ToList();
    }
    public new IEnumerable<T> EnumerateResultSets()
    {
        return base.EnumerateResultSets().Cast<T>();
    }
    public new IEnumerable<T> EnumerateResultSets(Database database)
    {
        return base.EnumerateResultSets(database).Cast<T>();
    }
}

这种设计创建了最少的工作,因为所有常见的操作都已经在这些类中定义了。所以,我只在下面的列表中创建这些元素:

  • 存储过程的字段和属性
  • CreateCommand方法
  • SetOutputParameterValue方法
  • CreateResultSet方法
  • SetResultSet方法

这是其中的一些代码:

 public partial class MyTaskTableSelectBy_TaskId :
      StoredProcedureWithResultSet<MyTaskTableSelectBy_TaskId.ResultSet>
 {
     private Guid? _TaskId;

     public Guid? TaskId
     {
         get
         {
             return _TaskId;
         }
         set
         {
             this.SetPropertyValue
               (ref _TaskId, value, this.GetPropertyChangedEventHandler());
         }
     }

     public override DbCommand CreateCommand()
     {
         var db = new SqlServerDatabase("");
         var cm = db.CreateCommand();
         cm.CommandType = CommandType.StoredProcedure;
         cm.CommandText = this.GetStoredProcedureName();

         DbParameter p = null;

         p = db.CreateParameter("@TaskId", SqlDbType.UniqueIdentifier, 0, 0);
         p.SourceColumn = p.ParameterName;
         p.Direction = ParameterDirection.Input;
         p.Size = 16;
         p.Value = this.TaskId;
         cm.Parameters.Add(p);

         for (int i = 0; i < cm.Parameters.Count; i++)
         {
             if (cm.Parameters[i].Value == null) cm.Parameters[i].Value = DBNull.Value;
         }
         return cm;
     }
     protected override void SetOutputParameterValue(DbCommand command)
     {
     }
     public override MyTaskTableSelectBy_TaskId.ResultSet CreateResultSet()
     {
         return new ResultSet(this);
     }
     protected override void SetResultSet
           (MyTaskTableSelectBy_TaskId.ResultSet resultSet, IDataReader reader)
     {
         var r = resultSet;
         Int32 index = -1;
         try
         {
             index += 1;
             if (reader[index] != DBNull.Value) r.TaskId = reader.GetGuid(index);
             index += 1;
             if (reader[index] != DBNull.Value) r.Title = reader[index] as String;
             index += 1;
             if (reader[index] != DBNull.Value) r.Priority = reader.GetInt32(index);
             index += 1;
             if (reader[index] != DBNull.Value) r.State = reader[index] as String;
             index += 1;
             if (reader[index] != DBNull.Value) r.CreateTime = reader.GetDateTime(index);
             index += 1;
             if (reader[index] != DBNull.Value) r.ScheduleDate = reader.GetDateTime(index);
             index += 1;
             if (reader[index] != DBNull.Value) r.Detail = reader[index] as String;
             index += 1;
             if (reader[index] != DBNull.Value) r.TimestampColumn = reader[index] as Byte[];
         }
         catch (InvalidCastException ex)
         {
             throw new StoredProcedureSchemaMismatchedException(this, index, ex);
         }
     }
     //...abbreviated other elements
}

如您所见,无论存储过程如何CreateResultSet都会生成相同的代码,并且在许多情况下SetOutputParameterValue是空的。它使我很容易实现生成器。在下一节中,我将解释从数据库获取模式数据并从模式生成C#代码的详细信息。

从数据库中读取模式

从上面的设计来看,当我生成一些方法的实现时,我必须得到模式数据。

  • CreateCommand
  • SetOutputParameterValue
  • SetResultSet

我计划在Microsoft SqlServer中支持这些通用类型。

bigint
timestamp
bigint
binary
image
varbinary
bit
char
nchar
ntext
nvarchar
text
varchar
xml
datetime
smalldatetime
date
time
datetime2
decimal
money
smallmoney
float
int
real
uniqueidentifier
smallint
tinyint
datetimeoffset
variant

还支持这些类型特定的Microsoft SqlServer

udt(SqlGeometry)
udt(SqlGeography)
udt(HierarchyId)
UserDefinedTable column

我还想支持类型为UserDefinedType的参数。虽然它不是关于类型,但我计划支持 C# Enum功能。以下是本节列出的所有内容。

  • 泛型列类型(BitIntNVarchar等)
  • Udt(SqlGeometry, SqlGeography, HierarchyId)
  • Enum
  • UserTable

我通过对本地数据库执行DbSharp_SqlServer.sql文件(在HigLaboSample.Data.SqlServer.CSharp项目中)来准备数据库。

Usp_Structure存储过程的CreateCommand方法内部必须如下所示:

public override DbCommand CreateCommand()
{
    var db = new SqlServerDatabase("");
    var cm = db.CreateCommand();
    cm.CommandType = CommandType.StoredProcedure;
    cm.CommandText = this.GetStoredProcedureName();

    DbParameter p = null;

    //General parameter
    p = db.CreateParameter("@BigIntColumn", SqlDbType.BigInt, 19, 0);
    p.SourceColumn = p.ParameterName;
    p.Direction = ParameterDirection.InputOutput;
    p.Size = 8;
    p.Value = this.BigIntColumn;
    cm.Parameters.Add(p);

    //UserDefinedTable column
    p = db.CreateParameter("@StructuredColumn", SqlDbType.Structured, 0, 0);
    p.SourceColumn = p.ParameterName;
    p.Direction = ParameterDirection.Input;
    p.Size = -1;
    p.SetTypeName("MyTableType");
    var dt = this.StructuredColumn.CreateDataTable();
    foreach (var item in this.StructuredColumn.Records)
    {
        dt.Rows.Add(item.GetValues());
    }
    p.Value = dt;
    cm.Parameters.Add(p);

    for (int i = 0; i < cm.Parameters.Count; i++)
    {
        if (cm.Parameters[i].Value == null) cm.Parameters[i].Value = DBNull.Value;
    }
    return cm;
}

SetOutputParameterValue方法内部必须如下所示:

protected override void SetOutputParameterValue(DbCommand command)
{
    var cm = command;
    DbParameter p = null;
    p = cm.Parameters[0] as DbParameter;
    if (p.Value != DBNull.Value && p.Value != null) this.BigIntColumn = (Int64)p.Value;
    //Other parameters...
}

SetResultSet方法内部必须如下所示:

protected override void SetResultSet
    (AllDataTypeTableSelectBy_PrimaryKey.ResultSet resultSet, IDataReader reader)
{
    var r = resultSet;
    Int32 index = -1;
    try
    {
        index += 1; r.PrimaryKeyColumn = reader.GetInt64(index);
        index += 1; r.TimestampColumn = reader[index] as Byte[];
        index += 1;
        if (reader[index] != DBNull.Value) r.BigIntColumn = reader.GetInt64(index);
        //Other parameters...
        index += 1;
        if (reader[index] != DBNull.Value)
            r.SqlVariantColumn = reader[index] as Object;
        index += 1;
        if (reader[index] != DBNull.Value)
            r.GeometryColumn = (Microsoft.SqlServer.Types.SqlGeometry)reader[index];
        index += 1;
        if (reader[index] != DBNull.Value)
            r.GeographyColumn = (Microsoft.SqlServer.Types.SqlGeography)reader[index];
        index += 1;
        if (reader[index] != DBNull.Value)
            r.HierarchyIDColumn = (Microsoft.SqlServer.Types.SqlHierarchyId)reader[index];
        index += 1;
        if (reader[index] != DBNull.Value)
            r.EnumColumn = StoredProcedure.ToEnum<MyEnum>
                           (reader[index] as String) ?? r.EnumColumn;
        index += 1;
        r.NotNullBigIntColumn = reader.GetInt64(index);
        index += 1;
        r.NotNullBinaryColumn = reader[index] as Byte[];
        //Other parameters...
    }
    catch (InvalidCastException ex)
    {
        throw new StoredProcedureSchemaMismatchedException(this, index, ex);
    }
}

要创建这些方法,我必须从模式中获取这些信息:

  • ColumnName
  • Type
  • Length
  • Precision
  • Scale
  • IsOutput范围
  • UserTableTypeName
  • UdtTypeName

因此,我创建了一个T-Sql脚本文件来获取上述架构。

Select T01.name as StoredProcedureName 
,T02.name as ParameterName
,CASE T03.is_table_type 
    When 1 Then 'structured' 
    Else 
        Case T03.is_assembly_type
        When 1 Then 'udt' 
        Else
            Case T03.name 
            When 'sql_variant' Then 'variant'
            Else T03.name 
            End
    End
End as ParameterType
,Case T02.max_length 
    When -1 Then -1 
    Else
    Case T03.name 
        When 'nvarchar' Then T02.max_length / 2
        When 'nchar' Then T02.max_length / 2
        Else T02.max_length 
    End
End as ParameterLength
,T02.precision as ParameterPrecision
,T02.scale as ParameterScale
,T02.is_output as IsOutput
,T02.default_value as DefaultValue
,CASE T03.is_table_type 
    When 1 Then T03.name 
    Else ''
End as UserTableTypeName
,Case T03.is_assembly_type
When 1 Then T03.name
Else '' 
End as UdtTypeName 
From sys.procedures as T01 
Inner Join sys.parameters as T02 
ON T01.object_id = T02.object_id 
Inner Join sys.types as T03 
ON T02.user_type_id = T03.user_type_id
Where T01.name = 'MyStoredProcedureName'
Order By T02.parameter_id

结果如下:

  • 创建获取模式数据的T-Sql脚本(MySqlOraclePostgreSql等)
  • 列出我想要支持的所有类型(MySqlOraclePostgreSql等)

接下来,我必须为上面的每种类型创建这三个代码块:

  • 创建SetOutputParameterValue方法代码块
  • 创建CreateCommand方法代码块
  • 创建SetResultSet方法代码块

例如,我针对NVarchar创建了它。

CreateCommand

p = db.CreateParameter("@NVarCharColumn", SqlDbType.NVarChar, 0, 0);
p.SourceColumn = p.ParameterName;
p.Direction = ParameterDirection.InputOutput;
p.Size = 100;
p.Value = this.NVarCharColumn;
cm.Parameters.Add(p);

SetResultSet

r.NVarCharColumn = reader[index] as String;

SetOutputParameterValue

this.NVarCharColumn = (String)p.Value;

再举一个针对Geometry的例子。

CreateCommand

p = db.CreateParameter("@GeometryColumn", SqlDbType.Udt, 0, 0);//SqlDbType is Udt
p.SourceColumn = p.ParameterName;
p.Direction = ParameterDirection.InputOutput;
p.Size = -1;
p.SetUdtTypeName("geometry");//Set UdtTypeName property.
p.Value = this.GeometryColumn;
cm.Parameters.Add(p);

SetResultSet

r.GeometryColumn = (Microsoft.SqlServer.Types.SqlGeometry)reader[index];

SetOutputParameterValue

this.GeometryColumn = (Microsoft.SqlServer.Types.SqlGeometry)p.Value;

我创建了针对Microsoft SqlServerMySql的所有实现,但没有实现OraclePostgreSql。如果您是这些数据库的专家,请告诉我。

  • 创建获取模式数据的T-Sql脚本
  • 列出所有类型
  • 创建CreateCommand方法代码块
  • 创建SetResultSet方法代码块
  • 创建SetOutputParameterValue方法代码块

如果您为OraclePostgreSql创建上述列表,我将不胜感激,我会将您的代码合并到我的库中。

枚举支持

什么是Enum支持?我设计Enum这样的支持:

  • C#属性定义为Enum,您可以获得强类型的好处
  • 通过调用EnumToString方法将Enum值作为String插入到数据库中
  • Column类型必须是NVarchar,NChar或其他文本类型

这些Enum属性可防止您将无效值插入数据库。我必须在从Object创建EnumStoredProcedure类中实现ToEnum方法。

public static T? ToEnum<T>(Object value)
    where T : struct
{
    if (value == null) return null;
    if (typeof(T).IsEnum == false) throw new ArgumentException("T must be Enum type");
    T result;
    var tp = value.GetType();
    if (tp == typeof(T)) return (T)value;
    if (tp == typeof(String) && Enum.TryParse((String)value, true, out result))
    {
        return result;
    }
    throw new InvalidEnumDataException(typeof(T), value);
}

我为Enum创建了方法代码块。

CreateCommand

p = db.CreateParameter("@EnumColumn", SqlDbType.NVarChar, 0, 0);
p.SourceColumn = p.ParameterName;
p.Direction = ParameterDirection.Input;
p.Size = 20;
p.Value = this.EnumColumn.ToStringFromEnum();
cm.Parameters.Add(p);

SetResultSet

r.EnumColumn = StoredProcedure.ToEnum<MyEnum>(reader[index] as String) ?? r.EnumColumn;

SetOutputParameterValue

this.EnumColumn = StoredProcedure.ToEnum<MyEnum>(p.Value as String) ?? this.EnumColumn;

如果数据库中存在无效值,StoredProcedure.ToEnum<MyEnum> throw InvalidEnumDataException

用户定义类型

在本节中,我将向您展示如何设计UserDefinedType类。您可以在DbSharp_SqlServer.sql文件的顶部(在HigLabo.DbSharp.CodeGenerator.Version1项目中)看到MyTableType

Create Type MyTableType As Table
(BigIntColumn bigint not null
,BinaryColumn binary(100)
,ImageColumn image
,VarBinaryColumn varbinary(100)
,BitColumn bit
,CharColumn char(100)
,NCharColumn nchar(100)
,NTextColumn ntext
,NVarCharColumn nvarchar(100)
,TextColumn text
,VarCharColumn varchar(100)
,XmlColumn xml
,DateTimeColumn datetime
,SmallDateTimeColumn smalldatetime
,DateColumn date
,TimeColumn time
,DateTime2Column datetime2
,DecimalColumn decimal
,MoneyColumn money
,SmallMoneyColumn smallmoney
,FloatColumn float
,IntColumn int
,RealColumn real
,UniqueIdentifierColumn uniqueidentifier
,SmallIntColumn smallint
,TinyIntColumn tinyint
,DateTimeOffsetColumn datetimeoffset(7)
,EnumColumn nvarchar(20)
)

我不支持TimeStampGeometryGeographyHierarchyId。由于错误,我无法支持SqlVariant请参阅错误详细信息

您可以使用自己的UserDefinedType作为存储过程的参数。这是MyTableType的使用。

Create Procedure Usp_Structure
(@BigIntColumn bigint out
,@StructuredColumn as MyTableType readonly
) As

您将DataTable作为UserDefinedType参数值传递给数据库。这意味着您可以将记录列表发送到数据库。我从调用者位置设计如何使用UserDefinedType

var udt = new MyTableType();
var record = new MyTableType.Record();
record.BigIntColumn = 100;
//Set other property...
udt.Records.Add(record);

var sp = new Usp_Structure();
sp.StructuredColumn = udt;
//Set other property...
sp.ExecuteNonQuery();

我设计了以下四个类:

  • UserDefinedTableType
  • UserDefinedTableType<T>
  • MyTableType
  • MyTableType.Record

我设计知道参数模式的UserDefinedTableTypeUserDefinedTableTypeGetDataTable abstract方法。UserDefinedTableType<T>提供保持多记录的功能。MyTableType是从数据库模式生成的,并有GetDataTable.GetDataTable的具体实现创建DataTable完全相同的模式UserDefinedType在数据库上。MyTableType.Record是一个POCO对象,表示UserDefinedType的记录。这是一个关于UserDefinedType的类图。

public abstract class UserDefinedTableType
{
    public abstract DataTable CreateDataTable();
}
public abstract class UserDefinedTableType<T> : UserDefinedTableType
    where T : UserDefinedTableTypeRecord
{
    private List<T> _Records = new List<T>();
    public List<T> Records
    {
        get { return _Records; }
    }

    public DataTable CreateDataTable(IEnumerable<T> records)
    {
        var dt = this.CreateDataTable();
        foreach (var item in records)
        {
            dt.Rows.Add(item.GetValues());
        }
        return dt;
    }
}

并从数据库的UserDefinedType模式中生成一个MyTableType类。

public partial class MyTableType : UserDefinedTableType<MyTableType.Record>
{
    public override DataTable CreateDataTable()
    {
        var dt = new DataTable();
        dt.Columns.Add("BigIntColumn", typeof(Int64));
        //abbreviated other code...
        return dt;
    }

    public partial class Record : UserDefinedTableTypeRecord
    {
        private Int64 _BigIntColumn;
        //abbreviated other field...

        public Int64 BigIntColumn
        {
            get
            {
                return _BigIntColumn;
            }
            set
            {
                this.SetPropertyValue
                    (ref _BigIntColumn, value, this.GetPropertyChangedEventHandler());
            }
        }
        //abbreviated other properties...

        public Record()
        {
        }

        public override Object[] GetValues()
        {
            Object[] oo = new Object[28];
            oo[0] = this.BigIntColumn;
            //abbreviated other...
            return oo;
        }
    }
}

UserDefinedType仅用于存储过程的参数。所以,我必须创建一个CreateCommand,SetOutputParameter方法。

CreateCommand

p = db.CreateParameter("@StructuredColumn", SqlDbType.Structured, 0, 0);
p.SourceColumn = p.ParameterName;
p.Direction = ParameterDirection.Input;
p.Size = -1;
p.SetTypeName("MyTableType");
var dt = this.StructuredColumn.CreateDataTable();
foreach (var item in this.StructuredColumn.Records)
{
    dt.Rows.Add(item.GetValues());
}
p.Value = dt;
cm.Parameters.Add(p);

您不能将UserDefinedType作为输出参数:

Create Procedure Usp_Structure
(@BigIntColumn bigint out
,@StructuredColumn as MyTableType out --Invalid!!!
) As

所以你不需要生成SetOutputParameterValue方法代码。并且您不能将UserDefinedType用作结果集列。所以你不需要生成GetResultSets方法代码。

现在您可以使用强类型UserDefinedType作为存储过程的参数。

来自数据库的多个结果集

您可以从数据库中获取多个结果集。例如,您创建一个如下所示的存储过程:

Create Procedure MultipleResultSets_Procedure
As

select * from Table1
select * from Table1
select * from Table1

您不能将UserDefinedType作为输出参数:

var sp = new MultipleResultSets_Procedure();
var rsl = sp.GetResultSetsList();

foreach (var item in rsl.ResultSet1List)
{
}
foreach (var item in rsl.ResultSet2List)
{
}

您可以通过DbSharpApplication将自己的名称分配给结果集。

异步API

您可以使用ExecuteNonQueryGetResultSetsAsync,GetResultSetsListAsync方法进行异步调用。

var sp = new MyStoredProcedure();
var rs = await sp.GetResultSetsAsync();
foreach (var item in rs)
{
    ///Do something...
}

使用起来非常简单。

CRUD操作的设计API

在本节中,我将向您展示如何设计对数据库表的CRUD操作。我通过这个脚本为数据库创建了一个示例表。

Create Table IdentityTable
(IntColumn int not null IDENTITY(1,1)
,NVarCharColumn nvarchar(100)
)

ALTER TABLE [dbo].[IdentityTable] ADD CONSTRAINT [PK_IdentityTable]
PRIMARY KEY CLUSTERED ([IntColumn])

我从调用者的位置设计TableTable.Record类关于获取如下数据:

var db = new HigLabo.Data.SqlServerDatabase("connection string");
var t = new IdentityTable();
var records = t.SelectAll(db);
//Or you can call like this by extension method of DatabaseExtensions class
//var records = db.SelectAll(t);
foreach(var record in recordList)
{
    //Do something...
}
//Get record that has a value of IntColumn = 1
var record = t.SelectByPrimaryKey(db, 1);

我设计了InsertUpdateDelete如下所示:

var db = new HigLabo.Data.SqlServerDatabase("connection string");
var record = new IdentityTable.Record();

//Insert sample
//IntColumn's value is automatically inserted(Because IDENTITY(1,1)) on database.
//So, you don't have to set value.
record.IntColumn = null;
record.NVarCharColumn = "MyText";
var t = new IdentityTable();
t.Insert(db, record);

//You can get a IntColumn value that is created on database
//after Insert method is executed.
Console.WriteLine(record.IntColumn);

//Update sample
record.NVarCharColumn = "MyNewText";
t.Update(db, record);

//Delete sample
t.Delete(db, record);

您可以缩写数据库的参数。如果缩写,表使用按DatabaseFactory类设置的默认数据库(与StoredProcedure类相同)。您可以通过调用DatabaseFactory对象SetCreateDatabaseMethod来设置默认数据库。

DatabaseFactory.Current.SetCreateDatabaseMethod
 ("DbSharpSample", () => new HigLabo.Data.SqlServerDatabase("connection string to DB1"));

var t = new IdentityTable();

var record = new IdentityTable.Record();
record.IntColumn = null;
record.NVarCharColumn = "MyText";
t.Insert(record);//Inserted to DB1

我设计的事务特性与StoredProcedures相同。

var db = new HigLabo.Data.SqlServerDatabase("connection string");
using (DatabaseContext dc = new DatabaseContext(db))
{
    dc.BeginTransaction(IsolationLevel.ReadCommitted);
    var t = new IdentityTable();
    for (int i = 0; i < 3; i++)
    {
        var record = new IdentityTable.Record();
        //Set properties...
        t.Insert(record);//Inserted on transaction
    }
    dc.CommitTransaction();
}

这就是我想从调用方做的所有事情。

设计表,记录类

我设计了​​6个类,以CRUDoperation.IdentityTableIdentityTable.Record类生成。其他在HigLabo.DbSharp.dll中定义。

  • ITable
  • Table<T>
  • CRUD的存储过程
  • IdentityTable
  • TableRecord
  • IdentityTable.Record

ITable类提供了与TableRecord类一起进行常见CRUD操作的功能。Table<T>类提供具有强类型记录类的CRUD操作的实现。此类具有用于CRUD操作的SelectAllSelectByPrimaryKeyInsertUpdate,Delete方法。为每个CRUD操作Table<T>使用StoredProcedure类。模式编辑器将生成五个存储过程和StoredProcedure类。这五个存储过程是:

  • IdentityTableSelectAll
  • IdentityTableSelectByPrimaryKey
  • IdentityTableInsert
  • IdentityTableUpdate
  • IdentityTableDelete

IdentityTable类将从数据库中的表模式生成。TableRecord类对每个表的记录类都有共同的实现。Record类是表示数据库中表记录的类。它是从表的模式生成的。这是一个简单的POCO类来保存记录数据。

类图在这里:

这种设计也让我做最少的工作,因为所有常见的操作都在这些类中定义。

从数据库中获取架构

我必须获取列的模式数据才能生成C#源代码。这是一个从Microsoft数据库中获取模式数据的脚本。

SELECT T01.TABLE_NAME AS TableName
,T01.COLUMN_NAME AS ColumnName
,CASE T03.COLUMN_NAME 
    When T01.COLUMN_NAME Then convert(bit, 1) 
    Else convert(bit, 0) 
End As IsPrimaryKey
,CASE T06.is_table_type 
    When 1 Then 'structured' 
    Else
        Case T06.is_assembly_type
        When 1 Then 'udt' 
        Else 
            Case T01.DATA_TYPE 
            When 'sql_variant' Then 'variant'
            Else T01.DATA_TYPE End 
        End 
    End As DbType
,T01.CHARACTER_MAXIMUM_LENGTH AS ColumnLength
,T01.NUMERIC_PRECISION AS ColumnPrecision
,IsNull(T01.NUMERIC_SCALE,T01.DATETIME_PRECISION) AS ColumnScale
,Case T01.IS_NULLABLE When 'YES' Then convert(bit, 1) Else convert(bit, 0) End As AllowNull
,convert(bit, COLUMNPROPERTY(OBJECT_ID(QUOTENAME(T01.TABLE_SCHEMA) + '.' + _
QUOTENAME(T01.TABLE_NAME)), T01.COLUMN_NAME, 'IsIdentity')) as IsIdentity
,convert(bit, COLUMNPROPERTY(OBJECT_ID(QUOTENAME(T01.TABLE_SCHEMA) + '.' + _
QUOTENAME(T01.TABLE_NAME)), T01.COLUMN_NAME, 'IsRowGuidCol')) as IsRowGuid
,CASE T06.is_table_type 
    When 1 Then T06.name 
    Else
        Case T06.is_assembly_type
        When 1 Then T06.name
        Else '' 
    End
End as UdtTypeName
,'' as EnumValues
FROM INFORMATION_SCHEMA.COLUMNS AS T01
LEFT JOIN (
    SELECT T02.CONSTRAINT_NAME
    , T02.TABLE_NAME
    , T02.COLUMN_NAME
    FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS T02
    LEFT JOIN sys.key_constraints AS S01
    ON T02.CONSTRAINT_NAME = S01.name
    WHERE S01.type = 'PK'
) AS T03
ON T01.TABLE_NAME = T03.TABLE_NAME
AND T01.COLUMN_NAME = T03.COLUMN_NAME
Inner Join sys.tables as T04 
ON T01.TABLE_NAME = T04.name 
Inner Join sys.columns as T05 
ON T04.object_id = T05.object_id AND T01.COLUMN_NAME = T05.name 
Inner Join sys.types as T06 
ON T05.user_type_id = T06.user_type_id
WHERE T01.TABLE_NAME = '{0}'
ORDER BY T01.ORDINAL_POSITION

结果在IdentityTable这里。

我将在下一节解释如何实现这些类。

记录类特性和实现

有了这些模式信息,我就可以生成IdentityTable.Record类了。该类提供以下功能:

  • 属性对应于表的列
  • OldRecord属性、SetOldRecordProperty方法
  • CompareAllColumn,IsChanged方法
  • SetProperty方法
  • ConstructorExecuted部分方法
  • GetValueGetValuesSetValue,SetValues方法
  • CreateValueArray方法
  • GetTableName,GetColumnCount方法

IdentityTable.Record具有对应于列的属性。

public String NVarCharColumn
{
    get
    {
        return _NVarCharColumn;
    }
    set
    {
        this.SetPropertyValue(ref _NVarCharColumn, value,
                              this.GetPropertyChangedEventHandler());
    }
}

在为上述OldRecord属性设置值之前,属性会保留以前的值。首先,这个属性是null,你必须在访问这个属性之前调用SetOldRecordProperty方法。您可以通过调用SetOldRecordProperty方法设置Record对象属性的相同值。

var r = new IdentityTable.Record();
//r.OldRecord is null
r.NVarCharColumn = "MyValue1";
r.SetOldRecordProperty();
//r.OldRecord is not null
Console.WriteLine((r.NVarCharColumn == r.OldRecord.NVarCharColumn).ToString());//True

稍后,我将解释在Table类的SelectByPrimaryKey,Update,Delete方法内使用的OldRecord属性。

CompareAllColumn方法是从这样的模式生成的:

public override Boolean CompareAllColumn(Record record)
{
    if (record == null) throw new ArgumentNullException("record");
    var r = record;
    return Object.Equals(this.IntColumn, r.IntColumn) &&
    Object.Equals(this.NVarCharColumn, r.NVarCharColumn);
}

如您所见,此方法比较所有属性并返回所有列具有相同值的结果。

IsChanged方法将对象本身与OldRecord对象进行比较。该方法在TableRecord<T>类中定义,简单地调用CompareAllColumn方法。

public Boolean IsChanged()
{
    if (this.OldRecord == null) throw new InvalidOperationException
       ("You must call SetOldRecordProperty method before call IsChanged method.");
    return this.CompareAllColumn(this.OldRecord);
}

您可以通过调用此方法知道某些值是否已更改。

SetProperty方法是从这样的模式生成的:

public void SetProperty(IRecord record)
{
    if (record == null) throw new ArgumentNullException("record");
    var r = record;
    this.IntColumn = r.IntColumn;
    this.NVarCharColumn = r.NVarCharColumn;
}

您可以使用此方法轻松设置属性。请注意,IdentityTableSelectAll.ResultSetIdentityTableSelectByPrimaryKey.ResultSet类实现IdentityTable.IRecord接口。您可以将这些类作为IRecord参数传递给此方法。

您可以看到在IdentityTable.Record类的构造函数中调用了ConstructorExecuted部分方法。

public Record()
{
    ConstructorExecuted();
}
public Record(IRecord record)
{
    this.SetProperty(record);
    ConstructorExecuted();
}
partial void ConstructorExecuted();

您可以定义自己的构造函数来设置此记录的默认值。

GetValue,SetValue生成并且GetValues,SetValues方法在TableRecord<T>类中定义。通过这些方法,您可以使用CSV,DataTable和其他Object[]GetValue方法返回一个值作为对象。

public override Object GetValue(Int32 index)
{
    switch (index)
    {
        case 0: return this.IntColumn;
        case 1: return this.NVarCharColumn;
    }
    throw new ArgumentOutOfRangeException();
}

如果该值可以转换为属性的类型,则SetValue方法将值设置为属性。并返回bool您是否成功为属性设置值或失败。

public override Boolean SetValue(Int32 index, Object value)
{
    switch (index)
    {
        case 0:
            if (value == null)
            {
                return false;
            }
            else
            {
                var newValue = TableRecord.TypeConverter.ToInt32(value);
                if (newValue == null) return false;
                this.IntColumn = newValue.Value;
                return true;
            }
        case 1:
            if (value == null)
            {
                this.NVarCharColumn = null;
                return true;
            }
            else
            {
                var newValue = value as String;
                if (newValue == null) return false;
                this.NVarCharColumn = newValue;
                return true;
            }
    }
    throw new ArgumentOutOfRangeException("index", index, "index must be 0-1");
}

如您所见,IntColumn不可为空,NVarCharColumn可以为空。行为是不同的,这取决于您传递null时属性是否可以为空。如果不能为null的属性,则不设置值并返回falsecase 0)。如果属性可以null,则属性设置为null并返回truecase 1)。

TableRecord<T>GetValues方法被定义,并将所有属性值作为Object[]返回。

public Object[] GetValues()
{
    var count = this.GetColumnCount();
    var oo = new Object[count];
    for (int i = 0; i < count; i++)
    {
        oo[i] = this.GetValue(i);
    }
    return oo;
}

例如,您可以在处理CSV文件时使用此方法。

SetValues方法将所有值设置为属性。

public SetValueResult[] SetValues(params Object[] values)
{
    var count = values.Length;
    var bb = new SetValueResult[count];
    for (int i = 0; i < count; i++)
    {
        if (values[i] == TableRecord.SkipSetValue)
        {
            bb[i] = SetValueResult.Skip;
            continue;
        }
        if (this.SetValue(i, values[i]) == true)
        {
            bb[i] = SetValueResult.Success;
        }
        else
        {
            bb[i] = SetValueResult.Failure;
        }
    }
    return bb;
}

您可以使用TableRecord.SkipSetValue跳过某些属性。此方法返回SetValueResult[]SetValueResult是一个具有三个值SuccessSkipFailureenum

public enum SetValueResult
{
    Success,
    Skip,
    Failure,
}

您可以通过调用该CreateValueArray方法轻松创建值。

public Object[] CreateValueArray()
{
    return this.CreateValueArray(SkipSetValue);
}
public Object[] CreateValueArray(Object defaultValue)
{
    var count = this.GetColumnCount();
    var oo = new Object[count];
    for (int i = 0; i < count; i++)
    {
        oo[i] = defaultValue;
    }
    return oo;
}

您可以使用此方法,如下所示:

var r = new IdentityTable.Record();
var oo = TableRecord.CreateValueArray();
oo[1] = "MyText1";
var results = r.SetValues(oo);
//Do something...

您可以轻松地设置来自DataTableCSV文件等的所有值。

GetTableName方法返回此记录的TableName,并且GetColumnCount返回此表的列数。

表类特性和实现

我生成具有以下功能的表类:

  • SelectAll方法
  • SelectByPrimaryKey方法
  • Insert方法
  • Update方法
  • Delete方法
  • Save方法
  • BulkCopy

SelectAll返回List<IdentityTable.Record>对象。您可以通过DatabaseFactory调用SetCreateDatabaseMethod方法来设置默认数据库。

DatabaseFactory.Current.SetCreateDatabaseMethod
     ("DbSharpSample", () => new SqlServerDatabase("Connection string"));
var t = new IdentityTable();
var records = t.SelectAll();
foreach(var record in records)
{
    //Do something...
}

您可以使用其他数据库,如下所示:

var db = new SqlServerDatabase("Connection string");
var t = new IdentityTable();
var records = t.SelectAll(db);
foreach(var record in records)
{
    //Do something...
}

SelectByPrimaryKey方法返回一个与您传递的主键值相同的IdentityTable.Record。如果您传递的值与任何记录都不匹配,则TableRecordNotFoundException被抛出。

var t = new IdentityTable();
var record = t.SelectByPrimaryKey(-123);//TableRecordNotFoundException will be thrown here.

如果你想得到null而不是TableRecordNotFoundException,你可以调用SelectByPrimaryKeyOrNull方法来实现它。

var t = new IdentityTable();
var record = t.SelectByPrimaryKeyOrNull(1);//TableRecordNotFoundException is not thrown
//record may be null

您可以通过调用Insert方法插入记录。

var t = new IdentityTable();
var record = new IdentityTable.Record();
//record.IntColumn is auto increment value.The value will be set on Database.
record.NVarCharColumn = "MyValue1";
t.Insert(record);

insert之后,您可以获得在数据库上创建的值,如下所示:

//Code to prepare to insert...
t.Insert(record);
Console.WriteLine(record.IntColumn);//Show new value of auto increment column

您可以通过调用Update方法更新记录。

var t = new IdentityTable();
var record = t.SelectByPrimaryKey(1);
record.NVarCharColumn = "MyValue1";
t.Update(record);

请注意,record.OldRecord.IntColumn被用于确定您将更新的记录。

您可以通过调用如下Delete方法删除:

var t = new IdentityTable();
var record = new IdentityTable.Record();
record.SetOldRecordProperty();
record.OldRecord.IntColumn = 1;
t.Delete(record);

作为Update方法,record.OldRecord.IntColumn用于确定要删除的记录。

所有这些方法都有一个重载来传递Database对象。

var db = new SqlServerDatabase("Connection string");
var t = new IdentityTable();
var records = t.SelectAll(db);
var record = t.SelectByPrimaryKey(db);
t.Insert(db, record);
t.Update(db, record);
t.Delete(db, record);

您可以使用Save方法一次执行Insert, Update,Delete

List<ISaveMode> records = new List<ISaveMode>();
var r1 = new IdentityTable.Record();
r1.SaveMode = SaveMode.Insert;
//Set properties...
records.Add(r1);

var r2 = new IdentityTable.Record();
r2.SaveMode = SaveMode.Update;
//Set properties...
records.Add(r2);

var r3 = new IdentityTable.Record();
r3.SaveMode = SaveMode.Delete;
//Set properties...
records.Add(r3);

var t = new IdentityTable();
t.Save(records);

DataAdapterSave方法内部使用。在我的粗略测试中,它比执行InsertUpdate, Delete方法快。

使用SqlServer时可以使用Table类的BuldCopy扩展方法。

var records = new List<IdentityTable.Record>();
var r1 = new IdentityTable.Record();
//Set properties...
records.Add(r1);

var t = new IdentityTable();
t.BulkCopy(records);

您还可以使用这样的SqlServerDatabase类执行批量插入:

var records = new List<IdentityTable.Record>();
var r1 = new IdentityTable.Record();
//Set properties...
records.Add(r1);

var reader = new TableRecordReader(records);
var db = new SqlServerDatabase("Connection string");
db.BulkCopy(IdentityTable.Name, reader);

BulkCopy方法也比执行Insert方法快。

IndentityRowGuidTimestamp

在本节中,我将解释有关IndentityRowGuid,Timestamp列的规范。这些列具有以下列出的功能:

  • 值在数据库上自动生成
  • 我们想知道新分配的值

您不能更新Indentity,Timestamp列,但可以更新RowGuid列。因此,您不必在插入记录时为这些列设置值。

var sp = new IndentityInsert();
//sp.IntColumn = 1;             //You don't have to set a value
sp.NVarCharColumn = "MyValue1";
sp.ExecuteNonQuery();
Console.WriteLine(sp.IntColumn);//You can get newly assigned values.

您也可以按Indentity.Record类进行。

var t = new Indentity();
var r = new Indentity.Record();
r.NVarCharColumn = "MyValue1";
t.Insert(r);
Console.WriteLine(r.IntColumn);//You can get newly assigned values
                               //after Insert method is executed.

深入了解DatabaseContextDatabaseFactoryDatabase

在本节中,我将解释DatabaseContext类的内部。您可以通过以下DatabaseContext方式管理事务:

var db = new HigLabo.Data.SqlServerDatabase("connection string");
DatabaseContext dc = new DatabaseContext(db, "Transaction1");

初始化DatabaseContext对象时,将实例分配给DatabaseContext._Contexts线程静态字段。

您可以看到DatabaseContext._Contexts Dictionary<String, DatabaseContext>是并标记为ThreadStaticAttribute

[ThreadStatic]
private static Dictionary<String, DatabaseContext> _Contexts;

ThreadStatic是一个属性,指示此static字段对于每个线程都是唯一的。

您必须注意的一件事是,如果您的initialize _Contexts像下面这样字段,它将导致NullReferenceException

//Caution!! This is invalid code!!
[ThreadStatic]
private static Dictionary<String, DatabaseContext> _Contexts =
                               new Dictionary<String, DatabaseContext>();

因为首先,您在Thread1上访问_Contexts,然后,您在Thread2上访问_Contexts字段。static字段的初始化只在"Thread1"上执行一次,_Contexts字段仍然在"Thread2"上是null和并且抛出NullReferenceException

所以你必须实现如下所示的ThreadStatic字段。并使用Contexts属性来访问它。

[ThreadStatic]
private static Dictionary<String, DatabaseContext> _Contexts = null;
private static Dictionary<String, DatabaseContext> Contexts
{
    get
    {
        if (_Contexts == null)
        {
            _Contexts = new Dictionary<String, DatabaseContext>();
        }
        return _Contexts;
    }
}

并通过属性访问。

var contexts = DatabaseContext.Contexts;

那是关于ThreadStatic变量的模式。请注意,您不必使用lock语句,因为只有当前线程可以访问此变量。

接下来,我将解释DatabaseContext对象的生命周期。例如,您如下所示初始化DatabaseContext

var db = new HigLabo.Data.SqlServerDatabase("connection string");
DatabaseContext dc = new DatabaseContext(db);

这个实例被使用Database对象和transactionKey分配给_Contexts字段。您可以在DatabaseContext类的构造函数中看到它。

public DatabaseContext(Database database)
{
    this.Initialize(database, "", null);
}
public DatabaseContext(Database database, String transactionKey)
{
    this.Initialize(database, transactionKey, null);
}
public DatabaseContext(Database database, String transactionKey,
                       IsolationLevel isolationLevel)
{
    this.Initialize(database, transactionKey, isolationLevel);
}
private void Initialize(Database database, String transactionKey,
                        IsolationLevel? isolationLevel)
{
    this.TransactionKey = transactionKey;
    this.Database = database;
    DatabaseContext.SetDatabaseContext(this.TransactionKey, this);
    if (isolationLevel.HasValue == true)
    {
        this.BeginTransaction(isolationLevel.Value);
    }
}
private static void SetDatabaseContext(String transactionKey, DatabaseContext database)
{
    var dcs = DatabaseContext.Contexts;
    if (dcs.ContainsKey(transactionKey) == true)
               throw new TransactionKeyAlreadyUsedException();
    dcs[transactionKey] = database;
}

当您调用DatabaseContext类的Dispose方法时,此实例将从_Contexts字段中删除。

public void Dispose()
{
    Database db = this.Database;

    var dcs = DatabaseContext.Contexts;
    if (dcs.ContainsKey(this.TransactionKey) == true)
    {
        dcs[this.TransactionKey] = null;
    }
    db.Dispose();
}

因此,您的实例位于从实例化到调用Dispose方法的括号之间。

//dc is assigned to _Contexts field from this line
using (DatabaseContext dc = new DatabaseContext(db, "Transaction1"))
{
    //Do something...
}   //dc is removed from _Contexts on this line

这是使用transaction特性的示例代码:

var db = new HigLabo.Data.SqlServerDatabase("connection string");
using (DatabaseContext dc = new DatabaseContext(db, "Transaction1"))
{
    dc.BeginTransaction(IsolationLevel.ReadCommitted);
    for (int i = 0; i < 3; i++)
    {
        var sp = new MyTaskTableInsert();
        sp.TransactionKey = "Transaction1";
        //...Set property of MyTaskTableInsert object
        var result = sp.ExecuteNonQuery();
    }
    dc.CommitTransaction();
}

这是ExecuteNonQuery方法内部的代码:

public Int32 ExecuteNonQuery()
{
    return this.ExecuteNonQuery(this.GetDatabase());
}
public Int32 ExecuteNonQuery(Database database)
{
    if (database == null) throw new ArgumentNullException("database");
    var affectedRecordCount = -1;
    var previousState = database.ConnectionState;

    try
    {
        var cm = CreateCommand();
        affectedRecordCount = database.ExecuteCommand(cm);
        this.SetOutputParameterValue(cm);
    }
    finally
    {
        if (previousState == ConnectionState.Closed &&
            database.ConnectionState == ConnectionState.Open) { database.Close(); }
        if (previousState == ConnectionState.Closed &&
            database.OnTransaction == false) { database.Dispose(); }
    }
    return affectedRecordCount;
}

如您所见,StoredProcedure类通过调用IDatabaseContext接口的GetDatabase扩展方法来获取Database对象。

这是一个GetDatabase方法代码:

public static Database GetDatabase(this IDatabaseContext context)
{
    Database db = null;
    var dc = DatabaseContext.GetDatabaseContext(context.TransactionKey);
    if (db == null)
    {
        if (context.TransactionKey == "")
        {
            db = DatabaseFactory.Current.CreateDatabase(context.GetDatabaseKey());
        }
        else
        {
            throw new TransactionKeyNotFoundException();
        }
    }
    else
    {
        return dc.Database;
    }
    return db;
}

IDatabaseContext.GetDatabase方法获取声明为来自DatabaseContext_Contexts static字段的dcDatabaseContext实例。并在dc private属性中使用Database。请注意,您指定了一些值给TransactionKey并且没有发现DatabaseContextTransactionKeyNotFoundException将被抛出。

如果您设置sp.TransactionKeyTransaction2,如下所示,TransactionKeyNotFoundException将被抛出。

var db = new HigLabo.Data.SqlServerDatabase("connection string");
using (DatabaseContext dc = new DatabaseContext(db, "Transaction1"))
{
    dc.BeginTransaction(IsolationLevel.ReadCommitted);
    for (int i = 0; i < 3; i++)
    {
        var sp = new MyTaskTableInsert();
        sp.TransactionKey = "Transaction2";
        //...Set property of MyTaskTableInsert object
        var result = sp.ExecuteNonQuery();//Throw exception!!!
    }
    dc.CommitTransaction();
}

因此,请尝试在设置TransactionKey=""时从DatabaseFactory.Current.CreateDatabase方法中获取Database

public static Database GetDatabase(this IDatabaseContext context)
{
    Database db = null;
    var dc = DatabaseContext.GetDatabaseContext(context.TransactionKey);
    if (db == null)
    {
        if (context.TransactionKey == "")
        {
            db = DatabaseFactory.Current.CreateDatabase(context.GetDatabaseKey());
        }
        else
        {
            throw new TransactionKeyNotFoundException();
        }
    }
    else
    {
        return dc.Database;
    }
    return db;
}

请注意,GetDatabaseKey将返回您在生成源代码时设置的值。

您可以通过调用DatabaseFactory.Current.SetCreateDatabaseMethod设置Database默认值。

DatabaseFactory.Current.SetCreateDatabaseMethod
   ("DbSharpSample", () => new SqlServerDatabase("Connection string"));

如果将Database对象作为方法参数传递,则使用它。

var db = new HigLabo.Data.SqlServerDatabase("connection string");
var sp = new MyTaskTableInsert();
//...Set property of MyTaskTableInsert object
var result = sp.ExecuteNonQuery(db);

在上面的示例中,在sp执行ExecuteNonQuery方法时使用db。这是有关这些规则的优先事项。

  • Database作为方法参数传递的对象
  • DatabaseContext如果TransactionKey匹配
  • DatabaseFactory.Current.CreateDatabase方法创建的Database对象

结论

DbSharp是我的爱好库,所以其他库可能比DbSharp有很多有点。有很多库可以帮助您访问数据源。这是我发现的一个名为ORMMicro-ORMDAL生成器的列表。

您可以将这些库用作备用库。如果您使用业务应用程序,它可能会更好。

DbSharp的主要特点是:

  • SqlServerMySql支持(OraclePostgreSql以后会支持...
  • 事务透明
  • Timestamp, 身份支持
  • UserDefinedTypeRowGuidGeometryGeographyHierarchyId(SqlServer)
  • BulkInsert支持(SqlServer
  • Enum支持
  • Set支持(MySql
  • 没有XML映射文件
  • 没有性能损失
  • 没有LINQ

如果您发现其他缺少的功能,请与我联系。我会和你一起改进DbSharp
现在,您可以从GitHub获取所有源代码。

https://www.codeproject.com/Articles/776811/DbSharp-DAL-Generator-Tool-on-NET6

asp.net 代码生成器 【基本说明】 1、能够生成三层模式操作的所有后台代码,简单的SQL Server 2005数据库操作。 2、生成的代码包括了 MODEL、BLL、DALDBHelper、Config 生成的代码内有详细注释可提供参考。 3、提供数据库增、删、改、查、分页及其事务,并提供多种重载方式。 4、所有数据表必须有主键且主键是第一列,这个主要是为了保证获取记录和分页获取的统一性,其实可以取消这个规则。 5、建议新建App_Code文件夹将生成的C#代码放里面。见此文件夹直接拷贝到项目下既可以使用。 6、不保证所提供软件或程序的完整性和安全性。 7、请在使用前查毒 (这也是您使用其它网络资源所必须注意的) 。 8、《Coder.NET代码生成器》需要.Net FrameWork2.0运行环境,基于SQL Server 2005使用。 9、如无法运行本软件,请下载并安装由微软公司提供的.Net FrameWork2.0系统. 10、如果您在使用过程中遇到程序问题或建议请于我联系我的Email是 mailto:liangaspx@163.com。 11、如需要源码与我联系 李亮 QQ:542529107 或登陆 http://liliang119007.download.csdn.net/下载更新版本。 【生成单表代码】 输入数据库名(Server)登录名(Name)密码(Pwd),连接后选择库名(Database)表名(Tables), 之后单击'生成单表代码'新建App_Code文件夹将生成的C#代码(ASP.NET后台代码)放里面。 【生成三层工厂模式项目】 (1)B/S架构(ASP·NET): 输入数据库名(Server)登录名(Name)密码(Pwd)连接数据库成功后直接点生成整个项目选择路径确定就好了。 (2)C/S架构(Windows应用程序): 输入数据库名(Server)登录名(Name)密码(Pwd)连接数据库成功后直接点生成整个项目选择路径确定, 生成项目后打开该项目解决方案将表示层删掉, 再单击vs的(文件→添加→新建项目→选择Windows应用程序),这样就生成C/S架构的程序了! 程序员:李亮 更新日期:2010-5-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值