WSORM:为WSBLog写的简单ORM

用户 wubuku 2006-12-30 7980

WSORM:为WSBLog写的简单ORM(一)

我曾经说过:我心目中的下一个版本的WSBLog,是要用ORM去做,并且,是专门写一个ORM,不需要多复杂,够用就好。

至于怎么才算够用?我的想法是:尽可能得减少WSBLog在数据访问方面的代码;在不修改WSBLog的代码的情况下,能将WSBLog无缝地移植到几个主流的数据库:Sql Server、Accesss、Oracle、MySql。

我将这个ORM暂且命名为:WSORM,可以理解为:WSBLog's Simple ORM——为WSBLog实现的简单ORM。呵呵。

我做这个东西的时候,没有使用过Hibernate、iBatis等任何一种产品,更谈不上参考他们的源代码了。我只是从我所知道的ORM的一些皮毛中想象出我要做的WSORM的样子。所以呢,使用ORM的行家估计看了要排砖了。嘿嘿。

目前的WSORM对Sql Server的支持已经基本实现了,当然,BUG是免不了的。代码也不算优雅,还是慢慢搞吧。

至于对Oracle的支持,实现了一部分。而且我相信,对于Access的实现起来应该会简单得多,在我看来,从支持Sql Server改到支持Access,要改动的代码很少。我打算下一步把对在Oracle上的实现先放一放,先做Access的再说。

感兴趣的朋友,可以到我的BLOG下载一下预览版本:http://www.wubuku.com


这份源代码可以编译成类库dll,也可以直接编译成一个可执行的测试程序。更改一下项目属性就可以了。

要运行测试程序,需要Sql Server上安装了Northwind样本数据库——这个我相信搞过Sql Server的都知道是什么东西吧?还有就是可能需要修改一下源代码中测试程序使用的数据库连接字符串。

这个WSORM怎么用呢?

就拿测试程序中的代码来说一下吧:


首先定义和Northwind中的订单表、订单明细表、产品信息表对应的几个类,然后定义一下这些类和数据表之间的影射关系。

订单类对应订单表Orders的Field成员和Property成员定义如下:


private int id;
//ID定义为Property
public int ID 
{
	get{ return this.id; }
	set{ this.id = value; }
}
//其他的字段定义为Field
public string CustomerID ;

public int EmployeeID ;

public DateTime OrderDate;

public DateTime RequiredDate;

public DateTime ShippedDate;

public int ShipVia;

public decimal Freight;

public string ShipName;

public string ShipAddress;

public string ShipCity;

public string ShipRegion;

public string ShipPostalCode;

public string ShipCountry;

//注意这个地方,定义为订单明细类对象的数组
public OrderDetail[] Details;



订单类Order和数据库中的订单表Orders之间的影射关系定义如下:

public static ObjectMap GetObjectMap()
{
	ObjectMap omap = new ObjectMap(typeof(Order),"Orders");

	omap.MemberMaps.Add("ID","OrderID", DbFieldTypes.Int, MemberMapTypes.Identity);//[OrderID] [int] IDENTITY (1, 1) NOT NULL ,
	omap.MemberMaps.Add("CustomerID", "CustomerID", DbFieldTypes.String, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("EmployeeID", "EmployeeID", DbFieldTypes.Int, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("OrderDate", "OrderDate", DbFieldTypes.DateTime, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("RequiredDate", "RequiredDate", DbFieldTypes.DateTime, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("ShippedDate", "ShippedDate", DbFieldTypes.DateTime, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("ShipVia", "ShipVia", DbFieldTypes.Int, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("Freight", "Freight", DbFieldTypes.Money, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("ShipName", "ShipName", DbFieldTypes.String, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("ShipAddress", "ShipAddress", DbFieldTypes.String, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("ShipCity", "ShipCity", DbFieldTypes.String, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("ShipRegion", "ShipRegion", DbFieldTypes.String, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("ShipPostalCode", "ShipPostalCode", DbFieldTypes.String, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("ShipCountry", "ShipCountry", DbFieldTypes.String, MemberMapTypes.NormalField);
	omap.MemberMaps.Add("Details","OrderID", DbFieldTypes.Int, typeof(OrderDetail), "OrderID", MemberMapTypes.One2Many);
	
	return omap;
}


订单明细类OrderDetail的成员定义如下:


public int OrderID;

public int ProductID;

public Decimal UnitPrice;

public int Quantity;

public float Discount; 

//注意这个地方,产品信息不是基本类型
public Product Product;



订单明细类OrderDetail和数据表Order Details的影射关系定义如下:

public static ObjectMap GetObjectMap()
{

	ObjectMap omap = new ObjectMap( typeof(OrderDetail),"Order Details");

	omap.MemberMaps.Add("OrderID", "OrderID", DbFieldTypes.Int, MemberMapTypes.PrimaryKey);

	//联合主键
	//	[ProductID] [int] NOT NULL ,
	omap.MemberMaps.Add("ProductID", "ProductID", DbFieldTypes.Int, MemberMapTypes.PrimaryKey);	//联合主键
	//	[UnitPrice] [money] NOT NULL ,
	omap.MemberMaps.Add("UnitPrice", "UnitPrice", DbFieldTypes.Money, MemberMapTypes.NormalField);

	omap.MemberMaps.Add("Quantity", "Quantity", DbFieldTypes.Int, MemberMapTypes.NormalField);

	omap.MemberMaps.Add("Discount", "Discount", DbFieldTypes.Real, MemberMapTypes.NormalField);
	//Many 2 One

	omap.MemberMaps.Add("Product", "ProductID", DbFieldTypes.Int, typeof(Product), "ProductID", MemberMapTypes.Many2One);
	return omap;
}



再看一下产品信息类以及它和数据表之间的影射关系:

public class Product
{

public static ObjectMap GetObjectMap()
{

	ObjectMap omap = new ObjectMap(typeof(Product),"Products");

	omap.MemberMaps.Add("ID", "ProductID", DbFieldTypes.Int, MemberMapTypes.Identity);

	omap.MemberMaps.Add("ProductName", "ProductName", DbFieldTypes.String, MemberMapTypes.NormalField);

	omap.MemberMaps.Add("SupplierID", "SupplierID", DbFieldTypes.Int, MemberMapTypes.NormalField);

	omap.MemberMaps.Add("CategoryID", "CategoryID", DbFieldTypes.Int, MemberMapTypes.NormalField);

	omap.MemberMaps.Add("QuantityPerUnit", "QuantityPerUnit", DbFieldTypes.String, MemberMapTypes.NormalField);

	omap.MemberMaps.Add("UnitPrice", "UnitPrice", DbFieldTypes.Money, MemberMapTypes.NormalField);

	omap.MemberMaps.Add("UnitsInStock", "UnitsInStock", DbFieldTypes.Int, MemberMapTypes.NormalField);

	omap.MemberMaps.Add("UnitsOnOrder", "UnitsOnOrder", DbFieldTypes.Int, MemberMapTypes.NormalField);

	omap.MemberMaps.Add("ReorderLevel", "ReorderLevel", DbFieldTypes.Int, MemberMapTypes.NormalField);

	omap.MemberMaps.Add("Discontinued", "Discontinued", DbFieldTypes.Boolean, MemberMapTypes.NormalField);
#if USE_VERSION_LOCK
	//如果需要使用版本控制,需要在Products表中添加一个Version字段,Product添加一个Version成员,然后添加以下字段影射
	omap.MemberMaps.Add("Version", "Version", DbFieldTypes.Int, MemberMapTypes.Version);
#endif
	return omap;
}


public int ID;

public string ProductName;

public int SupplierID;

public int CategoryID;

public string QuantityPerUnit;

public decimal UnitPrice;

public int UnitsInStock;

public int UnitsOnOrder;

public int ReorderLevel;

public bool Discontinued;

public int Version;

public Product()
{
	//构造函数
}

}


定义好这些“实体类”和影射信息后,怎么用呢?请看下一篇文章……
最新回复 (17)
全部楼主
  • 用户 wubuku 2006-12-30 0
    引用 2
    首先,定义一个SessionFactory,然后用定义好的影射信息初始化它:

    
    private SessionFactory northwindFactory = new SessionFactory();
    	
    private void initNorthwindFactory()
    {
    	this.northwindFactory.ConnectionString = 
    		@"Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=Northwind;Data Source=localhost";
    
    	ObjectMap omap = null;
    	omap = Order.GetObjectMap();
    	this.northwindFactory.ObjectMaps.Add(omap);
    	omap = OrderDetail.GetObjectMap();
    	this.northwindFactory.ObjectMaps.Add(omap);
    	omap = Product.GetObjectMap();
    	this.northwindFactory.ObjectMaps.Add(omap);
    }
    
    


    测试查询订单,执行的是下面的代码(我们把查询结果用XML格式输出到Debug中):

    
    private void queryOrders()
    {
    
    	ISession session = this.northwindFactory.CreateSession();
    	Order[] orders = (Order[])session.Query( typeof(Order),"ShipCountry in ('USA','CN') or ShipCountry='USA' order by ID desc",5 );
    
    	if ( orders != null )
    	{
    		foreach( Order order in orders )
    		{
    			System.Diagnostics.Debug.WriteLine( this.getXmlString(order) );
    		}
    
    	}
    	
    }
    
    private string getXmlString(object obj)
    {
    	System.Text.StringBuilder sb = new System.Text.StringBuilder();
    	System.IO.StringWriter writer = new System.IO.StringWriter(sb);
    
    	XmlSerializer  serializer = new XmlSerializer(obj.GetType());
    	serializer.Serialize(writer, obj);
    	return sb.ToString();
    }
    
    


    ——查询代码够简单的吧?呵呵。查询结果呢:

    <?xml version="1.0" encoding="utf-16"?>
    <Order xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <CustomerID>WHITC</CustomerID>
      <EmployeeID>5</EmployeeID>
      <OrderDate>2006-12-29T17:35:19.7130000+08:00</OrderDate>
      <RequiredDate>2007-01-08T17:35:19.7130000+08:00</RequiredDate>
      <ShippedDate>2006-12-30T17:35:19.7130000+08:00</ShippedDate>
      <ShipVia>1</ShipVia>
      <Freight>4.5600</Freight>
      <ShipName>White Clover Markets</ShipName>
      <ShipAddress>1029 - 12th Ave. S.</ShipAddress>
      <ShipCity>Seattle</ShipCity>
      <ShipRegion>WA</ShipRegion>
      <ShipPostalCode>98124</ShipPostalCode>
      <ShipCountry>USA</ShipCountry>
      <ID>11091</ID>
    </Order>
    <?xml version="1.0" encoding="utf-16"?>
    <Order xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <CustomerID>WHITC</CustomerID>
      <EmployeeID>5</EmployeeID>
      <OrderDate>2006-12-29T17:33:52.8670000+08:00</OrderDate>
      <RequiredDate>2007-01-08T17:33:52.8670000+08:00</RequiredDate>
      <ShippedDate>2006-12-30T17:33:52.8670000+08:00</ShippedDate>
      <ShipVia>1</ShipVia>
      <Freight>4.5600</Freight>
      <ShipName>White Clover Markets</ShipName>
      <ShipAddress>1029 - 12th Ave. S.</ShipAddress>
      <ShipCity>Seattle</ShipCity>
      <ShipRegion>WA</ShipRegion>
      <ShipPostalCode>98124</ShipPostalCode>
      <ShipCountry>USA</ShipCountry>
      <ID>11090</ID>
    </Order>
    <?xml version="1.0" encoding="utf-16"?>
    <Order xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <CustomerID>WHITC</CustomerID>
      <EmployeeID>5</EmployeeID>
      <OrderDate>2006-12-29T17:24:02.3800000+08:00</OrderDate>
      <RequiredDate>2007-01-08T17:24:02.3800000+08:00</RequiredDate>
      <ShippedDate>2006-12-30T17:24:02.3800000+08:00</ShippedDate>
      <ShipVia>1</ShipVia>
      <Freight>4.5600</Freight>
      <ShipName>White Clover Markets</ShipName>
      <ShipAddress>1029 - 12th Ave. S.</ShipAddress>
      <ShipCity>Seattle</ShipCity>
      <ShipRegion>WA</ShipRegion>
      <ShipPostalCode>98124</ShipPostalCode>
      <ShipCountry>USA</ShipCountry>
      <ID>11089</ID>
    </Order>
    <?xml version="1.0" encoding="utf-16"?>
    <Order xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <CustomerID>WHITC</CustomerID>
      <EmployeeID>5</EmployeeID>
      <OrderDate>2006-12-29T17:20:16.8230000+08:00</OrderDate>
      <RequiredDate>2007-01-08T17:20:16.8230000+08:00</RequiredDate>
      <ShippedDate>2006-12-30T17:20:16.8230000+08:00</ShippedDate>
      <ShipVia>1</ShipVia>
      <Freight>4.5600</Freight>
      <ShipName>White Clover Markets</ShipName>
      <ShipAddress>1029 - 12th Ave. S.</ShipAddress>
      <ShipCity>Seattle</ShipCity>
      <ShipRegion>WA</ShipRegion>
      <ShipPostalCode>98124</ShipPostalCode>
      <ShipCountry>USA</ShipCountry>
      <Details>
        <OrderDetail>
          <OrderID>11088</OrderID>
          <ProductID>102</ProductID>
          <UnitPrice>2.0000</UnitPrice>
          <Quantity>60</Quantity>
          <Discount>0.05</Discount>
          <Product>
            <ID>102</ID>
            <ProductName>Geitost test ---</ProductName>
            <SupplierID>15</SupplierID>
            <CategoryID>4</CategoryID>
            <QuantityPerUnit>500 g</QuantityPerUnit>
            <UnitPrice>2.5000</UnitPrice>
            <UnitsInStock>112</UnitsInStock>
            <UnitsOnOrder>0</UnitsOnOrder>
            <ReorderLevel>20</ReorderLevel>
            <Discontinued>false</Discontinued>
            <Version>0</Version>
          </Product>
        </OrderDetail>
        <OrderDetail>
          <OrderID>11088</OrderID>
          <ProductID>103</ProductID>
          <UnitPrice>27.8000</UnitPrice>
          <Quantity>20</Quantity>
          <Discount>0.05</Discount>
          <Product>
            <ID>103</ID>
            <ProductName>Mozzarella di Giovanni ----</ProductName>
            <SupplierID>14</SupplierID>
            <CategoryID>4</CategoryID>
            <QuantityPerUnit>24 - 200 g pkgs.</QuantityPerUnit>
            <UnitPrice>34.8000</UnitPrice>
            <UnitsInStock>14</UnitsInStock>
            <UnitsOnOrder>0</UnitsOnOrder>
            <ReorderLevel>0</ReorderLevel>
            <Discontinued>false</Discontinued>
            <Version>0</Version>
          </Product>
        </OrderDetail>
      </Details>
      <ID>11088</ID>
    </Order>
    <?xml version="1.0" encoding="utf-16"?>
    <Order xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <CustomerID>WHITC</CustomerID>
      <EmployeeID>5</EmployeeID>
      <OrderDate>2006-12-29T17:01:11.4470000+08:00</OrderDate>
      <RequiredDate>2007-01-08T17:01:11.4470000+08:00</RequiredDate>
      <ShippedDate>2006-12-30T17:01:11.4470000+08:00</ShippedDate>
      <ShipVia>1</ShipVia>
      <Freight>4.5600</Freight>
      <ShipName>White Clover Markets</ShipName>
      <ShipAddress>1029 - 12th Ave. S.</ShipAddress>
      <ShipCity>Seattle</ShipCity>
      <ShipRegion>WA</ShipRegion>
      <ShipPostalCode>98124</ShipPostalCode>
      <ShipCountry>USA</ShipCountry>
      <ID>11083</ID>
    </Order>
    


    ——可以看到,查询订单的时候,订单明细和产品信息也被级联查询出来了。下一篇文章我们看看怎么保存和删除数据……
  • 用户 wubuku 2006-12-30 0
    引用 3
    也许有的朋友要说:一个BLOG而已,需要搞个ORM出来么?

    呵呵,我想起某些狂热的OO信徒的口号:不OO,毋宁死!

    其实,我觉得,这个口号不如改为:不优雅,毋宁死!

    即使是一个简单的BLOG,我们也应该追求用优美的代码去实现它。

    不停地写sql代码,反正我是觉得很难看的……
  • 用户 wubuku 2006-12-31 0
    引用 4
    我们来看看WSORM怎么保存和删除一个对象。

    先看保存。我们假设有这样一个需求:我们需要新建一个订单,订单中有两条明细记录,每条明细记录“订”一种产品,该产品的产品信息在数据库中还不存在,需要新录入到数据库中。

    以下是用WSORM的实现代码:

    
    private void testSaveOrder()
    {
    
    	//新建一个订单
    	Order order = new Order();	
    
    	//订单信息
    	order.CustomerID = "WHITC";		
    	order.EmployeeID = 5;			
    	order.OrderDate = DateTime.Now;
    	order.RequiredDate = DateTime.Now.AddDays(10);
    	order.ShippedDate = DateTime.Now.AddDays(1);
    	order.ShipVia = 1;
    	order.Freight = 4.5600M;
    	order.ShipName = "White Clover Markets";
    	order.ShipAddress = "1029 - 12th Ave. S.";
    	order.ShipCity = "Seattle";
    	order.ShipRegion = "WA";
    	order.ShipPostalCode = "98124";
    	order.ShipCountry = "USA";
    
    	ArrayList orderdetails = new ArrayList();
    	OrderDetail detail = new OrderDetail();
    
    	//第一条订单明细记录
    	detail.UnitPrice = 2.0000M;
    	detail.Quantity = 60;
    	detail.Discount = 0.05F;
    	//第一条订单明细记录的产品信息
    	Product product = new Product();
    	product.ProductName = "Geitost test ---";
    	product.SupplierID = 15;
    	product.CategoryID = 4;
    	product.QuantityPerUnit = "500 g";
    	product.UnitPrice = 2.5000M;
    	product.UnitsInStock = 112;
    	product.UnitsOnOrder = 0;
    	product.ReorderLevel = 20;
    	product.Discontinued = false;
    	detail.Product = product;
    	orderdetails.Add(detail);
    
    	//第二条订单明细记录
    	detail = new OrderDetail();
    	detail.UnitPrice = 27.8000M;
    	detail.Quantity = 20;
    	detail.Discount = 0.05F;
    	//第二条订单明细记录的产品信息
    	product = new Product();
    	product.ProductName = "Mozzarella di Giovanni ----";
    	product.SupplierID = 14;
    	product.CategoryID = 4;
    	product.QuantityPerUnit = "24 - 200 g pkgs.";
    	product.UnitPrice = 34.8000M;
    	product.UnitsInStock = 14;
    	product.UnitsOnOrder = 0;
    	product.ReorderLevel = 0;
    	product.Discontinued = false;
    	detail.Product = product;
    	orderdetails.Add(detail);
    
    	order.Details = (OrderDetail[])orderdetails.ToArray(typeof(OrderDetail));
    
    	ISession session = this.northwindFactory.CreateSession();
    	
    	System.Diagnostics.Debug.WriteLine("============================================================");
    	session.Save(order);
    	session.Execute();
    	
    }
    
    


    代码非常简单,我们没有写一条Sql语句,WSORM能正确处理吗?

    我们运行一下测试程序,在调试状态下查看Debug的输出。Debug输出信息的前面的一部分是这样的:

    ============================================================
    insert into Orders (ShipName,ShippedDate,OrderDate,ShipCity,ShipAddress,ShipPostalCode,RequiredDate,Freight,EmployeeID,ShipVia,ShipRegion,ShipCountry,CustomerID) values (@ShipName,@ShippedDate,@OrderDate,@ShipCity,@ShipAddress,@ShipPostalCode,@RequiredDate,@Freight,@EmployeeID,@ShipVia,@ShipRegion,@ShipCountry,@CustomerID)
    ShipName	:	White Clover Markets
    ShippedDate	:	2007-1-1 0:32:25
    OrderDate	:	2006-12-31 0:32:25
    ShipCity	:	Seattle
    ShipAddress	:	1029 - 12th Ave. S.
    ShipPostalCode	:	98124
    RequiredDate	:	2007-1-10 0:32:25
    Freight	:	4.5600
    EmployeeID	:	5
    ShipVia	:	1
    ShipRegion	:	WA
    ShipCountry	:	USA
    CustomerID	:	WHITC
    
    


    ——可见WSORM自动生成了相应的insert的SQL语句,往订单表插入了一条订单信息。接下来的Debug输出信息:

    insert into Products (ReorderLevel,Discontinued,CategoryID,ProductName,QuantityPerUnit,UnitsInStock,UnitsOnOrder,UnitPrice,SupplierID) values (@ReorderLevel,@Discontinued,@CategoryID,@ProductName,@QuantityPerUnit,@UnitsInStock,@UnitsOnOrder,@UnitPrice,@SupplierID)
    ReorderLevel	:	20
    Discontinued	:	False
    CategoryID	:	4
    ProductName	:	Geitost test ---
    QuantityPerUnit	:	500 g
    UnitsInStock	:	112
    UnitsOnOrder	:	0
    UnitPrice	:	2.5000
    SupplierID	:	15
    
    


    ——WSORM往产品表中添加了一条产品记录。再接下来的Debug信息:

    insert into [Order Details] (ProductID,UnitPrice,OrderID,Quantity,Discount) values (@ProductID,@UnitPrice,@OrderID,@Quantity,@Discount)
    ProductID	:	107
    UnitPrice	:	2.0000
    OrderID	:	11094
    Quantity	:	60
    Discount	:	0.05
    


    ——WSORM往订单明细表中添加了一条订单明细记录。

    为什么WSORM会先保存产品信息,再保存订单明细信息呢?很简单,只有先保存产品信息,才能得到数据库给产品记录分配的ID。而产品明细记录需要这个ID。WSORM能自动处理这个情况。

    接下Debug的输出信息:

    insert into Products (ReorderLevel,Discontinued,CategoryID,ProductName,QuantityPerUnit,UnitsInStock,UnitsOnOrder,UnitPrice,SupplierID) values (@ReorderLevel,@Discontinued,@CategoryID,@ProductName,@QuantityPerUnit,@UnitsInStock,@UnitsOnOrder,@UnitPrice,@SupplierID)
    ReorderLevel	:	0
    Discontinued	:	False
    CategoryID	:	4
    ProductName	:	Mozzarella di Giovanni ----
    QuantityPerUnit	:	24 - 200 g pkgs.
    UnitsInStock	:	14
    UnitsOnOrder	:	0
    UnitPrice	:	34.8000
    SupplierID	:	14
    
    insert into [Order Details] (ProductID,UnitPrice,OrderID,Quantity,Discount) values (@ProductID,@UnitPrice,@OrderID,@Quantity,@Discount)
    ProductID	:	108
    UnitPrice	:	27.8000
    OrderID	:	11094
    Quantity	:	20
    Discount	:	0.05
    
    


    ——WSORM成功保存了另外一条产品记录和另外一条订单明细记录。


    我们把上面的方法修改一下,方法的最后加上这么几句代码:

    
    	//我们不对订单作任何修改,再保存一次看看:
    	System.Diagnostics.Debug.WriteLine("============================================================");
    	session.Save(order);
    	session.Execute();
    	
    	//删除保存过的订单
    	System.Diagnostics.Debug.WriteLine("============================================================");
    	session.Delete(order);
    	session.Execute();
    
    


    再执行一次看看,会有什么结果呢?我们下一篇文章再说……
  • 用户 wubuku 2006-12-31 0
    引用 5
    在上一篇文章中,我们测试了订单的保存。下面我们测试一下订单的删除。

    我们执行下面这样一段测试代码,这段代码先新建订单,保存两次,然后删除:

    private void testSaveOrder()
    {
    
    	//新建一个订单
    	Order order = new Order();	
    
    	//订单信息
    	order.CustomerID = "WHITC";		
    
    	//省略部分代码,请参考上一篇文章的testSaveOrder方法
    	//...
    
    	order.Details = (OrderDetail[])orderdetails.ToArray(typeof(OrderDetail));
    
    	ISession session = this.northwindFactory.CreateSession();
    
    	//第一次保存订单
    	System.Diagnostics.Debug.WriteLine("============================================================");
    	session.Save(order);
    	session.Execute();
    	
    	//我们不对订单作任何修改,再保存一次看看:
    	System.Diagnostics.Debug.WriteLine("============================================================");
    	session.Save(order);
    	session.Execute();
    	
    	//删除保存过的订单
    	System.Diagnostics.Debug.WriteLine("============================================================");
    	session.Delete(order);
    	session.Execute();
    
    }


    我们看看Debug的输出结果(以“//”开头的行是我添加的注解,不是Debug的输出):

    
    //省略部分输出信息,这些信息和上一篇文章中的testSaveOrder方法的输出信息相同
    //...
    
    insert into [Order Details] (ProductID,UnitPrice,OrderID,Quantity,Discount) values (@ProductID,@UnitPrice,@OrderID,@Quantity,@Discount)
    ProductID	:	108
    UnitPrice	:	27.8000
    OrderID	:	11094
    Quantity	:	20
    Discount	:	0.05
    
    //第一次保存订单完毕!
    ============================================================
    ============================================================
    delete from [Order Details] where ProductID=107 and OrderID=11094
    
    delete from [Order Details] where ProductID=108 and OrderID=11094
    
    delete from Orders where OrderID=11094
    
    


    ——我们看到,在第一次保存订单信息完毕后,Debug连续输出了两条分隔线。检查测试代码,我们可以发现:第一条分隔线是第二次保存订单前输出的,第二条分隔线是在删除订单前输出的。也就是说,WSORM只保存了一次订单,因为我们没有对订单做任何修改,WSORM不需要保存两次订单。

    最后,WSORM执行了三条delete语句,先删除了两条订单明细,然后删除了整个订单。

    为什么WSORM没有自动删除产品信息呢?因为订单明细记录和产品信息记录是一种“多对一”的关系,就业务上来说,“多对一”中“一”的一方,往往是一种基础数据,所以WSORM在实现的时候,对“多对一”的关系不作级联删除。

    如果你想要删除产品信息,需要显式得编写删除产品信息的代码。
  • 用户 wubuku 2006-12-31 0
    引用 6
    难道我写得太烂?没有人回应。郁闷啊……😓
  • 用户 wubuku 2006-12-31 0
    引用 7
    我们说说数据库的并发控制问题,WSORM通过版本控制实现了乐观锁。

    它的实现原理是什么样的呢?大家可以用“ORM 乐观锁”google一下,应该可以找到相应的文章。这里举例简述一下:

    某银行系统的数据库中有一张“帐户表”,里面保存了“帐户余额”记录,我们在这个表中设计了一个整形的“Version”字段。

    在读取帐户余额时,我们把版本号“Version”字段一同读取出来。在更新帐户余额时,比对刚才“已经读取出来的版本号”与“数据库中的最新版本号”,如果读取出来的版本号等于数据库中的版本号,则予以更新,否则报错。

    举例说,A客户在银行柜台上从某帐户中提款,银行柜台的系统客户端读取账户余额为1000元,并连带读取出记录版本号为5;恰好此时,B客户在某地自动柜员机上也从同一帐户中取款,读取账号余额1000元,版本号也为5。

    A客户取款500元后,账户余额应被更新为500。A客户这边的银行柜台系统客户端“在更新帐户余额的同时检查数据库中的记录版本号”,如果版本号仍为5,则将版本号加1,把版本号变更为6,同时更新帐户余额信息为500。

    我们假设A客户这边首先取款成功,看看接下来B客户这边系统客户端的处理情况:

    B客户领款后也要变更数据库,B客户这边的自动柜员机系统所取得的帐户余额记录其版本号为5,但“更新帐户记录的同时检查数据库中的记录版本号”,发现其已经被更新为6,放弃更新数据库。自动柜员机系统可以提示B客户取款失败,请客户重新进行取款操作。

    怎么样才能在“在更新帐户余额的同时检查数据库中的记录版本号”,这里面有一个巧妙的方法,把“Version=已读取的版本号”作为更新条件对记录进行更新,如果更新影响的行数为0,则说明读取的版本已经过期,更新失败。

    我们看看WSORM是怎么做的。

    首先,我们在Northwind数据库的产品信息Products表中增加一个整形字段Version,不允许为Null,默认值为0。

    然后修改类Product的定义,保证已经添加了Version这个Field,并且建立了和数据库中Version字段的映射信息:

    public class Product
    {
    
    	public static ObjectMap GetObjectMap()
    	{
    		ObjectMap omap = new ObjectMap(typeof(Product),"Products");
    		omap.MemberMaps.Add("ID", "ProductID", DbFieldTypes.Int, MemberMapTypes.Identity);
    		omap.MemberMaps.Add("ProductName", "ProductName", DbFieldTypes.String, MemberMapTypes.NormalField);
    		omap.MemberMaps.Add("SupplierID", "SupplierID", DbFieldTypes.Int, MemberMapTypes.NormalField);
    		omap.MemberMaps.Add("CategoryID", "CategoryID", DbFieldTypes.Int, MemberMapTypes.NormalField);
    		omap.MemberMaps.Add("QuantityPerUnit", "QuantityPerUnit", DbFieldTypes.String, MemberMapTypes.NormalField);
    		omap.MemberMaps.Add("UnitPrice", "UnitPrice", DbFieldTypes.Money, MemberMapTypes.NormalField);
    		omap.MemberMaps.Add("UnitsInStock", "UnitsInStock", DbFieldTypes.Int, MemberMapTypes.NormalField);
    		omap.MemberMaps.Add("UnitsOnOrder", "UnitsOnOrder", DbFieldTypes.Int, MemberMapTypes.NormalField);
    		omap.MemberMaps.Add("ReorderLevel", "ReorderLevel", DbFieldTypes.Int, MemberMapTypes.NormalField);
    		omap.MemberMaps.Add("Discontinued", "Discontinued", DbFieldTypes.Boolean, MemberMapTypes.NormalField);
    
    		//如果需要使用版本控制,需要在Products表中添加一个Version字段,Product添加一个Version成员,然后添加以下字段影射
    		omap.MemberMaps.Add("Version", "Version", DbFieldTypes.Int, MemberMapTypes.Version);
    
    		return omap;
    	}
    
    	public int ID;
    
    	public string ProductName;
    
    	public int SupplierID;
    
    	public int CategoryID;
    
    	public string QuantityPerUnit;
    
    	public decimal UnitPrice;
    
    	public int UnitsInStock;
    
    	public int UnitsOnOrder;
    
    	public int ReorderLevel;
    
    	public bool Discontinued;
    	
    	public int Version;
    
    	public Product()
    	{
    	}
    }
    



    我们写一个下面这样的测试函数,并且在代码中设置一个断点(见注释):

    private void saveAndDeleteProductInfo()
    {
    
    	ISession session = this.northwindFactory.CreateSession();
    	Product product = new Product();
    	product.ProductName = "Outback Lager test--";
    	product.SupplierID = 7;
    	product.CategoryID = 1;
    	product.QuantityPerUnit = "24 - 355 ml bottles";
    	product.UnitPrice = 15.0000M;
    	product.UnitsInStock = 15;
    	product.UnitsOnOrder = 10;
    	product.ReorderLevel = 30;
    	product.Discontinued = false;
    
    	System.Diagnostics.Debug.WriteLine("==================================================");
    	session.Save(product);
    	session.Execute();
    	
    	//注意,我们在下一句设置一个断点!
    	product.ProductName = "Outback Lager test----";
    	System.Diagnostics.Debug.WriteLine("==================================================");
    	session.Save(product);
    	session.Execute();
    	
    	session.Delete(product);
    	session.Execute();
    }
    
    


    执行到断点处,Debug的输出如下:

    insert into Products (ReorderLevel,Discontinued,CategoryID,ProductName,QuantityPerUnit,UnitsInStock,UnitsOnOrder,Version,UnitPrice,SupplierID) values (@ReorderLevel,@Discontinued,@CategoryID,@ProductName,@QuantityPerUnit,@UnitsInStock,@UnitsOnOrder,@Version,@UnitPrice,@SupplierID)
    ReorderLevel	:	30
    Discontinued	:	False
    CategoryID	:	1
    ProductName	:	Outback Lager test--
    QuantityPerUnit	:	24 - 355 ml bottles
    UnitsInStock	:	15
    UnitsOnOrder	:	10
    Version	:	0
    UnitPrice	:	15.0000
    SupplierID	:	7
    


    我们在断点处停住,马上去检查一下数据库的Products表,发现记录确实已经成功添加到表中。

    我们手工把新插入的记录的Version字段的值改为1。

    继续执行代码,程序会抛出异常,Debug的输出如下:

    ==================================================
    update Products set ProductName=@ProductName,Version=@Version where ProductID=110 and Version=0
    ProductName	:	Outback Lager test----
    Version	:	1
    未处理的“com.oucsoft.WSORM.WSORMException”类型的异常出现在 WSORM.exe 中。
    


    ——我们可以看到,WSBLog是将“where ProductID=110 and Version=0”作为update的条件。一旦发现update影响的行数为0,WSBLog就会回滚数据库事务,并且向上抛出异常。
  • 离退版主 yangkui 2006-12-31 0
    引用 8
    厉害

  • 用户 wubuku 2007-1-2 0
    引用 9
    YangKui的谬赞俺可不敢当。ORM其实很多了,我只是手太痒,非要自己费神费力去自己弄一个而已。

    也许有人说,搞个Blog而已,需要费那么大劲搞个什么WSORM出来么?也许WSORM的实现确实不是那么漂亮,但是我们来看看,即使做一个简单的Blog,ORM到底有没有它的价值,到底ORM是把WSBLog的实现搞复杂了还是搞简单了?

    我设想的WSBLog是支持分级的树形目录的,关于树形目录的样子,可以参考一下我这个测试中的WSBLog网站:http://www.66ph.com

    题外话,这个测试BLOG有13万篇文章,由于目前程序中没有使用任何缓存优化机制,速度有点慢。我对WSBLog的目标呢,是做一个快速、稳定、多用户的BLOG,关于这些想法,可以看看我的BLOG上的相关文章:

    http://www.wubuku.com/post/125.aspx
    http://www.wubuku.com/post/34.aspx

    言归正传,目录类TCategory、数据库中的目录表blog_Category、以及它们之间的映射关系如下:

    
    Public Class TCategory
        Inherits EntityBase
    
        Public Shared Function GetObjectMap() As ObjectMap
    
            'blog目录表blog_Category的建表脚本
    
            'CREATE TABLE [dbo].[blog_Category] (
            '	[cate_ID] [int] IDENTITY (1, 1) NOT NULL ,
            '	[cate_Name] [nvarchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
            '	[cate_Order] [nvarchar] (10) COLLATE Chinese_PRC_CI_AS NULL ,
            '	[cate_Intro] [nvarchar] (255) COLLATE Chinese_PRC_CI_AS NULL ,
            '	[cate_Count] [int] NOT NULL ,
            '	[cate_Code] [nvarchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
            '	[cate_ParentCode] [nvarchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
            '	[cate_ParentID] [int] NULL 
            ') ON [PRIMARY]
    
            Dim omap As ObjectMap = New ObjectMap(GetType(TCategory), "blog_Category")
    
            omap.MemberMaps.Add("ID", "cate_ID", DbFieldTypes.Int, MemberMapTypes.Identity)
            omap.MemberMaps.Add("Name", "cate_Name", DbFieldTypes.String, MemberMapTypes.NormalField)
            omap.MemberMaps.Add("Order", "cate_Order", DbFieldTypes.String, MemberMapTypes.NormalField)
            omap.MemberMaps.Add("Intro", "cate_Intro", DbFieldTypes.String, MemberMapTypes.NormalField)
            omap.MemberMaps.Add("Count", "cate_Count", DbFieldTypes.Int, MemberMapTypes.NormalField)
            omap.MemberMaps.Add("Code", "cate_Code", DbFieldTypes.String, MemberMapTypes.NormalField)
            omap.MemberMaps.Add("ParentCode", "cate_ParentCode", DbFieldTypes.String, MemberMapTypes.NormalField)
            omap.MemberMaps.Add("ParentID", "cate_ParentID", DbFieldTypes.Int, MemberMapTypes.NormalField)
    
            '目录之间的父子关系是通过这句关联起来的
            omap.MemberMaps.Add("Childs", "cate_ID", DbFieldTypes.Int, GetType(TCategory), "cate_ParentID", MemberMapTypes.One2Many)
    
            Return omap
    
        End Function
    
    
        Public ID As Integer'目录的ID
    
        Public Code As String'目录的代码
    
        Public Name As String
    
        Public Intro As String
    
        Public Order As String 'Public Order As Integer
    
        Public Count As Integer
       
        Public ParentID As Integer'父级目录的ID
    
        Public ParentCode As String'父级目录的代码
    
        Public Childs() As TCategory
    
        Public ReadOnly Property Url() As String
            Get
                Url = BLOG_HOST & "Catalog.aspx?" & "cate=" & ID
            End Get
        End Property
    
    End Class
    
    



    题外话,为了便于人的识读和查询,上面的数据表在设计上留有一些冗余字段。

    我们看看使用了WSORM后,代码怎么写:

    
        Dim sessionFactory As com.oucsoft.WSORM.SessionFactory = New com.oucsoft.WSORM.SessionFactory
        sessionFactory.FactoryType = WSORM.SessionFactoryTypes.SqlServer'设置生成的Session的类型
        sessionFactory.ConnectionString = Me.ConnectionString'设置连接字符串
        sessionFactory.ObjectMaps.Add(TCategory.GetObjectMap())'添加映射关系
    
        '取得树形的目录信息
        Public Function GetTreeViewCategorys() As TCategory()
    
            Dim session As com.oucsoft.WSORM.ISession = sessionFactory.CreateSession()
            '只要查询顶级目录,就会级联取得所有子目录的信息
            Return CType(session.Query(GetType(TCategory), "(ParentID IS NULL OR ParentID=0) ORDER BY Code"), TCategory())
    
        End Function
    
    


    ——除去映射信息的设置(SessionFactory是整个BLOG程序里公用的,只需要设置一次),取得所有目录树的代码其实只有两行,一切就是这么简单。可见,ORM的引入对于优化BLOG程序的代码结构还是很有好处的。

    为了在html页面上生成树形目录,我们希望输出类似这样的html代码:

    
    <li class="Closed"><a href="http://localhost/WSBLog/Catalog.aspx?cate=1">农、林、牧、渔业 (14)</a> <a href="http://localhost/WSBLog/Rss.aspx?cate=1" target="_blank"> <img src="http://localhost/WSBLog/IMAGE/LOGO/rss.png" border="0" alt="rss"></a>
        <ul>
        <li class="Closed"><a href="http://localhost/WSBLog/Catalog.aspx?cate=2">农业 (3)</a> <a href="http://localhost/WSBLog/Rss.aspx?cate=2" target="_blank"> <img src="http://localhost/WSBLog/IMAGE/LOGO/rss.png" border="0" alt="rss"></a>
            <ul>
            <li class="Child"><a href="http://localhost/WSBLog/Catalog.aspx?cate=3">谷物及其他作物的种植 (0)</a> <a href="http://localhost/WSBLog/Rss.aspx?cate=3" target="_blank"> <img src="http://localhost/WSBLog/IMAGE/LOGO/rss.png" border="0" alt="rss"></a></li><li class="Child"><a href="http://localhost/WSBLog/Catalog.aspx?cate=13">蔬菜、园艺作物的种植 (0)</a> <a href="http://localhost/WSBLog/Rss.aspx?cate=13" target="_blank"> <img src="http://localhost/WSBLog/IMAGE/LOGO/rss.png" border="0" alt="rss"></a></li><li class="Child"><a href="http://localhost/WSBLog/Catalog.aspx?cate=17">水果、坚果、饮料和香料作物的种植 (0)</a> <a href="http://localhost/WSBLog/Rss.aspx?cate=17" target="_blank"> <img src="http://localhost/WSBLog/IMAGE/LOGO/rss.png" border="0" alt="rss"></a></li><li class="Child"><a href="http://localhost/WSBLog/Catalog.aspx?cate=1200">中药材的种植 (0)</a> <a href="http://localhost/WSBLog/Rss.aspx?cate=1200" target="_blank"> <img src="http://localhost/WSBLog/IMAGE/LOGO/rss.png" border="0" alt="rss"></a></li>
            </ul>
        </li>
        <li class="Closed"><a href="http://localhost/WSBLog/Catalog.aspx?cate=22">林业 (0)</a> 
    <a href="http://localhost/WSBLog/Rss.aspx?cate=22" target="_blank"> <img src="http://localhost/WSBLog/IMAGE/LOGO/rss.png" border="0" alt="rss"></a>
            <ul>
            <li class="Child"><a href="http://localhost/WSBLog/Catalog.aspx?cate=23">林木的培育和种植 (0)</a> <a href="http://localhost/WSBLog/Rss.aspx?cate=23" target="_blank"> <img src="http://localhost/WSBLog/IMAGE/LOGO/rss.png" border="0" alt="rss"></a></li><li class="Child"><a href="http://localhost/WSBLog/Catalog.aspx?cate=27">木材和竹材的采运 (0)</a> <a href="http://localhost/WSBLog/Rss.aspx?cate=27" target="_blank"> <img src="http://localhost/WSBLog/IMAGE/LOGO/rss.png" border="0" alt="rss"></a></li><li class="Child"><a href="http://localhost/WSBLog/Catalog.aspx?cate=1202">林产品的采集 (0)</a> <a href="http://localhost/WSBLog/Rss.aspx?cate=1202" target="_blank"> <img src="http://localhost/WSBLog/IMAGE/LOGO/rss.png" border="0" alt="rss"></a></li>
            </ul>
        </li>
        </ul>
    </li>
    
    


    至于输出Html代码,这样写就是了:

    
        'Blog目录索引重新时,会执行这个方法
        Public Sub BlogReBuild_Catalogs()
    
            Dim strCatalog As String = ""
    
            Dim cates As TCategory() = GetTreeViewCategorys()
            Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder
            If Not IsNothing(cates) Then
                For Each cate As TCategory In cates
                    exportHtmlCategoryTree(cate, sb)
                Next
            End If
    
            strCatalog = TransferHTML(sb.ToString(), "[no-asp]")
    
            Call SaveToFile(System.IO.Path.Combine(BlogPath, "INCLUDE/catalog.asp"), strCatalog, "utf-8")
    
        End Sub
    
        '输出一颗目录树的html代码
        Private Sub exportHtmlCategoryTree(ByVal cate As TCategory, ByRef sb As System.Text.StringBuilder)
    
            Dim strClass As String = "Closed"
            Dim hasChilds As Boolean = True
    
            If cate.Childs Is Nothing Then
                hasChilds = False
            Else
                If cate.Childs.Length = 0 Then
                    hasChilds = False
                End If
            End If
            If Not hasChilds Then
                strClass = "Child"
            End If
    
            sb.Append("<li class=""")
            sb.Append(strClass)
            sb.Append(""">")
            sb.Append("<a href=""" & cate.Url & """>" + cate.Name + " (" & cate.Count & ")" + "</a>")
            sb.Append(" <a href=""" & BLOG_HOST & "Rss.aspx?cate=" & cate.ID.ToString() & """ target=""_blank"">")
            sb.Append(" <img src=""" & BLOG_HOST & "IMAGE/LOGO/rss.png"" border=""0"" alt=""rss""></a>")
    
            If hasChilds Then
    
                sb.Append("<ul>")
                For Each child As TCategory In cate.Childs
                    '递归调用,输出子目录的Html代码
                    exportHtmlCategoryTree(child, sb)
                Next
                sb.Append("</ul>")
    
            End If
    
            sb.Append("</li>")
    
        End Sub
    
    


    当然,仅仅有了html代码还是不够的,可能还需要少量JavaScript和CSS的支持。网上讲怎么在Web中通过JavaScript和CSS实现树形目录的文章还是蛮多的,所以,这里就不多说了。大家可以google一下,或者可以参考这篇文章:

    http://www.cnlei.org/mycode/CNLTreeMenu/Ver1.0.2/index.html
  • 用户 evernory 2007-1-10 0
    引用 10
    不懂啊,。。不懂。
  • 用户 chq1860 2007-2-11 0
    引用 11
    很神奇
  • 离退版主 Zx.MYS 2007-2-12 0
    引用 12
    谁来给我解释一下。ORM是什么?(别砸我,自己架BLOG才1个月不到)

    [AD]:个人BLOG:思想碎片@Zx.MYS
    [说明]:1.有事请发帖,论坛短消息已禁用。2.写Z-Blog程序的不是我,是另外一个zx。

  • 用户 danieldu2008 2010-10-6 0
    引用 13
    这个要学习下

    淘宝皇冠店淘宝好店

  • 用户 mengqiu 2010-10-6 0
    引用 14
    一头雾水:dizzy:

    BB霜 眼霜排行榜

  • 用户 714032303 2010-10-6 0
    引用 15
    开发组的都不懂,我也不懂............

    抠着鼻子看帖子...

  • 用户 somin 2010-10-8 0
    引用 16
    不清楚是什么哦···
  • 用户 38huo 2011-5-30 0
    引用 17
    看不懂也 http://www.38huo.cn
  • 用户 郝帅 2011-7-11 0
    引用 18
    看不懂,路过。。。

    赤尔商贸

    • ZBlogger技术交流中心
      19
          
返回