Lostinet

Lostinet
随笔 - 18, 文章 - 0, 评论 - 297, 引用 - 4
数据加载中……

AbstractRecord 预告. (关键字 ORM,ActiveRecord,DomainModel)

# 前言:数据库交互之路

 

还记得以前在ASP时代,虽然VB/JS是基于对象的, 但是那时做网页的编程, 基本不会去声明对应数据库的格式的类. 当时的编程方式就是, 用ADO的RecordSet去读取数据. RecordSet可以说是一个集合类. 提供了最简单的, 基于字段名读取数据的Indexer. 例如 <%=rs("CustomerName")%> 这样的语句,也许大家都不会觉得陌生. 如果要进行INSERT/UPDATE/DELETE, 那么就需要手动写SQL了. 那时纯粹是传递一条SQL语句. 这个语句是需要程序员自己构造的. 如果一个表有几十个字段, 那条语句写下来, 单单是双引号和单引号就能让人觉得头晕. 好在,我在ASP上, 只工作了8个月. 就转去ASP.NET了.

 

ASP.NET下,我们有全新的ADO.NET. 那时基本都是从DataSet开始的. 因为它实在和RecordSet很像. 当时只是觉得语法有点不同而已. 重要的一点是, SqlDataAdapter能够执行INSERT/UPDATE. 也就是说, 我可以把一条记录Load下来, 然后修改其中的某些属性, 然后再更新回去. 这个过程并不需要写很长的SQL语句. 出错的机会很少.

 

相比ASP时代. ADO.NET已经是救世主了. 当时的我, 并不觉得DataSet有什么不好的.

 

后来ORM在网上热起来. 我突然发觉row["CustomerName"]是个很笨的方法. 在我的桌子旁边, 总有一张数据表结构的印刷版. 当我要操作某张表, 我就把那张纸拿出来对照. 那实在是很麻烦. 我也慢慢体会到那种强类型的对象编程的好处. 不是虚拟继承的好处, 而是当我在vs.net上打一'.'的时候,属性就会被列出来了.

 

是的,我并不关心面向对象有什么好处. 我只希望我的编程工作能简单点,尽量少出错. 可惜那时候的ORM框架并没有解决一些问题. 有些要我先设计对象,然后自动生成数据库. 有些则需要写一大堆的xml文件. 有些生成工具, 要不断地执行用于更新代码. 我自己后来也实现了自己的生成工具, 不过效果也不太理想.

 

后来的一段时间, 我没有去弄那些东西了. 我采用POCO的方案. 老老实实地把数据从SqlDataReader复制到POCO去. 插入和更新的时候, 老老实实地写那种很长的语句. 这个方案其实已经比DataSet好很多了. 起码数据是强类型的对象, 在VS.NET下有提示. 而SqlScope也帮我省了很多代码.

 

后来我渐渐过渡到一种简单的DomainModel+DomainService的方案.我把对象的属性都弄成ReadOnly,把数据都改为internal. 这样就能保护我的数据的逻辑. 这个模型一直用到现在.

 

 

世界在发展,编程的模式不会一成不变. . 在我离开CSDN后,我曾经有一段时间脱离社区. 当我回头时, 才发现自己已经脱离编程世界好久似的. 我充满恐惧, 恐怕被变化所抛弃. 于是我重新返回社区. 先是潜水, 去读博客, 了解这个世界这今年来技术有什么更新. 博客园帮了我很多. 像博客园里很多人都自己做了一套ORM的实现, 正是对以往的数据库操作模型的的不满. 而我也深受他们的影响. 我想起了以前写的文章 <<用 System.Reflection.Emit 自动实现调用存储过程的接口>> http://blog.joycode.com/lostinet/archive/2004/11/19/39238.aspx . 我决定沿用那个方式, 去实现一个全新的ORM.

 

 

# AbstractRecord的基本概念

 

一开始是这样的, 我想用interface来表示一个数据库的记录. 例如

[Table("Employees")]

public interface Employee

{

     string EmployeeName { get;set;}

}

 

后来经过思考, 根本没必要做成interface, 而换成abstract class, 能添加用户自定义的代码, 那样会更好:

[Table("Employees")]

public abstract partial class Employee

{

     public abstract string EmployeeName{get;set;}

}

后面会有一些篇幅去描述作为abstract class的好处.

 

这个编写类型的方式, 不是普通的POCO或者DomainModel. 它也不是Active Record,因为它不需要集成某个基类. 后来我发现它连 ORM 都不是. 因为不存在Mapping这个东西. 它负责的就是读写数据库而已. 也就是说, 它针对的是数据库方面, 而不是对象逻辑方面.

 

我自己给了它一个新的名字 : "Abstract Record" .

 

 

下面直接给出一个例子, 描述AbstractRecord框架下编程的第一印象:

 

拿Northwind数据来说 , 定义方式:

[CSPAR("Categories")]

public abstract partial class Category

{

     public abstract int CategoryID { get;}

 

     public abstract string CategoryName { get;set;}

     public abstract string Description { get;set;}

 

}

 

是的, 就这样, 就足够了. 无需编写配置文件, 也无需定义太多的Attribute, 也不需要定义字段, 然后傻傻地get和set.

 

既然是abstract, 不需要写实现代码了? 不需要. 这就是AbstractRecord的核心思想. 由框架去帮你实现.

 

那么,这样的对象,是无法new Category()的, 怎样实现CRUD操作?? 下面是说明的代码(ASP.NET):

 

//下面是CreateCategory.Aspx的内容

protected void ButtonCreate_Click(object sender, EventArgs args)

{

     Category cate = CSPAbstractRecord.NewRow<Category>();   //实例化一个抽象类!

     cate.CategoryName = textBoxName.Text;

     cate.Description = textBoxDescription.Text;

     CSPAbstractRecord.Save(cate);    //INSERT

     Response.Redirect("EditCategory.Aspx?CategoryID=" + cate.CategoryID); //自动得到自增的id.

}

 

上面的代码,描述了如何创建一个abstract class的实例. CSPAbstractRecord正是负责控制CRUD的类.

CSPAbstractRecord是AbstractRecord在我的那个系统上的实现. 开发人员只需要学习2个类, 就能够完成绝大部分的事情了!

 

//下面是EditCategory.Aspx的内容

protected Category _category;

protected void EnsureCategory()

{

     if (_category != null) return;

     int id = int.Parse(Request.QueryString["CategoryID"]);

     _category = CSPAbstractRecord.LoadRow<Category>(id);    //SELECT

     if (_category == null) throw (new Exception("没有该数据或者数据已经被删除!"));

}

protected override void OnLoad(EventArgs args)

{

     base.OnLoad(args);

     if (IsPostBack) return;

     EnsureCategory();

     //初始化界面

     textBoxName.Text = _category.CategoryName;

     textBoxDescription.Text = _category.Description;

}

protected void ButtonUpdate_Click(object sender, EventArgs args)

{

     EnsureCategory();

     _category.CategoryName = textBoxName.Text;

     _category.Description = textBoxDescription.Text;

     CSPAbstractRecord.Save(_category);   //UPDATE

}

protected void ButtonDelete_Click(object sender, EventArgs args)

{

     EnsureCategory();

     CSPAbstractRecord.Delete(_category); //DELETE

     Response.Redirect("CategoryList.Aspx");

}

 

上面的代码,使用了CSPAbstractRecord.LoadRow和CSPAbstractRecord.Delete去进行SELECT和DELETE的操作.

 

由上面的代码可以看出, AbstractRecord是非常容易使用, 而且, 代码量非常少. 短短几行, 就已经完成CRUD的操作界面. 我实在是想不出能比这种方式更节省代码的方式了.

 

使用AbstractRecord编程的重点:

1. 用极少的代码去定义类型化数据的抽象类.

2. 使用CSPAbstractRecord.NewRow/LoadRow/Save/Delete进行CRUD的操作.

 

 

# 封装数据

 

传统的POCO或贫血的DomainModel都有一个共同点, 就是所有的属性, 都是 get;set 的. 能得到那些对象, 就能随意更改属性, 甚至会破坏应用程序的逻辑. 如果是小型的应用, 业务逻辑简单, 那无所谓. 但是如果是一个复杂的系统, 那么保护数据不被滥用 , 是非常重要的事情. 这个,是很多ORM或相关框架无法做到的.

 

AbstractRecord允许程序员把数据成员定义为protected或者是protected internal. 这就是封装的根本实现方案.

 

就上面那个Category的例子, 里面有一个Picture字段. 通过数据的封装, 可以实现类型的转换:

public abstract partial class Category

{

     protected abstract byte[] InternalPicture { get;set;}

     public System.Drawing.Image Picture

     {

         get

         {

              if (InternalPicture == null)

                   return null;

              return System.Drawing.Image.FromStream(new System.IO.MemoryStream(InternalPicture));

         }

         set

         {

              if (value == null)

              {

                   InternalPicture = null;

                   return;

              }

              System.IO.MemoryStream ms = new System.IO.MemoryStream();

              value.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);

              InternalPicture = ms.ToArray();

         }

     }

}

上面的代码 , 一个是 abstract 的 InternalPicture. 这个属性被读写时, 反映的是数据库字段的读写. 而 public System.Drawing.Image Picture 并不是abstract的, 它通过InternalPicture,实现外部数据类型和数据库类型的转换. 这是程序员自定义的代码, 也就是为什么我使用abstract class, 而不是interface的原因.

 

(TIPS: 我们还可以定义这样的属性: public abstract string MyField { get;internal protected set;} 这种属性能被外部读取, 但只能在同assembly的范围内修改.)

 

基于这种形式的数据封装, 程序员甚至可以把所有的数据属性都定义为protected或protected internal. 然后提供一个公共的方法, 或者是使用另外的一个DomainService去修改那些数据. 业务逻辑就这样被保护起来了.

 

 

# 关系处理

 

关系处理一直是ORM的难题. 因为基于POCO的ORM, 它要帮你填充所有相关的数据, 不能实现LAZY LOAD. 而有些能实现的呢? 则需要很多定义. 或者要继承某个基类, 然后调用基类的方法去取得相关数据.

 

AbstractRecord使用一种基于数据库定义的关系去生成相关对象的实现. 在关系的处理上, 很简单:

[CSPAR("Order Details")]

public abstract partial class OrderDetail

{

     public abstract Order Order { get;}

     public abstract Product Product { get;}

}

 

就是这样,当程序员定义一个abstract的,返回其他相关类型的属性, 就已经完成了多对一关系的定义了. 无需写更多的代码. 无需定义Attribute或者是xml文件.

 

那么一对多呢?

[CSPAR("Orders")]

public abstract partial class Order

{

     public abstract OrderDetail[] Details { get;}

}

 

当返回值改为相关对象类型的数组是, 就是多对一的情况. 一对多,和多对一的定义, 是独立的.少了哪个都可以 .

 

剩下的就是 多对多的"难题"了. 也许你看到下面的代码, 都会惊叹原来多对多是那么地简单:

 

[CSPAR("Orders")]

public abstract partial class Order

{

     public abstract OrderDetail[] Details { get;}

     public Product[] Products

     {

         get

         {

              return Array.ConvertAll<OrderDetail, Product>(Details, delegate(OrderDetail d) { return d.Product; });

         }

     }

}

[CSPAR("Products")]

public abstract partial class Product

{

     public abstract OrderDetail[] Details { get;}

     public Order[] Orders

     {

         get

         {

              return Array.ConvertAll<OrderDetail, Order>(Details, delegate(OrderDetail d) { return d.Order; });

         }

     }

}

 

上面的代码, 使用一对多和多对一的属性, 再使用一次 Array.ConvertAll , 就实现了多对多. (其实数据库的多对多实现也是通过一对多组合而成的. )

 

# 数据库查询

 

很多人不喜欢ORM, 是因为大多的ORM框架, 都企图让数据库变得透明. 甚至是弄出HQL之类的语法去统一数据库查询方案. 其实这是软件发展的一个方向. 但不见得是程序员所希望的.

 

CSPAbstractRecord设计的时候, 就和数据库紧密相连, 并且能与传统的数据库查询相结合.

 

先看看CSPAbstractRecord是如何进行简单查询的:

 

查询存货不足的产品:

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).OrderBySql("ProductName", false);

实际上UnitsInStock<{0}就是一条SQL语句. 而CSPAbstractRecord会把{0}换成@p1_1这样的形式去查询SqlServer.

 

根据Category查询Product:

Product[] products = CSPAbstractRecord.All<Product>().Where("CategoryID IN (SELECT CategoryID FROM Categories WHERE CategoryName={0})", category).OrderBySql("ProductName", false);

 

或者是组合起来:

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).Where("CategoryID IN (SELECT CategoryID FROM Categories WHERE CategoryName={0})", category).OrderBySql("ProductName", false);

 

实际上, 使用IN/EXISTS就能实现INNER JOIN的功能了. 甚至在子查询内使用聚合:

Order[] orders=CSPAbstractRecord.All<Order>().Where("OrderID IN (SELECT OrderID FROM [Order Details] GROUP BY OrderID HAVING SUM(UnitPrice*Quantity)>{0} )", 10000);

 

如果你曾经研究过INNER JOIN,IN,EXISTS的区别, 你就会明白数据库会对这些查询进行优化. 三种方案的性能是一致的.

 

数据分页: AbstractRecord还带一个高性能的分页方式:

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).OrderBySql("ProductName", false).GetRange(start, pagesize, out allcount);

输入开始位置(例如pageindex*pagesize),要取回的记录数(pagesize),就能返回符合要求的记录,并且返回所有记录数.

就这样的一个GetRange方法, 就能满足目前ASP.NET开发的分页需求了.

 

如果上面的查询依然不能满足你的需要, 还可以使用CSPAbstractRecord.BatchLoadRows方法:

object[] productid=......//用自定义的方法去取得ProductID列表

Product[] products = CSPAbstractRecord.BatchLoadRows<Product>(productids);

 

上面的,都是基于SQL的查询方案. Where方法还可以指定Predicate<T>来选择符合要求的记录:

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).Where(delegate(Product p) { return p.Details.Length>10 });

这种查询,先优先执行SQL,然后把所有符合SQL的数据返回,在本地进行CLR的筛选. 其实内部只是简单地使用Array.FindAll<Product>()方法去筛选而已.

因为要加载所有数据, 所以只适合小记录的表. 或者是肯定SQL语句能排除大部分记录的情况下使用.

 

查询时的排序:

 

上面的方式使用了 .OrderBySql("ProductName", false); 来进行SQL的排序.

CSPAbstractRecord还支持使用 Comparison<T> , Comparer<T> 进行排序:

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).OrderBy(delegate(Product d1, Product d2) { return d1.Supplier.CompanyName.CompareTo(d2.Supplier.CompanyName); });

 

这种方案会加载所有相关的Supplier. 如果不想加载相关的对象, 还可以使用OrderByKey方法:

IDictionary dict = SqlScope.ExecuteDictionary("SELECT ProductID,CategoryName FROM Products INNER JOIN Categories ON Products.CategoryID=Categories.CategoryID");

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).OrderByKey(delegate(object k1, object k2)

{

     string n1 = dict[k1] as string;

     string n2 = dict[k2] as string;

     return string.Compare(n1, n2);

}, false);

上面的例子使用ProductID作为Key,找出相关的数据,然后用相关数据进行排序.

 

这个方法看上去和

Product[] products = CSPAbstractRecord.All<Product>().Where("UnitsInStock<{0}", 10).OrderBy(delegate(Product p1, Product p2)

{

     string n1 = dict[p1.ProductID] as string;

     string n2 = dict[p2.ProductID] as string;

     return string.Compare(n1, n2);

}, false);

没两样.

 

但实际上OrderBy(delegate(Product p1, Product p2)的方案会加载所有符合Where的Product, 而OrderByKey则不需要. 在使用GetRange分页的情况下, OrderByKey会先对主键进行分页, 然后才加载符合分页的 Product, 明显性能会高很多.

 

 

# 性能问题

 

CSPAbstractRecord内部实现了2级的缓存. 这个会对多对一,一对多,多对多的关系起到非常重要的作用. 不过因为这个实现是透明的, 程序员完全不用去理解其内部实现机制. 这里就省略了.

 

而对数据库的读写操作, 是完全的强类型. 可以断定的是, AbstractRecord 比 DataSet 要快!

 

 

总结:

Abstract Record在开发模式上的创新和其灵活的特点, 能极大提高开发速度. 但这篇文章只是作为预告, 而CSPAbstractRecord是直接集成到另外一个项目中. 所以目前不会提供任何下载. 估计能提供下载的时间是国庆之后.

 

下面是我临时写的CSP的Feature列表:

 

CSP.AbstractRecord的特点:

 

- 极简单 -开发人员只需要了解1 个类的静态方法就OK了. 不需要去明白架构设计接口之类的.

- 轻量级 -用数据库的定义去决定代码. 维护方便.

- 强类型 -数据储存为强类型字段,读写数据并不使用反射.

- 零实现 -你无需再定义字段,和编写属性的get;set;的实现

- 可扩展 -通过实现自定义的属性和方法,去得到通知或改变默认的行为.

- 零配置 -你无需为映射设置配置文件. 而数据库配置工作,由CSP完成

- 零整合 -直接包含在CSP内, 无需额外下载,部署.

- 一致性 -当同一行记录有多个实例时, 如果其中一个实例采用Save方法,那么其他对象将得到同步.

 

- 适合设计,适合分工合作. 强调,CSP.AR非常适合经常改变需求的企业信息管理软件的开发.

 

- 不需要继承类似EntityBase的类.也不需要实现任何接口.

 

- 支持web-trust-level=Medium,

     但需要对~/Portals/有写权限(这个以后可能可以选用BuildProvider的方式去防止写目录的问题)

 

- 轻松处理一对多,多对多的关系.

    

- 支持自定义查询

     可以用SQL语句,把所有的主键读取回来, 然后使用CSPAbstractRecord<T>.BatchLoadRows 方法.

    

- 支持复合查询

     使用INNER JOIN或者其他更加高级的SQL语句. 使用自定义的查询方案, 或者使用CSPAbstractRecordQuery的Where方法

    

- 支持超大数据的分页.

     使用CSPAbstractRecordQuery的GetRange方法去进行超大数据的分页:

     int totalCount;int pageIndex=11;int pageSize=20;int start=pageIndex*pageSize;

     Product[] products=CSPAbstractRecord.All<Product>().Where("CategoryId={0}",11).OrderBy("ProductName").GetRange(start,pageSize,out totalCount);

 

CSP.AbstractRecord的缺点:

- 不支持abstract class的序列化.

     其实已经提供了对序列化的支持. 不过那些abstract的类是不能用于序列化的.

     如果要把对数据进行序列化, CSP.AbstractRecord提供了种很好的做法.

     1.直接使用CSPAbstractRecord.Serialize/Deserialize方法去读写数据.

     2.使用POCO对象.定义abstract class的Copy方法,然后序列化该POCO对象.

     3.使用新类,把abstract class的实例当作成员,在类实例序列化,反序列化时,使用方案去处理abstract class的实例.

 

- 集成在CSP中, 只能运行在CSP.只支持SqlServer.

     对于使用CSP开发的人员来说, 这反而是优点.

     因为直接集成, 开发人员可以不用关系实现的细节, 减少学习和部署的难度.

     另外因为只支持SqlServer, 所以开发人员,在他们的代码上, 可以很放心地使用各种SqlServer的SQL功能!

     特别是在查询是使用IN/EXISTS 语句上, 针对SQLServer能更加简单直接地实现更多的功能.

 

- 支持继承,但是有个限度.

     原因是数据的储存是需要和类设计严格关联的.这样类的数据储存就受到了一定的限制.

     这个会在谈到分表,视图的设计的时候谈到如何实现继承的应用.

 

不少ORM框架都希望能和SQL语句say goodbye, 所以那些ORM框架都建立自己的查询定义语句.

其实编写那些语句本身就是麻烦的事情.

CSP.AbstractRecord则建议开发者使用简单直接的SQL条件语句.

例如: Product[] products=CSPAbstractRecord.All<Product>().Where("CategoryId={0}",11).OrderBy("ProductName").GetRange(10,10,out totalCount);

 

 

为什么叫CSP.AbstractRecord?而不是ORM?

ORM的最大特点是Mapper , 但是AbstractRecord不是Mapper. 它是一个数据的CRUD的可封装模式.

它可以保护数据,维护业务的逻辑的正确性.

 

偏向DB,而不是OO.

因为要搞出像Linq的db.Products.Add(product)的方案,实在不容易,而且得不偿失.

AbstractRecord的设计会接近数据库操作.

必须保存一项,才能进行更进步一步的操作.

而且这个会更加容易理解.

 

 

 

 

 

 

posted on 2007-08-15 18:16 Lostinet 阅读(4114) 评论(40)  编辑 收藏 所属分类: WOW

评论

#1楼   回复  引用  查看    

不错,辛苦了
2007-08-15 18:57 | 周银辉      

#2楼   回复  引用  查看    

很让人激动啊
2007-08-15 19:10 | JesseZhao      

#3楼   回复  引用  查看    

N年前,我刚转.net的时候有一个Lostinet非常让人崇拜,虽然此Lostinet仍是彼Lostinet,但是我却认为决非为同一人,因为此Lostinet太令人失望了!
认真读完了本文,我确信,AbstractRecord是个逆潮流的东西。为什么这样说?
1.和SQL语句say goodbye是合理的,要不然大家为什么对dlinq趋之若鹜?所以,这不仅是软件的发展方向,同时也是程序员所期待的。总不见得在一些业务代码中夹插一些SQL语句更让人容易理解吧?
2.“可以断定的是,AbstractRecord比DataSet要快!”这句话没有根据。DataTable从托管堆中分配内存的次数仅仅与Columns.Count有关,而与Rows.Count无关。当然,我相信你在后台Emit那些abstract类的具体类的时候也可以如法炮制。但是即便如此,也不至于比DataSet更快吧?
3.一些查询条件已经是BusinessModel的一部分,有必要封装成一个可传递的对象,而不是一堆堆SQL语句。聪明的业务开发人员总是通过篡改这些业务对象来实现公共的控制,这一切通过篡改SQL语句是无法实现的。很高兴dlinq保持了这一点,让我们有机会通过AOP来实现权限控制、实现公共的筛选器以及可配置的查询器。
4.Lostinet在讨论这些POCO类实现的时候总是强调“透明”的好处;而转回头来在讨论访问数据库细节的时候总是强调“透明”的麻烦。“透明”在这里成了一片可以任意支使的招牌,让人莫衷一是啊。
2007-08-15 21:36 | 双鱼座      

#4楼   回复  引用  查看    

的确 若干年前有个Lostinet非常强.现在好像已经不见了.最初是有他的"闪电论坛"了解的.
2007-08-15 21:40 | try      

#5楼   回复  引用  查看    

可否给出关闭对象缓存后的实际读写效率于ADO.NET fill DataSet的对比数据
2007-08-15 22:22 | 亚历山大同志      

#6楼   

|

#7楼[楼主]   回复  引用  查看    

测试环境是一个CPU 单核 P4 2.4G
SqlServer在本机.

Order : 830 Rows.
Repeat : 1000 Times.

Reader1 : 3831.84
Reader2 : 10645.5932
DataSet : 17439.1678
Record : 14019.9338

上面是输出的结果.
Orders表一共有830行.
用4种方式去读取数据.
每种方式都循环1000次.

Reader1 : 纯粹的while(reader.Read());
Reader2 : 在reader.Read()时,调用reader.GetValues()
DataSet : 采用SqlDataAdapter.Fill(DataSet)
Record : 采用AbstractRecord的方法(LoadReader模式不依赖缓存).

如果减去Reader1/Reader2这些SqlServer查询,ADO.NET内部消耗的时间,
AbstractRecord的性能可以说是DataSet的1.3到2.0倍.

2007-08-16 00:03 | Lostinet      

#8楼[楼主]   回复  引用  查看    

测试的代码是:
 
2007-08-16 00:05 | Lostinet      

#9楼[楼主]   回复  引用  查看    

作为一个数据访问的框架, 它提供的功能是否被滥用, 是另外的一回事.
编写BLL是程序员的责任. 而不是我的框架的责任.
而我的框架则已经有足够的措施去保护BLL的逻辑不被破坏.

DataSet再快,优化得再好,它也只是一个数据结构类.
它永远不会比得上强类型的实现快.

TypedDataSet? 那个其实只是提供了强类型的属性. 而它内部实现还是基于FieldName的get和set.

我文中有2处出现了"透明"这个字眼, 不知道双鱼座如何看出指的是POCO?
我喜欢POCO,是因为智能提示. 如果你有认真看完文章再评论得话.
或者我觉得你是一开始就以先入的态度去看我的文章了.


2007-08-16 00:13 | Lostinet      

#10楼   回复  引用    

很好的东西啊,有创意。大的思路上跟我之前公司的框架一个样,实现机制不同而已。
用法简单方便,学习成本低,框架本身也简单,不用像NHibernate等考虑复杂的问题,目前来说在团队内非常容易推广实施,效果不错。

相比较而言复杂的OR框架,对于很多团队来说只是柏拉图式的,要团队完全掌握运用,门槛太高,成本和风险太大。
2007-08-16 00:39 | RicCC      

#11楼   回复  引用    

我原来也一直在想怎么把这个DataSet变成DomainObjectSet,楼主的方法确实精妙,不过现在已经不用这种思想了。
2007-08-16 00:46 | RicCC      

#12楼   回复  引用  查看    

如果是map机制,还是ibatis好啊:)
2007-08-16 08:16 | 怀念家驹      

#13楼   

|

#14楼   回复  引用    

按轻量级定义,是个不错的东西啊,继续关注。
2007-08-16 08:56 | 活靶子[未注册用户]

#15楼   回复  引用  查看    

关注
2007-08-16 09:10 | 维生素C.NET      

#16楼   回复  引用  查看    

DataSet : 17439.1678
Record : 14019.9338
怎看也只是20%左右的差距(能到30%几呼已经是尽头了),那来1.2到2倍.
其实两种操作的Reader方式一样,只是DataTable比Array构造相对复杂一点因为他提供了array所没有的功能.所以能快上一点根本不用质疑的.
你说的优点真是看不出有什么优点...
2007-08-16 09:26 | henry      

#17楼   

|

#18楼   回复  引用  查看    

已认真读完全文。支持 Lostinet.
这个框架以实用为原则,是个很好的轻量级选择,包括 Where 条件使用 sql 语句片段都体现了“实用”的特点。
相比起 Castle.ActiveRecord 的低性能,和学习 HQL 语法的负担,我觉得是不是 OO 的那么纯粹,并不重要;简单好用更重要。

现在比较关注的是该框架对复杂查询的支持。
2007-08-16 10:03 | 木野狐(Neil Chen)      

#19楼   回复  引用  查看    

@流言社
表面上简单的时候往往丧失可定制性。当你要细节处理的时候会发现无从下手,痛苦不堪。

django 的 forms, newforms 就是这样的东西,结果是我从来不用它。
2007-08-16 10:05 | 木野狐(Neil Chen)      

#20楼   回复  引用    

尽管性能是否上好或者这是否逆潮流而动我都不确定。

但从头看到尾,我看到了楼主的思考过程和处理问题的方法,很不错,支持!

提个建议,希望楼主按优先级确定要加入的feature,小步快跑,保证质量别赶速度,个人觉得如果要用业余时间把上面列出的所有特性在国庆之前实现时间太紧张。

2007-08-16 11:58 | Ray Zhang      

#21楼   回复  引用  查看    

有自己想法的就是好东西,支持一下
2007-08-16 14:17 | jjx      

#22楼   回复  引用  查看    

有几个问题:
1.类的Property名和表字段名之间如何映射?是不是通过命名规则来映射?
是否可以支持用户增加Property?
2.使用时只需要定义抽象基类而不需定义具体类,那么这个框架通过什么方式来实现对应的具体类?
3.是否支持各种不同类型的数据库?通过什么方式来支持?
2007-08-16 14:27 | 东风31      

#23楼   

|

#24楼[楼主]   回复  引用  查看    

@RicCC
的确如果一个东西做得太复杂,那么反而很难普及.
或者说这对于大公司,有长远计划的,问题不大.
但对于中小公司,就不好应用复杂的东西了.

@怀念家驹
没有MAP机制. 所以我才不认为它是ORM.
也不提供SQL语句的动态配置.
也许AbstractRecord是能和ibatis整合的.

@anran
subsonic是完全生成所有代码了.
在代码的生成方面, 它比其他的生成工具要方便点.
目前我不太了解它是否能定制一些自定义的代码.


@活靶子
的确是轻量级的.
它也比较适合老系统的改进.

@henry

可以把4个测试所需要的时间拆分为:

Reader1 : 3831.84 SQL查询IO
Reader2 : 10645.5932 SQL查询IO + 分析数据包
DataSet : 17439.1678 SQL查询IO + 分析数据包 + 填充DataSet
Record : 14019.9338 SQL查询IO + 分析数据包 + 填充Record

这样就得出
填充DataSet = 6793.5746
填充Record = 3374.3406

可以看到,CSPAbstractRecord的实现所需要的时间, 是DataSet的 1/2.
这也是我说性能是DataSet的2倍的根据.

我认为无论是填充数据,还是读取属性,这个比例应该是一致的.

一个系统把数据从SQL读取一直到显示到界面上. 填充数据和读取属性都占很重的比例.
特别是在缓存的作用下, 读取属性的次数就会比查询数据库更多了.

如果一个对象读下来可以使用10次,
那个时候,总体的效果就变成 SQL查询IO + 分析数据包 + 填充 + 读取属性*10 .
那么效率比例就会很明显:
SQL查询IO + 分析数据包 + 填充DataSet + 读取属性*10 = 85374.9138
SQL查询IO + 分析数据包 + 填充Record + 读取属性*10 = 47763.3398
这个情况下,效率比例是179%


@木野狐(Neil Chen)
"实用"这个词的确能概括我的大体想法.

我做这个的目的,主要是为了实现下面的想法:

1. 强类型的定义,智能提示,自定义的数据验证,还可以当作文档用
2. 方便快速地加载一条或多条记录 , 方便高效地对数据进行分页.
3. 不再需要写那种长长的INSERT/UPDATE语句了. 对于初级的程序员,也免去SQL注入的风险.
4. 高效的更新方法. CSPAR能自己判断哪些字段需要更新.
5. 在内部实现缓存, 而不是由我自己手动去重复写缓存的代码.
6. 对象在内存中的状态管理,隔离,和对事务的支持. 当事务回滚不会造成缓存数据的错误.
7. 能迅速得到相关的数据. 在界面绑定中尤其重要. 例如 <%#Eval("Order.Customer.CompanyName")%>
8. 能对数据加以保护. 而不是在任何逻辑层都能自由地set任何属性.
9. 不排除我想做自己的轮子.

我做这个是完全从应用上考虑, 简单直接是最重要的. SQL语句本身不是坏事. 所以提供对SQL的支持.
标题写的 (ORM,ActiveRecord,DomainModel) 的用意纯粹是让别人能搜索到这个文章.


@Ray Zhang
CSPAbstractRecord的大部分功能已经完成了.
不然也不会有上面的性能测试代码.

现在不能发布,是因为产品还不能投入生产的应用.
而我也要先使用它来做一个产品,一方面可以省我时间,
令一方面可以作为对AR的测试.如果出现BUG我也会立刻更正.

@东风31
默认按名字查找,可以添加Internal作为前缀后缀. 这些能应付绝大部分的情况.
如果仍然需要改变名字, 则需要添加[CSPARFieldAttribute("FieldName")]
我考虑过Emit,但是它不支持web-trust-level=Medium的情况.
所以目前的实现方式, 是后台生成和编译C#代码.
按原理,AbstractRecord是可以跨数据库的.
但是CSPAbstractRecord是一个具体的实现,受制于CSP,只能用于SqlServer

@小老头
这个和ActiveRecord是不同的东西.
2007-08-16 14:45 | Lostinet      

#25楼   

|

#26楼   回复  引用  查看    

感觉任何声称能够比ADO.NET原生技术更快的不论ORM,DAL还是什么也好,感觉都是有问题的,还是NBear的比较老实,比ADO.NET慢多少就慢多少,直说,这样子的东西心里有底也才敢用。如果不启用缓存 NHibernate都要比ADO.NET原生技术慢上一倍,不管用没用EMit还是怎么的,内部还是用的ADO.NET,还要做那么多的判断配置的工作,最后还是要生成SQL和参数去用Command执行,要真的比ADO.NET还快了就真的是见鬼了
2007-08-16 15:31 | 亚历山大同志      

#27楼   回复  引用  查看    

@亚历山大同志
这个也太教条主义了,那么大个类库里面没有臃肿拙劣的痕迹才叫怪事
2007-08-16 15:42 | RicCC      

#28楼   回复  引用  查看    

SQL查询IO + 分析数据包 + 填充DataSet + 读取属性*10 = 85374.9138
SQL查询IO + 分析数据包 + 填充Record + 读取属性*10 = 47763.3398
这个情况下,效率比例是179%
如果楼主这么有信心就拿出同样数据ToArray和ToDataSet的达到2倍的结果给我看一下才是真的,我这个人对公式不感冒...
(因为我硬编码To entities也没试过达到比ToDataSet快50%)
2007-08-16 16:13 | henry      

#29楼   

|

#30楼   回复  引用  查看    

只靠填充list或array以及crud的orm性能测试是不全面的

请参考:
http://www.cnblogs.com/progame/archive/2007/07/29/orm_performance_unit_test2.html" target="_new">http://www.cnblogs.com/progame/archive/2007/07/29/orm_performance_unit_test2.html

顺便也可以测试你的orm的使用便捷性

最新的测试我在itemdetail和orderitem各加了一个attachment的附属对象 让关系更复杂了一步
2007-08-16 18:01 | progame      

#31楼   回复  引用  查看    

这东西和subsonic有什么区别?

感觉还没有subsonic封装的好!!!
2007-08-16 20:47 | xiao_p      

#32楼[楼主]   回复  引用  查看    

@shenloqi
AR会在运行时生成一个类似这样的实现:
public class Category_Impl : Category
{
internal byte[] __Picture;
protected override byte[] InternalPicture
{
get
{
return __Picture;
}
set
{
__Picture=value;
}
}
}
所以在Category.get_Picture中调用InternalPicture时,
是访问具体实现的字段. (而具体实现比上面代码要复杂点)


@亚历山大同志
ADO.NET也是分成几个部分的.
SqlClient是一部分, DataSet是另外一部分.
我的实现只是基于SqlClient, 所以在测试报告中, 远远不够Reader1/Reader2快.
但我的实现并不使用DataSet, 所以比DataSet快, 无论是理论上,还是实际测试, 都是正确的.


@henry
我知道你也是写ORM的. 我想如果你拿我上面测试的代码去改改,
是很容易得到一个介乎与 Reader2 : 10645.5932 - Record : 14019.9338 的结果.
例如如果你取得一个12900的结果, 那么可以认为硬编码的性能是DataSet的3倍.
如果你还是不明白, 请参考 shenloqi 的回复.


@progame
你的测试我以前就看过.
不过那时候我没有按你的测试去做.
因为作为发起人, 你应该提供一组DataSet实现的方案. 不然根本无法做参考.
那个时候我已经做了NBear那个方案的测试. 我一会会把测试结果放出来.

@xiao_p
subsonic只是个代码生成工具. 它的定制能力不高.
我放出的介绍, 是最简单的部分.
具体的实现有很多其他框架/类库比不上的特征的.

2007-08-17 14:20 | Lostinet      

#33楼   

|

#34楼   

|

#35楼[楼主]   回复  引用  查看    

@Jealous
对于-并发冲突-
目前采取CLR端的版本检查. 对于大多数情况,个人觉得足够了.
考虑增加SQL端的检查,(类似SqlDataAdapter,Linq),用于WebFarm和群集的情况.

事务方面,则支持回滚,回滚后,不会造成数据错误.
,和一定程度的事务中的CLR端的数据隔离.

@cat
采用的是生成CS代码的方式. 放到Web目录下的一个临时文件夹中.
调试的时候是可以调试进去的.
2007-08-19 11:50 | Lostinet      

#36楼   回复  引用  查看    

我想做的一个组件和博主的思想几乎相同... 包括存储过程实现的生成和CRUD操作...
但我的开发还处于早期阶段..
http://www.cnblogs.com/Dah/archive/2007/08/25/869081.html" target="_new">http://www.cnblogs.com/Dah/archive/2007/08/25/869081.html
2007-08-25 10:18 | Adrian.H      

#37楼   回复  引用  查看    

强烈期待啊
2007-08-27 16:09 | try      

#38楼   回复  引用  查看    

@某些人

看了你们的言论, 真有点后悔到博客园来混. 有事说事, 犯得着踩一脚么(无论你用了1个字还是一段篇幅)? 这是什么风气.

@双鱼座
还有这位, 你爱确信什么就确信什么, 犯不着在公众场合言之凿凿. 多数人就会玩两句XML, 会使点元数据, 能根据需要使下emit,就以为大家全上天了? 别人就廉颇老矣了? 微软出个Linq, 以后全部程序员就再也不需要看见SQL了?

去看看GoF里解释器那一章, 看看为什么我们需要有除了编程语言之外的另一种语言吧. 就国内外社区这股ORM风潮, 在很多级别的应用上根本是脱了裤子放屁. 将SQL复杂性的成本转接到另外一项学习任务上, 白白损失了性能, 什么顺潮流逆潮流, 不是潮流说了算, 是要解决什么问题说了算!

业务逻辑又与LZ这个东西有什么冲突了? 你完全可以把它视为ADO.NET上的一层罩, 主要是为了方便使用. 你的业务逻辑可以在其上封装, 你的查询逻辑也可以等LZ的架构设计完整之后自行扩展. 不适合你更可以不用, 何必把话说死.

多的不想说了, 我才是真正失望ing :(
2007-10-05 05:02 | 怪怪      

#39楼   回复  引用  查看    

引用:
还记得以前在ASP时代,虽然VB/JS是基于对象的, 但是那时做网页的编程, 基本不会去声明对应数据库的格式的类

呵呵

我在asp时代的时候,对于一些简单的增删改界面,我已经不写sql语句了,虽然没有对应数据库格式的类,不过我用其他方式实现了用户界面的文本框等控件与数据库字段的对应,而且数据传递在当时已经采用了类似于现在提的JSON的数据格式,而JSON这个格式的提出,是在人们应用AJAX之后,承认XML作为数据传送格式是一个失误之后,才开始使用的,呵呵

本身我的技术应该是一般的,所以我想,肯定有高手早就做到asp上的这些了,不过难度是有的,也没有用.net实现起来容易.
2007-12-15 09:58 | bingdian3721      



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 857039


相关文章:

相关链接: