天天看點

第七章 Caché 持久性對象介紹

文章目錄

  • 第七章 Caché 持久性對象介紹
  • 持久化類
  • 介紹預設的SQL映射
  • 儲存對象辨別符:ID和OID
    • 對象ID映射到SQL
  • SQL中的對象ID
  • 特定于持久類的類成員
    • 存儲定義
    • 索引
    • 外鍵
    • 觸發器
  • 其他類成員
  • 繼承管理
  • 繼承查詢
  • 附錄

第七章 Caché 持久性對象介紹

持久化類

持久類是繼承自%persistent的任何類。持久對象就是這樣一個類的執行個體。

%Persistent類是%RegisteredObject的子類,是以是一個對象類。除了提供前一章中描述的方法之外,%Persistent類還定義了持久性接口和一組方法。除其他外,這些方法能夠将對象儲存到資料庫、從資料庫加載對象、删除對象和測試是否存在。

介紹預設的SQL映射

對于任何持久性類,編譯器都會生成一個SQL表定義,以便除了通過本書中描述的對象接口之外,還可以通過SQL通路存儲的資料。

該表包含每個儲存對象的每一條記錄,可以通過 Caché SQL查詢該表。下面顯示了示例的查詢結果。Sample.Person 表:

第七章 Caché 持久性對象介紹

下表總結了預設的映射:

Object-SQL映射

From (Object Concept) … To (Relational Concept) …
Package Schema
Class Table
OID Identity field
Data type property Field
Reference property Reference field
Embedded object Set of fields
List property List field
Array property Child table
Stream property BLOB
Index Index
Class method Stored procedure

儲存對象辨別符:ID和OID

當第一次儲存一個對象時,Caché會為它建立兩個永久辨別符,可以使用其中一個來通路或删除儲存的對象。更常用的辨別符是對象ID。ID是表中惟一的值。預設情況下,Caché生成一個整數作為ID。

OID更加通用:它還包含類名,并且在資料庫中是惟一的。在一般實踐中,應用程式永遠不需要使用OID值;ID值通常就足夠了。

%Persistent類提供使用ID或OID的方法。在使用%OpenId()、%ExistsId()和%DeleteId()等方法時指定ID。将OID指定為方法的參數,如%Open()、%Exists()和%Delete()。也就是說,使用ID作為參數的方法在它們的名稱中包含ID。使用OID作為參數的方法的名稱中不包含Id;這些方法的使用頻率要低得多。

當持久對象存儲在資料庫中時,其任何引用屬性(即對其他持久對象的引用)的值都存儲為OID值。對于沒有oid的對象屬性,對象的文字值與對象的其他狀态一起存儲。

對象ID映射到SQL

對象的ID在對應的SQL表中可用。如果,Caché 使用字段名ID。如果不确定要使用哪個字段名,Caché 還提供了通路ID的方法。系統如下:

  • 對象ID不是對象的屬性,與屬性不同。
  • 如果類不包含名為ID的屬性,那麼表也包含字段ID,而該字段包含對象ID。
  • 如果類包含一個屬性,這個屬性用名稱ID(在任何情況下都是變體)映射到SQL,那麼表也包含字段ID1,這個字段包含對象ID的值。

類似地,如果類包含映射為ID和ID1的屬性,那麼表也包含ID2字段,該字段包含對象ID的值。

  • 在所有情況下,表還提供了僞字段%ID,其中儲存了對象ID的值。

OID在SQL表中不可用。

SQL中的對象ID

Caché 強制ID字段的唯一性(無論它的實際名稱是什麼)。Caché也防止更改此字段。這意味着不能在該字段上執行SQL更新或插入操作。

例如,下面顯示了向表添加新記錄所需的SQL:

INSERT INTO PERSON (FNAME, LNAME)VALUES (:fname, :lname)
           

注意,此SQL不引用ID字段。Caché 為ID字段生成一個值,并在建立請求的記錄時插入該值。

特定于持久類的類成員

Caché 類可以包含幾種隻有在持久類中才有意義的類成員。存儲過程、索引、外鍵和觸發器。 storage definitions, indices, foreign keys, and triggers.

存儲定義

在大多數情況下(如後面讨論的),每個持久類都有一個存儲定義。存儲定義的目的是描述Caché在為類儲存資料或為類讀取儲存的資料時使用的全局結構。在以編輯模式檢視類時,Studio将在類定義的末尾顯示存儲定義。以下是部分例子:

<Storage name="Default">
<Data name="PersonDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>SSN</Value>
</Value>
<Value name="4">
<Value>DOB</Value>
</Value>
<Value name="5">
<Value>Home</Value>
</Value>
<Value name="6">
<Value>Office</Value>
</Value>
<Value name="7">
<Value>Spouse</Value>
</Value>
<Value name="8">
<Value>FavoriteColors</Value>
</Value>
</Data>
<DataLocation>^Sample.PersonD</DataLocation>
<DefaultData>PersonDefaultData</DefaultData>
<ExtentSize>200</ExtentSize>
<IdLocation>^Sample.PersonD</IdLocation>
<IndexLocation>^Sample.PersonI</IndexLocation>
<Property name="%%CLASSNAME">
<Selectivity>50.0000%</Selectivity>
</Property>
...
           

在大多數情況下,編譯器也會生成和更新存儲定義。

索引

與其他SQL表一樣,Caché SQL表可以有索引;要定義這些,需要将索引定義添加到相應的類定義中。

索引可以添加限制,以確定給定字段或字段組合的唯一性。

索引的另一個用途是定義與類關聯的常用請求資料的特定排序子集,以便查詢可以更快地運作。例如,作為一般規則,如果一個查詢包含使用給定字段的WHERE子句,那麼如果該字段被索引,則查詢運作得更快。相反,如果該字段上沒有索引,則引擎必須執行一個完整的表掃描,檢查每一行,以檢視它是否符合給定的條件——如果表很大,這是一個耗時的操作。

外鍵

Caché SQL表也可以有外鍵。要定義這些,需要将外鍵定義添加到相應的類定義中。

外鍵在表之間建立引用完整性限制,Caché在添加新資料或更改資料時使用這些限制。如果使用的是關系,那麼Caché将自動将這些關系視為外鍵。但是,如果不想使用關系或者有其他原因需要添加他們,則可以添加外鍵。

觸發器

Caché SQL表也可以有觸發器。要定義這些,需要将觸發器定義添加到相應的類定義中。

觸發器定義在特定事件發生時自動執行的代碼,特别是在插入、修改或删除記錄時。

其他類成員

可以定義類方法或類查詢,以便将其作為存儲過程調用,能夠從SQL調用它。

對于本章沒有讨論的類成員,SQL沒有對應映射。也就是說,Caché 不提供直接的方式使用它們 從SQL或使它們從SQL可用的直接方法。

術語繼承是指磁盤上給定持久類的所有記錄。如下一章所示,%Persistent類提供了幾個對類繼承進行操作的方法。

  • 如果持久類Person擁有子類Employee,則Person繼承包括Person的所有執行個體和Employee的所有執行個體。
  • 對于類Employee的任何給定執行個體,該執行個體都包含在Person繼承和Employee繼承中。

索引自動跨越其定義的類的整個範圍。Person中定義的索引同時包含Person執行個體和Employee執行個體。在Employee繼承中定義的索引隻包含Employee執行個體。

子類可以定義父類中未定義的其他屬性。這些在子類範圍内可用,但在父類範圍内不可用。例如,Employee繼承可能包括Department字段,而人員繼承不包括該字段。

前面幾點意味着在Caché中編寫一個查詢來檢索相同類型的所有記錄相對容易。例如,如果希望統計所有類型的人員,可以對Person表運作查詢。如果隻想計算雇員數量,請對Employee 表運作相同的查詢。與其他對象資料庫相反,為了統計所有類型的人員,需要編寫一個更複雜的組合表的查詢,并且需要在添加另一個子類時更新這個查詢。

類似地,使用ID的方法都具有多态性。也就是說,它們可以根據傳遞的ID值對不同類型的對象進行操作。

例如,Sample.Person對象包括Sample.Person執行個體和Sample.Employee 執行個體。當調用 Sample.Person類的%OpenId()時,得到的OREF是Sample.Person或Sample.Employee執行個體,取決于是存儲在資料庫的是什麼:

/// d ##class(PHA.OP.MOB.Test).TestObjectID()
ClassMethod TestObjectID()
{
	 Set obj = ##class(Sample.Person).%OpenId(1)
	 Write $ClassName(obj),!  
	 Set obj = ##class(Sample.Person).%OpenId(2)
	 Write $ClassName(obj),!
}
           
DHC-APP>d ##class(PHA.OP.MOB.Test).TestObjectID()
Sample.Person
Sample.Employee
           

注意示例的%OpenId()方法。如果嘗試打開ID 1, Sample.Employee 類将不會傳回對象,因為ID 1不是Sample.Employee的繼承:

ClassMethod TestIsObject()
{
	Set obj = ##class(Sample.Employee).%OpenId(1)
	Write $IsObject(obj),! 
	Set obj = ##class(Sample.Employee).%OpenId(2)
	Write $IsObject(obj),!
}


           
DHC-APP>d ##class(PHA.OP.MOB.Test).TestIsObject()
0
1
           

繼承管理

對于使用預設存儲類(%Library.CacheStorage)的類,Caché維護繼承定義和那些繼承注冊到其繼承管理器中使用的全局變量。到繼承管理器的接口是通%ExtentMgr.Util實作的。這個注冊過程發生在類編譯期間。如果存在任何錯誤或名稱沖突,則會導緻編譯失敗。若要編譯成功,解決沖突;這通常涉及更改索引的名稱或添加資料的顯式存儲位置。

MANAGEDEXTENT類參數的預設值為1;此值将導緻全局名稱注冊和沖突使用檢查。值0指定既不進行注冊也不進行沖突檢查。

注意:如果一個應用程式有多個類有意共享一個全局引用,那麼對于所有相關的類指定MANAGEDEXTENT類參數的預設值為1為0(如果它們使用預設存儲)。否則,重新編譯将生成以下錯誤

ERROR #5564: Storage reference: '^This.App.Global used in 'User.ClassA.cls' 
is already registered for use by 'User.ClassB.cls'
           

要删除繼承中繼資料,有多種方法:

  • 使用 ##class(%ExtentMgr.Util).DeleteExtentDefinition(extent,extenttype)
  1. extent 通常是類名
  2. extenttype 是繼承類型
  3. 對于類,這是cls,它也是這個參數的預設值
  • 使用以下調用之一:
  1. $SYSTEM.OBJ.Delete(classname,flags) classname是要删除的類,flags 包含e
  2. $SYSTEM.OBJ.DeletePackage(packagename,flags) packagename 是要删除的包 ,flags 包含e
  3. $SYSTEM.OBJ.DeleteAll(flags) flags 包含e

繼承查詢

每個持久化類都會自動包含一個類查詢。稱為“範圍”,它提供範圍中的所有id的集合。稱為“繼承”,它提供繼承中所有id的集合。

有關使用類查詢的一般資訊,下面的示例使用一個類查詢來顯示示例的所有id。Sample.Person :

/// d ##class(PHA.OP.MOB.Test).TestExtentQueries()
ClassMethod TestExtentQueries()
{
	set query = ##class(%SQL.Statement).%New()
	set status= query.%PrepareClassQuery("Sample.Person","Extent")
	if 'status {
		do $system.OBJ.DisplayError(status)
	}
	set rset=query.%Execute()

	While (rset.%Next()) {
		Write rset.%Get("ID"),!
	}
}

           
DHC-APP> d ##class(PHA.OP.MOB.Test).TestExtentQueries()
1
2
 
           

Sample.Person 拓展包含 Sample.Person 的執行個體和它的子類

“extent”查詢相當于以下SQL查詢:

SELECT %ID FROM Sample.Person
           
INSERT INTO Sample.Person (Age,SSN,Name) VALUES (1,"3N1","yaoxin")
INSERT INTO Sample.Employee (Age,SSN,Name,Title,Salary) VALUES (30,"111-11-1111","xiaoli","test",2000)
           

附錄

/// This sample persistent class represents a person.
/// <p>Maintenance note: This class is used by some of the bindings samples.
Class Sample.Person Extends (%Persistent, %Populate, %XML.Adaptor)
{

Parameter EXTENTQUERYSPEC = "Name,SSN,Home.City,Home.State";

// define indices for this class

/// Define a unique index for <property>SSN</property>.
Index SSNKey On SSN [ Type = index, Unique ];

/// Define an index for <property>Name</property>.
Index NameIDX On Name [ Data = Name ];

/// Define an index for embedded object property <b>ZipCode</b>.
Index ZipCode On Home.Zip [ Type = bitmap ];

// define properties for this class

/// Person's name.
Property Name As %String(POPSPEC = "Name()") [ Required ];

/// Person's Social Security number. This is validated using pattern match.
Property SSN As %String(PATTERN = "3N1""-""2N1""-""4N") [ Required ];

/// Person's Date of Birth.
Property DOB As %Date(POPSPEC = "Date()");

/// Person's home address. This uses an embedded object.
Property Home As Address;

/// Person's office address. This uses an embedded object.
Property Office As Address;

/// Person's spouse. This is a reference to another persistent object.
Property Spouse As Person;

/// A collection of strings representing the person's favorite colors.
Property FavoriteColors As list Of %String(JAVATYPE = "java.util.List", POPSPEC = "ValueList("",Red,Orange,Yellow,Green,Blue,Purple,Black,White""):2");

/// Person's age.<br>
/// This is a calculated field whose value is derived from <property>DOB</property>.
Property Age As %Integer [ Calculated, SqlComputeCode = { Set {Age}=##class(Sample.Person).CurrentAge({DOB})
}, SqlComputed, SqlComputeOnChange = DOB ];

/// This class method calculates a current age given a date of birth <var>date</var>.
ClassMethod CurrentAge(date As %Date = "") As %Integer [ CodeMode = expression ]
{
$Select(date="":"",1:($ZD($H,8)-$ZD(date,8)\10000))
}

/// Prints the property <property>Name</property> to the console.
Method PrintPerson()
{
	Write !, "Name: ", ..Name
	Quit
}

/// A simple, sample method: add two numbers (<var>x</var> and <var>y</var>) 
/// and return the result.
Method Addition(x As %Integer = 1, y As %Integer = 1) As %Integer
{
	Quit x + y // comment
}

/// A simple, sample expression method: returns the value 99.
Method NinetyNine() As %Integer [ CodeMode = expression ]
{
99
}

/// Invoke the <method>PrintPerson</method> on all <class>Person</class> objects 
/// within the database.
ClassMethod PrintPersons()
{
	// use the extent result set to find all person
	Set extent = ##class(%ResultSet).%New("Sample.Person:Extent")
	Do extent.Execute()
	
	While (extent.Next()) {
		Set person = ..%OpenId(extent.GetData(1))
		Do person.PrintPerson()
	}
	
	Quit
}

/// Prints out data on all persons within the database using SQL to 
/// iterate over all the person data.
ClassMethod PrintPersonsSQL()
{
	// use dynamic SQL result set to find person data
	Set query = ##class(%ResultSet).%New("%DynamicQuery:SQL")
	Do query.Prepare("SELECT ID, Name, SSN FROM Sample.Person ORDER BY Name")
	Do query.Execute()
	
	While (query.Next()) {
		Write !,"Name: ", query.Get("Name"), ?30, query.Get("SSN")
	}
	
	Quit
}

/// This is a sample of how to define an SQL stored procedure using a 
/// class method. This method can be called as a stored procedure via 
/// ODBC or JDBC.<br>
/// In this case this method returns the concatenation of a string value.
ClassMethod StoredProcTest(name As %String, ByRef response As %String) As %Integer [ SqlName = Stored_Procedure_Test, SqlProc ]
{
	// Set response to the concatenation of name.
	Set response = name _ "||" _ name
	QUIT 29
}

/// This is a sample of how to define an SQL stored procedure using a 
/// class method. This method can be called as a stored procedure via 
/// ODBC or JDBC.<br>
/// This method performs an SQL update operation on the database 
/// using embedded SQL. The update modifies the embedded properties 
/// <var>Home.City</var> and <var>Home.State</var> for all rows whose 
/// <var>Home.Zip</var> is equal to <var>zip</var>.
ClassMethod UpdateProcTest(zip As %String, city As %String, state As %String) As %Integer [ SqlProc ]
{
	New %ROWCOUNT,%ROWID
	
	&sql(UPDATE Sample.Person 
	SET Home_City = :city, Home_State = :state 
	WHERE Home_Zip = :zip)
	
	// Return context information to client via %SQLProcContext object
	If ($g(%sqlcontext)'=$$$NULLOREF) { 
		Set %sqlcontext.SQLCode = SQLCODE
		Set %sqlcontext.RowCount = %ROWCOUNT
	}
	QUIT 1
}

/// A sample class query that defines a result set that returns Person data 
/// ordered by <property>Name</property>.<br>
/// This query can be used within another Cach&eacute; method (using the
/// <class>%ResultSet</class> class), from Java, or from ActiveX.<br>
/// This query is also accessible from ODBC and/or JDBC as the SQL stored procedure 
/// <b>SP_Sample_By_Name</b>.
Query ByName(name As %String = "") As %SQLQuery(CONTAINID = 1, SELECTMODE = "RUNTIME") [ SqlName = SP_Sample_By_Name, SqlProc ]
{
SELECT ID, Name, DOB, SSN
FROM Sample.Person
WHERE (Name %STARTSWITH :name)
ORDER BY Name
}

Storage Default
{
<Data name="PersonDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>SSN</Value>
</Value>
<Value name="4">
<Value>DOB</Value>
</Value>
<Value name="5">
<Value>Home</Value>
</Value>
<Value name="6">
<Value>Office</Value>
</Value>
<Value name="7">
<Value>Spouse</Value>
</Value>
<Value name="8">
<Value>FavoriteColors</Value>
</Value>
</Data>
<DataLocation>^Sample.PersonD</DataLocation>
<DefaultData>PersonDefaultData</DefaultData>
<ExtentSize>200</ExtentSize>
<IdLocation>^Sample.PersonD</IdLocation>
<IndexLocation>^Sample.PersonI</IndexLocation>
<Property name="%%CLASSNAME">
<AverageFieldSize>8.5</AverageFieldSize>
<Selectivity>50.0000%</Selectivity>
</Property>
<Property name="%%ID">
<AverageFieldSize>2.46</AverageFieldSize>
<Selectivity>1</Selectivity>
</Property>
<Property name="Age">
<AverageFieldSize>1.88</AverageFieldSize>
<Selectivity>1.1765%</Selectivity>
</Property>
<Property name="DOB">
<AverageFieldSize>5</AverageFieldSize>
<Selectivity>0.5000%</Selectivity>
</Property>
<Property name="FavoriteColors">
<AverageFieldSize>6.71</AverageFieldSize>
<OutlierSelectivity>.34:</OutlierSelectivity>
<Selectivity>1.4043%</Selectivity>
</Property>
<Property name="Home">
<AverageFieldSize>36.23,City:7.27,State:2,Street:16.58,Zip:5</AverageFieldSize>
<Selectivity>0.5000%,City:3.8462%,State:2.0408%,Street:0.5000%,Zip:0.5000%</Selectivity>
</Property>
<Property name="Name">
<AverageFieldSize>15.83</AverageFieldSize>
<Selectivity>0.5000%</Selectivity>
</Property>
<Property name="Office">
<AverageFieldSize>36.43,City:7.15,State:2,Street:16.91,Zip:5</AverageFieldSize>
<Selectivity>0.5000%,City:3.8462%,State:2.0408%,Street:0.5000%,Zip:0.5000%</Selectivity>
</Property>
<Property name="SSN">
<AverageFieldSize>11</AverageFieldSize>
<Selectivity>1</Selectivity>
</Property>
<Property name="Spouse">
<AverageFieldSize>.95</AverageFieldSize>
<OutlierSelectivity>.5:</OutlierSelectivity>
<Selectivity>0.7937%</Selectivity>
</Property>
<SQLMap name="$Person">
<BlockCount>-4</BlockCount>
</SQLMap>
<SQLMap name="IDKEY">
<BlockCount>-20</BlockCount>
</SQLMap>
<SQLMap name="NameIDX">
<BlockCount>-8</BlockCount>
</SQLMap>
<SQLMap name="SSNKey">
<BlockCount>-8</BlockCount>
</SQLMap>
<SQLMap name="ZipCode">
<BlockCount>-8</BlockCount>
</SQLMap>
<StreamLocation>^Sample.PersonS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

}

           
/// This sample persistent class represents an employee.<br>
Class Sample.Employee Extends Person
{

/// The employee's job title.
Property Title As %String(MAXLEN = 50, POPSPEC = "Title()");

/// The employee's current salary.
Property Salary As %Integer(MAXVAL = 100000, MINVAL = 0);

/// A character stream containing notes about this employee.
Property Notes As %Stream.GlobalCharacter;

/// A picture of the employee
Property Picture As %Stream.GlobalBinary;

/// The company this employee works for.
Relationship Company As Company [ Cardinality = one, Inverse = Employees ];

/// This method overrides the method in <class>Person</class>.<br>
/// Prints the properties <property>Name</property> and <property>Title</property> 
/// to the console.
Method PrintPerson()
{
	Write !,"Name: ", ..Name, ?30, "Title: ", ..Title
	Quit
}

/// writes a .png file containing the picture, if any, of this employee
/// the purpose of this method is to prove that Picture really contains an image
Method WritePicture()
{
	if (..Picture="") {quit}
	set name=$TR(..Name,".") ; strip off trailing period
	set name=$TR(name,", ","__") ; replace commas and spaces
	set filename=name_".png"
	
	set file=##class(%Stream.FileBinary).%New()
	set file.Filename=filename
	do file.CopyFrom(..Picture)
	do file.%Save()
	write !, "Generated file: "_filename
}

Storage Default
{
<Data name="EmployeeDefaultData">
<Subscript>"Employee"</Subscript>
<Value name="1">
<Value>Company</Value>
</Value>
<Value name="2">
<Value>Notes</Value>
</Value>
<Value name="3">
<Value>Salary</Value>
</Value>
<Value name="4">
<Value>Title</Value>
</Value>
<Value name="5">
<Value>Picture</Value>
</Value>
</Data>
<DefaultData>EmployeeDefaultData</DefaultData>
<ExtentSize>100</ExtentSize>
<Property name="%%CLASSNAME">
<AverageFieldSize>17</AverageFieldSize>
<Selectivity>100.0000%</Selectivity>
</Property>
<Property name="%%ID">
<AverageFieldSize>3</AverageFieldSize>
<Selectivity>1</Selectivity>
</Property>
<Property name="Age">
<AverageFieldSize>1.85</AverageFieldSize>
<Selectivity>1.5873%</Selectivity>
</Property>
<Property name="Company">
<AverageFieldSize>1.45</AverageFieldSize>
<Selectivity>5.0000%</Selectivity>
</Property>
<Property name="DOB">
<AverageFieldSize>5</AverageFieldSize>
<Selectivity>1.0000%</Selectivity>
</Property>
<Property name="FavoriteColors">
<AverageFieldSize>5.81</AverageFieldSize>
<OutlierSelectivity>.39:</OutlierSelectivity>
<Selectivity>2.2593%</Selectivity>
</Property>
<Property name="Home">
<AverageFieldSize>36.56,City:7.66,State:2,Street:16.5,Zip:5</AverageFieldSize>
<Selectivity>1.0000%,City:3.8462%,State:2.4390%,Street:1.0000%,Zip:1.0000%</Selectivity>
</Property>
<Property name="Name">
<AverageFieldSize>15.92</AverageFieldSize>
<Selectivity>1.0000%</Selectivity>
</Property>
<Property name="Notes">
<Selectivity>100.0000%</Selectivity>
</Property>
<Property name="Office">
<AverageFieldSize>36.83,City:7.22,State:2,Street:17.19,Zip:5</AverageFieldSize>
<Selectivity>1.0000%,City:4.0000%,State:2.1739%,Street:1.0000%,Zip:1.0000%</Selectivity>
</Property>
<Property name="Picture">
<Selectivity>100.0000%</Selectivity>
</Property>
<Property name="SSN">
<AverageFieldSize>11</AverageFieldSize>
<Selectivity>1</Selectivity>
</Property>
<Property name="Salary">
<AverageFieldSize>4.91</AverageFieldSize>
<Selectivity>1.0000%</Selectivity>
</Property>
<Property name="Spouse">
<AverageFieldSize>1.89</AverageFieldSize>
<Selectivity>1.5873%</Selectivity>
</Property>
<Property name="Title">
<AverageFieldSize>21.36</AverageFieldSize>
<Selectivity>1.5385%</Selectivity>
</Property>
<SQLMap name="$Employee">
<BlockCount>-4</BlockCount>
</SQLMap>
<Type>%Library.CacheStorage</Type>
}

}