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

Yet Another ORM ADO.NET 包装器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.66/5 (21投票s)

2014年9月15日

MIT

18分钟阅读

viewsIcon

78246

downloadIcon

359

ADO.NET ORM

本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。要查看该许可协议的副本,请访问 http://creativecommons.org/licenses/by-sa/4.0/

您也可以在 Gitter 上通过 https://gitter.im/JPVenson/DataAccess 与我联系。

Branch 状态
Master

 

Package(Nuget) 提供商 状态
Data Access Core MsSQL, OleDb, Odbc
Data Access SqLite SqLite

 

引言

这篇文章将简要介绍我多策略 ADO.NET 包装器,它使用 FactoryMethod、反射或两者的组合。它易于使用,但对于简单快速(开发和使用速度快)的数据库访问而言,是一个复杂而强大的解决方案。

需要明确的是,这是为非常简单的任务设计的辅助工具。它不是 EF 的替代品!

背景

嗯,这个项目的背景是,我大多数同事都在使用一个非常老旧且庞大的解决方案,当我们在开始新项目时,它需要大量的维护和修改,即使是像下面这样的简单语句,

SELECT * FROM Foo

我也不得不手动打开连接、运行语句并解析 IDataReader。我认为这完全没有必要,因为:大多数时候,POCO 的设计就像数据库一样,具有名称与列名相同的属性等。所以,我试图自动化这项任务。

我想展示我的解决方案,并希望从中获得一些好的想法。

已测试数据库

我从 MsSQL 支持开始,大部分代码可能“设计”为与 SQL 一起工作。然后,我尝试实现 MySQL,这要复杂得多,我正在努力完成适配器,但这需要一段时间,因为我个人并不怎么使用 MySQL 数据库。今天,我想宣布 SQLite 适配器也已完成 90%(根据我为 MsSQL 编写的完全相同的单元测试进行衡量)。

我将来可能会在我的一个项目中将 SQLite 适配器用于生产环境,但我将开始重写所有单元测试(目前,只能测试 MsSQL 或其他适配器),以便在每次测试中都包含 SQLite。

Using the Code

主要部分是 IDatabaseIDatabaseStrategy 以及用于主反射和加载的 DbAccessLayer

IDatabase 定义了一个维护连接的接口,这意味着要打开连接,在需要时保持打开状态,然后关闭它。在 IDatabase 中,有一个 IDatabaseStrategy,用于连接到特定数据库,如 MySQL、MsSql、OleDB 等。该库在后台支持 MsSQL、Obdc、OleDb,但在项目中包含的程序集中,也有 MySQL 和 SqLite 的实现。

正如我提到的,有多种方式可以从数据库加载或提交数据。

例如:从数据库中进行简单的 Select。我们假设有一个名为 Northwind 的数据库和一个名为 Foo 的表。

  1. 创建一个名称与您的 TableFoo)相同的 Object
  2. 定义名称和类型与列相同的属性
  3. 创建一个新的 DbAccessLayer Object,并附带一个正确的连接字符串
  4. 调用 Select<Foo>();

通过这 4 个步骤,您将执行一个完整的数据库 select,然后结果将通过反射映射到 Object

代码

public class FooTest
{
    public class Foo
    {
        public long Id_Foo { get; set; }
        public string FooName { get; set; }
    }
 
    public FooTest()
    {
        var accessLayer = new DbAccessLayer(DbTypes.MsSql, 
	"Data Source=(localdb)\\Projects;Initial Catalog=Northwind;Integrated Security=True;");
        var @select = accessLayer.Select<Foo>();
    }
}

有大量 SelectSelectNativeSelectWhereRunPrimetivSelect 的重载。几乎所有带有泛型参数的方法都有一个接受 Type 实例的对应方法。

在所有示例中,当需要 DbAccessLayer 的实例时,它将由变量表示。

accessLayer

并且在测试中,使用了 MsSQL 数据库及其语法。

创建和自定义 POCO

这主要是一个对象关系映射器 (Object Relationship Mapper)。这意味着该库始终尝试将查询返回的输出映射到一个具有多个属性的 Object。您可以使用一些属性来定义该对象的特定部分和功能。

如示例所示,如果您遵循一些规则,可以跳过所有额外的配置。要“绕过”这些规则,例如类名必须与表名相同的规则,您可以设置一个属性。

ForModel

[ForModel("Foo")]
public class NotFooButSomeStrangeNameYouDoNotLike
{
    public long Id_Foo { get; set; }
    [ForModel("FooName")]
    public string Metallica4tw { get; set; }
}

ForModel 属性可以用于 Class | Table 和 Property | Column 级别。它向处理器提供信息,POCO 中使用的名称必须映射到 Table。

PrimaryKey

public class Foo
{
    [PrimaryKey]
    public long Id_Foo { get; set; }
    public string FooName { get; set; }
}

PrimaryKey 属性将一个 Property... 奇迹般地... 标记为数据库中的 PrimaryKey。使用此功能,您可以调用

accessLayer.Select<Foo>(155151 /*This is the PrimaryKey we are looking for*/);

InsertIgnore

标记一个 Property,使其不自动包含在 InsertStatement 中。默认情况下,PrimaryKey 继承此属性。

ForeignKey (正在进行中)

嗯,在某个美好的日子里,当我的工作不那么辛苦时,我曾想过,如果自动进程也能加载 NavigationPropertys ,那将很有趣。NavigationProperty 这个术语来自 EF,定义为

"在概念模型中表示从一个实体类型到另一个实体类型的导航。"

所以 NavProperty 只是一个属于另一个对象类型的 Property,并且这种关系是通过 ForeignKey 来描述的。

public class FooTest
{
    public class Foo
    {
        [PrimaryKey]
        public long Id_Foo { get; set; }
        public string FooName { get; set; }
 
        public long Image_Id { get; set; }
 
        /// <summary>
        /// A Property that is of the type that is referred to
        /// 1 TO 1 relation
        /// </summary>
        [ForeignKey("Image_Id")]
        public virtual Image img { get; set; }
 
        /// <summary>
        /// A Property that is a List of the type that is referred to
        /// 1 TO Many relation
        /// </summary>
        [ForeignKey("Image_Id")]
        public virtual IEnumerable<Image> imgs { get; set; }
    }
 
    public class Image
    {
        [PrimaryKey]
        public long Id_Image { get; set; }
        public byte[] ImageData { get; set; }
    }
}

正如所写,这是一个已知存在问题/bug/麻烦的功能

  • Select 是一次性的,对集合所做的更改不会被管理器观察到。
  • Foreign POCO 必须有且仅有一个 PrimaryKey 属性,如果找到多个,则取第一个。
  • 仅支持 Eager 加载。加载大型对象树时,所有对象都会一次性加载。

LoadNotImplimentedDynamic

Select 语句返回的信息比 POCO 中构建的信息更多时,此属性
(必须具有此签名)

[LoadNotImplimentedDynamic]
public IDictionary<string, object> UnresolvedObjects { set; get; }

(属性名称无关紧要) 将会填充数据(参见 FactoryMethods)。

IgnoreReflection

简单来说:与 XmlIgnore 属性一样,它标记一个 Property,使其不被 Mapper 的任何函数索引和访问。即使结果中包含一个与此属性匹配的列,该属性也不会被使用。

RowVersion

定义一个 RowVersion 属性。定义后,所有 accessLayer.Update()accessLayer.Refresh() 的调用都将使用此 Property 来检查更改。

加载策略

有两种加载方式:通过 POCO 中定义的工厂方法,或通过属性进行自动自定义。第二种方式是当没有或没有正确的工厂可用时的回退方式。

构造函数和方法注入

管理器可以检测到一个方法来从中提取语句。例如,如何定义一个不带参数创建 Select 语句的方法

public class Foo
{
    public long Id_Foo { get; set; }
    public string FooName { get; set; }
 
    [SelectFactoryMehtod]
    public static string CreateSelectStatement()
    {
        return "SELECT * FROM Foo";
    }
}

当定义了某个方法时,管理器将始终使用该方法来创建 Select 语句,并跳过任何其他基于反射的创建。

对于 Select,这在 Class 级别也是可能的

[SelectFactory("SELECT * FROM Foo")]
public class Foo

但是 Select 必须是 PublicStaticUpdateInsertDelete 工厂方法必须是非 static 的。您可以返回一个 stringIQueryFactoryResult 实例。为了防止 SqlInjection,当您使用参数时,强烈推荐此方法。

一个使用 IQueryFactoryResult 进行 UpdateDelete,以及使用 String 进行 Select 的示例

[SelectFactory("SELECT * FROM Foo")]
public class Foo
{
    public long Id_Foo { get; set; }
    public string FooName { get; set; }
 
    [DeleteFactoryMethod]
    public IQueryFactoryResult CreateDeleteStatement()
    {
        var result = new QueryFactoryResult("DELETE FROM Foo WHERE Id_Foo = @1", 
            new QueryParameter()
            {
                Name = "@1", Value = Id_Foo
            });
        return result;
    }
 
    [UpdateFactoryMethod]
    public IQueryFactoryResult CreateSomeKindOfUpdate()
    {
        var result = new QueryFactoryResult("Update Foo SET FooName = @param WHERE Id_Foo = @1",
            new QueryParameter()
            {
                Name = "@1",
                Value = Id_Foo
            },
            new QueryParameter()
            {
                Name = "@param",
                Value = FooName
            });
        return result;
    }
}

可以将参数从调用者传递给函数。当调用者提供参数时,这些参数将传递给具有相同签名的函数。这个想法或多或少地被 ASP.NET MVC 方法“厚颜无耻地”借鉴了。

在 2.2.27 版本之后,您还可以使用 RootQueryIQueryBuilder 方法上创建语句。

示例

[UpdateFactoryMethod] 
public IQueryBuilder CreateSomeKindOfUpdate(RootQuery query, string everythingElse) 
{ 
    return query.Select.Table<Foo>().Where.Colum(e => e.FooName).Is.Like(everythingElse);
}

请注意,如果您想在 FactoryMethod 中使用查询语法,您必须在 DbAccessLayer 上启用 Multipath 选项。

简单示例

public class FooTest
{
    public class Foo
    {
        public long Id_Foo { get; set; }
        public string FooName { get; set; }
 
        [UpdateFactoryMethod]
        public static IQueryFactoryResult CreateSomeKindOfUpdate(string someExternalInfos)
        {
            if (string.IsNullOrEmpty(someExternalInfos))
                return null; //Noting to do here, use the Automatic loading
 
            var result = new QueryFactoryResult
            ("SELECT * FROM Foo f WHERE f.FooName = @info", new QueryParameter()
            {
                Value = someExternalInfos,
                Name = "@info"
            });
            return result;
        }
    }
 
    public FooTest()
    {
        var access = new DbAccessLayer(DbTypes.MsSql, 
        "Data Source=(localdb)\\Projects;Initial Catalog=Northwind;Integrated Security=True;");
        var @select = access.Select<Foo>("SomeName");
    }
}

我们提供给...的 string

access.Select<Foo>("SomeName");

...将被传递给 Select 函数以创建语句,然后执行该语句。

通过使用接受这些参数的构造函数,还可以控制从 DataRecord 到您的类的加载。

public class Foo
{
    [ObjectFactoryMethod]
    public Foo(IDataRecord record)
    {
        Id_Foo = (long)record["Id_Foo"];
        FooName = (string)record["FooName"];
    }
 
    public long Id_Foo { get; set; }
    public string FooName { get; set; }
}

当有必要创建 Poco 的新实例时,总有一个 IDataRecord 可以从中加载,因此通过构造函数注入,我们找到这个并为其提供数据。

XML 字段加载

有一个新属性

FromXmlAttribute

它允许从 XML 序列化的列中简单地加载对象。该属性包含两个参数

  1. FieldName [必需]
  2. LoadStrategy [可选]

第一个参数与 ForModel 属性的效果相同。

最后一个参数定义了该 Property 的用法。

是否应包含在 Select 语句中 => 列存在

是否应从 Select 语句中排除 => 列不存在,但将在语句中添加

在这两种情况下,如果列存在于结果流中,都会尝试将其反序列化为 Property 定义的类型。如果这是一个实现或 IEnumerable<T>,结果也应该格式化为列表。

无属性配置

根据用户 Paulo Zemek 的建议,我修改了纯反射的元数据 API,以支持对元数据进行运行时操作。

要配置任何对象,您必须实例化一个 Config 类。它充当内部 API 的外观。

要扩展基于反射的行为,您必须在任何 Config 实例上调用 SetConfig 方法。在给定的回调中,您可以访问各种方法来添加属性信息,如 ForModel 等。所有辅助方法都使用 3 个基本方法

public void SetPropertyAttribute<TProp>(Expression<Func<T, TProp>> exp, DataAccessAttribute attribute)
{
    var classInfo = config.GetOrCreateClassInfoCache(typeof(T));
    var info = ConfigHelper.GetPropertyInfoFromLabda(exp);
    var fod = classInfo.GetOrCreatePropertyCache(info);
    fod.AttributeInfoCaches.Add(new AttributeInfoCache(attribute));
}

public void SetMethodAttribute<TProp>(Expression<Func<T, TProp>> exp, DataAccessAttribute attribute)
{
    var classInfo = config.GetOrCreateClassInfoCache(typeof(T));
    var info = ConfigHelper.GetMehtodInfoFromLabda(exp);
    var fod = classInfo.MethodInfoCaches.First(s => s.MethodName == info);
    fod.AttributeInfoCaches.Add(new AttributeInfoCache(attribute));
}

public void SetClassAttribute(DataAccessAttribute attribute)
{
    var classInfo = config.GetOrCreateClassInfoCache(typeof(T));
    classInfo.AttributeInfoCaches.Add(new AttributeInfoCache(attribute));
}

您可以直接使用这些方法将数据添加到内部 ConfigStore 或辅助类中。

public void SetForModelKey<TProp>(Expression<Func<T, TProp>> exp, string value) 
{     SetPropertyAttribute(exp, new ForModel(value)); }

在接下来的一个版本中,我将提供一种方法来加载和存储所有这些数据到 XML 中。所有类型信息都可以通过使用 Config 类中的 static 方法来访问。这将允许您重用类型信息。

所有类型访问部分都已实现 ThreadSave(线程安全)。

有两种管理配置的方法

从外部

您可以在代码的任何地方调用

new Config().SetConfig<T>(s => { ... })

这允许您以各种方式配置一个众所周知的 POCO。生成的将信息添加到 LocalConfig 存储。

从内部

万岁!有了一个新属性!ConfigMethodAttribute。您可以用该属性装饰一个 static 方法,该方法将接收一个 Config 实例,然后允许您在类本身内部进行配置。

速度测试

最近,我使用 Frans Bouma 的 RawBencher 对 YAORM 和其他 ORM 进行了评估。我发现当前版本在某些……我们称之为“非最优 POCO”用法方面存在一些极其严重的问题。由于 YAORM 严重依赖于 ADO.NET 兼容的构造函数,并且仅将反射作为一种回退方法,因此这种方式非常缓慢。在测试中,它花费了大约 6,000 毫秒来枚举所有 31465 个条目。与 EntityFramework 相比,这非常慢,更不用说 Dapper 了 ;-)。

因此,我对这些不自包含且非 ADO.NET 构造函数的 POCO 进行了一些重大改进。

引用

ADO.NET 构造函数

我一直在谈论一种符合 Ado.net 标准的 Ctor。这种构造函数由 POCO 定义,接受 IDataReader | IDataRecord 实例,从结果集中读取所有必需的字段,然后设置和/或将这些值转换为其属性。

在对现有代码进行更改后,包括运行时自动代码创建以及使用编译的 lambda 表达式而不是大量使用反射 API,我非常惊讶。从 6,000 毫秒降至 320 毫秒。通过此测试,我还对新的 Config API 进行了一些改进和更改,例如

  • 静态工厂设置
  • 为属性上的 Attributes 提供了多个预定义设置器
  • InMemory ADO.NET Ctor 创建的控制

内部反射

ORM 使用内部反射/IL/表达式/CodeDom 提供程序来在运行时生成大部分所需的代码。

这些技术混合使用,因为有些部分实现起来太耗时了,无法用 IL 完成。对于用于在运行时生成加载实体的构造函数的 CodeDOM 部分来说,这是正确的。这最初只由 EntityCreator 使用,但后来也被修改为在运行时调用。所有基于反射的工作都位于 MetaAPI 内,并从中派生出 ORM。

引用

MetaAPI 使用 IL 和表达式来编译属性和方法的访问器。方法被包装成 IL DynamicMethod,属性被包装成表达式。

将来,基本的反射 API (MetaAPI) 可能会被移到一个非常独立的程序集中,因为它被设计为通用的。访问所有内容的最基本存储是……

public class MetaInfoStore<TClass, TProp, TAttr, TMeth, TCtor, TArg> : 
	IDisposable
	where TClass : class, IClassInfoCache<TProp, TAttr, TMeth, TCtor, TArg>, new()
	where TProp  : class, IPropertyInfoCache<TAttr>, new()
	where TAttr  : class, IAttributeInfoCache, new()
	where TMeth  : class, IMethodInfoCache<TAttr, TArg>, new()
	where TCtor  : class, IConstructorInfoCache<TAttr, TArg>, new() 
	where TArg   : class, IMethodArgsInfoCache<TAttr>, new()

如前所述,它被设计为通用的和可重用的。它包含一个类,通过使用 GetOrCreateClassInfoCache 方法将 Type 实例转换为 TClass 的实例。此方法当然也是递归的,并且意识到这一点,它将从本地存储中提供一个实例,或枚举所有“最常用的信息”。这意味着它将枚举每个属性、方法、参数、构造函数和它们之上的属性,并存储它们。此可选的 ThreadSave 类通过使用 EnableThreadSafety 属性来实现。此可选属性的引入是为了确保最大程度的性能。

此类可以是 Global(全局)或 InstanceLocal(实例本地)。通过使用构造函数

public MetaInfoStore(bool isGlobal)

您可以指定这一点。为了确保最大程度的性能,您还可以实现例如 IPropertyInfoCache 并重写 Init 方法来定义常用访问的新 Attributes。这带来了巨大的性能提升,因为否则您必须循环遍历所有 Attributes 的集合来查找所需的 Attribute,这当然很耗时。请参阅 DbPropertyInfoCache 以查看示例。

使用此方法的另一个好理由是,通过简单地将“fake”属性和 Attributes 添加到集合中,可以动态地添加它们。此功能由 ConfigAttribute 用于扩展 POCO。YAORM 的每个部分都使用此存储,如果您向其中添加新属性,它都会找到。例如,MethodInfoCache 实现了一个构造函数

internal MethodInfoCache(Func<object, object[], object> fakeMehtod, 
string name = null, params TAtt[] attributes)

这允许您在不使用 .NET 技巧(如 dynamic)的情况下,将每个方法添加到每个类中。

LocalDbRepository

数据库,但本地的

它是一个集合,将来会强制执行 ForginKeyDeclarationsAttributes,以及 ForginKeyAttributes。使用此类,您可以在作用域内定义本地数据库。作用域内的所有“表”都将被验证,如果您向其中添加任何对象,并且您尝试添加一个会违反 ForeignKey 的实体,则会抛出异常。

首先,您需要设置一个 DatabaseScope

using (new DatabaseScope())
{

}

此范围将是一个容器,并验证作用域内定义的多个表。此语法取自 .NET Framework 中的 TransactionScope。然后,您需要通过在作用域内创建表来定义表。

using (new DatabaseScope())
{
	_books = new LocalDbReposetory<Book>();
	_images = new LocalDbReposetory<Image>();
}

BookImage 的定义如下:

public class Image
{
	[PrimaryKey]
	public long ImageId { get; set; }
 
	public string Text { get; set; }
 
	[ForeignKeyDeclaration("BookId", typeof(Book))]
	public int IdBook { get; set; }
}
 
public class Book
{
	[PrimaryKey]
	public int BookId { get; set; }
 
	public string BookName { get; set; }
}

重要的是要装饰 PrimaryKeyAttributeForeignKeyDeclarationAttribute 来定义两个表之间的有效连接。您可以选择使用 Attributes 或 Config 方法(等等)。ForgeinKeyDeclarationAttribute 的第一个参数很快就会被弃用。您可以使用 LocalDbReposetory 的构造函数来定义一个 PrimaryKey 生成器,如果您使用非 LongIntGuid 类型的 PrimaryKey,或者您想定义其他 Autoincriment(自动增量)为 1 且从 1 开始的。

版本 2.0.160 更改

我对当前版本进行了一些重大改进。包括一些性能改进以及许多额外功能。

TransactionScope

我实现了对本地数据库事务的支持。如果您定义了一个 TransactionScope,所有集合相关的操作(添加/删除)都会被跟踪并可以回滚。此外,它允许插入无效数据,只要事务正在进行中,就不会验证 ForginKeyConstrains 或其他 Constrains,并且一切都将被接受。在事务结束时,所有插入和受影响的项目都将被验证。这允许快速批量插入以及部分插入,您先插入一个实体,最后插入相关的实体。要启用此功能,您需要创建一个 .NET 类 TransactionScope 的新实例。

(示例来自 LocalDbTransactionalTest)

Assert.That(() =>
{
	using (var transaction = new TransactionScope())
	{
		var book = new Book();
		var image = new ImageNullable();
 
		Assert.That(() => _books.Add(book), Throws.Nothing);
		image.IdBook = book.BookId;
		Assert.That(() => _imagesNullable.Add(image), Throws.Nothing);
		transaction.Complete();
	}
}, Throws.Nothing);
IdentityInsertScope

支持插入具有有效 PrimaryKey 的实体。在旧实现中,LocalDbReposetory 总是为每个实体创建一个新的 Identity,而不考虑现有值。现在可以通过使用 IdentityInsertScope 来禁用此功能。与 TransactionScope 一样,它定义了一个作用域,其中不再应创建 IdentityValues

(示例来自 LocalDbTransactionalTest)

Assert.That(() =>
{
	using (var transaction = new TransactionScope())
	{
		using (new IdentityInsertScope(true))
		{
			var image = new Image();
			image.ImageId = 10;
			_images.Add(image);
			var book = new Book();
			_books.Add(book);
			image.IdBook = book.BookId;
 
			Assert.That(book.BookId, Is.EqualTo(1));
			Assert.That(image.ImageId, Is.EqualTo(10));
 
			Assert.AreNotEqual(_books.Count, 0);
			Assert.AreNotEqual(_images.Count, 0);
			transaction.Complete();
		}
	}
}, Throws.Nothing);

您可以条件性地决定是否仍要处理和创建默认值(在此示例中为 Id == 0)。对于所有其他 PK 值,将跳过 Identity 创建。此作用域必须在有效事务内部创建。

数据库 XML 序列化器

应一位读者的要求,现在可以一次性序列化整个数据库。从外部来看这有点棘手,因为数据的集成没有得到保证。读取数据很容易,但迁移回数据很困难。我实现了 IXmlSerializable 接口。您可以通过在任何表上调用来创建一个外观

(示例来自 DatabaseSerializerTest)

LocalDbReposetory<Users> users;
using (new DatabaseScope())
{
	users = new LocalDbReposetory<Users>(new DbConfig());
}
 
users.Add(new Users());
users.Add(new Users());
users.Add(new Users());
 
var serializableContent = users.Database.GetSerializableContent();
var xmlSer = new XmlSerializer(typeof(DataContent));
using (var memStream = new MemoryStream())
{
	xmlSer.Serialize(memStream, serializableContent);
	var content = Encoding.ASCII.GetString(memStream.ToArray());
	Assert.That(content, Is.Not.Null.And.Not.Empty);
}

要将数据写回表,您必须在设置过程期间执行此操作。这是一个我必须仔细考虑的设计决策。我将其作为一项要求,以绝对确保完整性。这也有性能原因。

(示例来自 DatabaseSerializerTest)

LocalDbReposetory<Users> users;
using (new DatabaseScope())
{
	users = new LocalDbReposetory<Users>(new DbConfig());
 
	using (var memStream = 
    new MemoryStream(Encoding.ASCII.GetBytes(DbLoaderResouces.UsersInDatabaseDump)))
	{
		new XmlSerializer(typeof(DataContent)).Deserialize(memStream);
	}
}
 
Assert.That(users.Count, Is.EqualTo(3));
Assert.That(users.ElementAt(0), Is.Not.Null.And.Property("UserID").EqualTo(1));
Assert.That(users.ElementAt(1), Is.Not.Null.And.Property("UserID").EqualTo(2));
Assert.That(users.ElementAt(2), Is.Not.Null.And.Property("UserID").EqualTo(3));

Entity Creator

该库现在包含一个控制台应用程序,该应用程序可以根据数据库创建实体。在当前状态(2014 年 11 月 1 日),仅支持 MsSql 数据库,并且测试非常基础。

其基本组件的使用很简单,但潜力巨大。此外,这里的想法是重写当前的 CommandoLine 工具以支持完全参数化的工作。

启动程序后,它会询问您一个目标目录(生成的文件将存储在此处)和一个连接字符串。

之后,您将看到该数据库的一些信息,包括表、存储过程和视图。视图的处理方式与表相同,因为调用语法几乎相同。

通过键入表、Sp 或视图的编号,您可以更改该对象的设置。其他命令包括:

  1. \compile
  2. \autoGenNames
  3. \add

您开始过程

  1. 开始编译所有未被排除的表、SP 和视图
  2. 开始一个简单的重命名过程,该过程将删除数据库名称中的所有 '_'、' ' 字符,并将其替换为 C# 兼容的名称
  3. 未实现(将来,将有可能添加静态加载器构造函数。这将极大地提高选择性能。但由于最新功能(基于 XML 的加载),此功能尚未完全实现)。

Entity Creator UI

虽然使用控制台应用程序可能是一个不错的开始,但更舒适的解决方案是一个合适的 UI 应用程序,它可以以更用户友好的方式执行相同的功能。因此,我在同一个 Repro(可能会更改)中创建了一个新的解决方案,其中将包含以后所有 EntityCreator 相关代码。包括核心程序集的新 UI。它尚未完成,将会更改,但方向是明确的。

它还包含一个新预览功能,允许您预览生成的代码,并查看您对配置所做的任何更改。新的存储格式与旧格式不同,不兼容。

版本 2.0 更改

  • 更多单元测试(耶!)
  • 从 DB 字段到类属性的映射现在存储在 ClassInfoCache 中并已持久化
  • Reflection API 现在使用 HashSet 而不是 list
  • DataConverterExtentions 已减少
  • PropertyInfoCache 现在用于通过动态编译的 Lambda 表达式直接访问属性
  • ClassInfoCache 级别的 Static 工厂方法 Delegate 现在负责创建 POCO。
  • EntityCreator 的一些方法已从 EXE 移至 DataAccess.dll
  • 一个新类“FactoryHelper”现在能够通过使用 EntityCreator 的改进方法在运行时创建 ADO.NET Ctor
    • ctor 创建的重大改进:EntityCreator 和运行时创建者现在能够创建构造函数,用于
      • (单个)XML
      • (列表)XML
      • (单个)ForginKey
      • (列表)ForginKey
      • 值转换器 (ValueConverter)
      • Null
      • (可能的)Null
      • ForModel
  • 添加了多个注释
  • 完全删除了 Linq Provider
  • DbCollection 替换了 ReposetoryCollection
  • Bug 修复

版本 2.0.160 更改

  • 查询语法的次要更改
  • LocalDbRepository 改进
  • 若干 Bug 修复
  • 更多测试(耶!)
  • DbConfig 用法的完全重构
    (注意:在旧版本中,使用了一个映射到全局缓存的 static 实例。现在您可以将 DbConfig 的一个自有实例传递给所有需要反射的部分)。
  • SQLite 适配器改进
    • 许多函数没有为 SqLite 提供适当的支持
    • 将所有 SqLite 单元测试设置为 CI 过程中的强制项

关注点

这个项目带给我很多乐趣,也让我熬过了一两个不眠之夜,我想我因为这个项目而失眠的日子不会是最后一次了。该库包含一个小的 Linq Provider,它被标记为已弃用,因为在实现和开发过程中,我……可以说我被 Linq 惹恼了。

我期望这个项目能给我一些想法,并进一步改进我的工作。

感谢所有花时间阅读本文的人。也感谢我的导师 Christian H. 的见解和帮助。

历史

  • V1 初始提交
  • V2 创建了一个简单的实体创建器,首次尝试构建 StoreProc 调用器和基于 XML 的 Ref 加载
  • V3 程序版本 V2 的更改日志
© . All rights reserved.