今天是九月二十六日

早上阳光明媚啊,六点钟醒来,然后祥睡,然后七点再次醒来,提醒自己,今天可以骑车上班.
于是骑行上班,八点就到公司了,很快啊.路过西二旗桥下时,看见一开捷达的司机在别一骑电动车的,然后下来就骂,操你Y的,老子XXXXX,云云.唉,很凶的样子,我只是茫然的路过,有些义愤而已,仅此而已.
最近这一个星期基本都狂加班,很是郁闷.手下的才毕业的小弟和我反映被压迫.呵呵,昨天和同事讨论这个问题,资本家都是黑心的主啊,不压迫我们,他们哪里赚钱啊.突然发现应届毕业生都是弱势群体,你不加班?那就是不努力,不上进.想想原来的才参加工的我,的确如此.你清华,交大又能如何?乖乖接受压榨吧,你不干活,自有很多愿意干活的人.
你是接受过高等教育的,要是光叫你写代码,不用上大学也能写,所以你要加班多写,你不多写怎么体现你和高中生的区别嘛.这就是资本家忽悠的经典台词啊.呵呵,我当然多写,不过我写的大部分应该和公司的项目无关啊.当然,你公司安排的任务我肯定保证按时完成,但是要我超负荷的加班帮你写,加薪水吧,否则我还是回家写自己的去,个人兴趣所在,当然不会是为了工作才写代码的.
于是乎发现老员工们没有奖金和加班工资的话,基本都不加班,想起刚参加工作带我的那个PM,唉,就TM一垃圾,装B不说,还觉得自己牛B,问个东西爱搭不搭的(不会你就说撒,会的不说怎么装B啊),自己懒得干的活都丢给下面.唉,所以当初离职的时候辞职报告都没给他看,办完手续才和他88,那Y的还在装B,云里雾里的,慢装不送.
很多人活得都很挣扎,好比朝九晚六的上班族,又好比今天早上开车上班的小资一族.看似光鲜淋漓,实则空洞,无奈.其实完全可以不用那么挣扎,那么痛苦,麻木一些就好,但是欲望啊,一个人的欲望是能够满足的么?算了,地球的生活留给了我们太多的无奈,无法改变.看来有朝一日我还得回火星去.
豆瓣的和BLOG留言板很有意思,我现在养成习惯了,看见一个人的地址,先进去看相册,美女的Blog总是像个小论坛一样火爆,不排除我这种网络游人的猎奇心理,总希望来上那么一段,,,省略若干.唉,伤啊.
最近空余时间泡豆瓣,发现有逐渐被90后侵占的势头,总是很多茫然,无奈,无聊,发傻,发神经,还是看我的<史记>去.
今天花了三百大元定了一套秋冬用骑行服,希望国庆之前能到,要不然我骑车去天津吃包子的愿望又要流产了.
全篇看了下,其实我自己也很装”B”.

SharePoint里面的Search结构框架

最好的说明是能够让你很快就明白的说明在说的是什么东西的东西,不论他是按照图片还是声音,还是文字。大多数时候我看英文翻译过来的中文书的感觉,就是大家读本文的第一句话所感受到的,所以更多的时候还是看英文原文吧,虽然N多的单词都是在猜意思,总比被中文绕得不知所云的好。
言归正传,最近小看了下SharePoint所提供的爬网服务的构架,感觉很好很强大,很容易操作和理解,听说新版本的企业级的搜索要当作独立的产品模块来卖,功能更加强大和YY,其实07里面的这些企业级别的功能就已经是按模块来收费的了,不是很好很强大,也许是大多应用都做的不深的缘故吧,挂羊头卖狗肉的事情还是很多了。当然,很多时候也是出于无奈。
13913589041324
企业级别的爬网包括了两大引擎,一个是查询引擎,一个是索引引擎。辅之以协议加工器,筛选器,内容索引,属性存储,搜索相关的配置数据,以及传说中的词法拆解器。当然,这些术语都是胡乱翻译过来的,具体代表什么,还得在应用中才能有深刻的体会,如果有经验的人士,看个构架图,应该也能看到个30%。
爬网,大多时候都是在没有人知道的情况下悄悄进行的,搜索引擎利用事先定义好的规则,创建一堆一堆的管道,从网络和各种数据源中检索有用的资源,并且创建快照和索引,数据筛选器负责对数据进行析取筛选,协议解析器对讲述来的数据进行包装加工,变成一堆堆分门别类的有组织有结构有分类的索引和快照,以方便数据的快速检索和定位,大海捞针,抽其纲要,吸其神髓,概览之,细节突破。
在爬网的过程当中,索引引擎把文档和文档的关键属性分离存储,文档的属性重新按照属性值分类存储,文档则分散存储,为了加速常用的查询,一些简单的全字符查询的索引会直接的存储下来。实际的内容项会存储在内容索引当中,用于内容查询。属性的分离存储同样可以用来维护和加强文档在爬网时的安全级别控制。
对于搜索关键字的拆解,爬完引擎专门提供了一个拆解器,他可以根据对应的语言类别来拆解关键字。这些都是大家在用google里面很常用的技术。
检索的过程说起来很简单,就是按照大家所提供的关键字,从索引里面迅速的检索出匹配的信息,当然,这个筛选匹配的过程就比较复杂了,不过我们有属性和索引在,所以对于信息的筛选和检索,没问题,二次校验,应该不会出错。
对于爬网数据的来源,可以从图上看出,
可以是如下的几种类型:
外部网站,
sp的网站,
exchange邮件服务器,
业务数据目录,
共享文件夹,
和自定义的内容。
10290355794349
企业级别搜索的schema有两种类型的属性组成,一种是爬网的属性,一种是托管的属性,他们之间有一定的映射关系。当爬网内容的时候,索引器从内容项目中萃取爬网的属性,这些属性根据他们的分类进行分组,分类的过程是有筛选器和协议加工器来完成的。
托管属性是一个个的用户搜索经验的集合,里面都是由爬网属性值组成的,所以必须有和他对应的文档属性的映射,所有的托管属性都是由共享服务提供程序管理和创建的。
爬网属性默认的有一下几种分类:
HTML
Lotus Notes
PDF
XML
Office
Exchange
People
Portal
SharePoint
如果想用编程的对象模型来看,没问题,看如下的代码:

try
{
string strURL = @"http://XXXX/ssp/admin/";
SearchContext context;
using (SPSite m_site = new SPSite(strURL))
{
context = SearchContext.GetContext(m_site);
}
Schema m_Schema = new Schema(context);
//Schema siteSchema = new Schema(SearchContext.GetContext("SharedServices1"));
//Schema sspSchema = new Schema(SearchContext.GetContext(new SPSite(strURL)));
ManagedPropertyCollection managedCategories = m_Schema.AllManagedProperties;
if (managedCategories.Count > 0)
{
foreach (ManagedProperty mp in managedCategories)
{
Console.WriteLine("托管的属性名称为: " + mp.Name.ToString());
Console.WriteLine("id为:" + mp.ID + "描述是: " + mp.Description.ToString());
Console.WriteLine("-------------");
}
}
CategoryCollection categories = sspSchema.AllCategories;
if (categories.Count > 0)
{
foreach (Category cat in categories)
{
Console.WriteLine(cat.Name.ToString());
foreach (CrawledProperty cpro in cat.GetAllCrawledProperties())
{
Console.WriteLine("爬网属性名称为: " + cpro.Name.ToString());
Console.WriteLine("id为:" + cpro.Propset + "变量类型是: " + cpro.VariantType.ToString());
Console.WriteLine("-------------");
}
}
}
}
catch (Exception ex1)
{
Console.WriteLine(ex1.ToString());
}
static void CreateMappingBetweenCrawledProAndManagePro(CrawledProperty crapro, ManagedProperty manpro, SearchContext context)
{
try
{
Mapping maping = new Mapping(crapro.Propset, crapro.Name, crapro.VariantType, manpro.PID);
MappingCollection mappingColect = manpro.GetMappings();
mappingColect.Add(maping);
manpro.SetMappings(mappingColect);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message.ToString());
}
}

对于映射的创建,主要思路就是创建一个mapping,然后添加到托管属性的映射集合中,然后更新托管属性的映射集合。
简要的说了那么多,其实这里只是一个抛砖引玉的说明而已,里面涉及到的东西远不止这些,在moss里面提供的东西,大多都可以轻点鼠标完成,但是对于深层次应用,还是需要施以对象模型来加以实现,所以对SDK的研读,是很有裨益的。我们的口号是有事就找SDK。

用DataSetSurrogate加速WebService―我承认我孤陋寡闻

这两天在WebService用的多,而且数据都是一堆一堆的在DataSet里面的,一个DataSet里面有时有四五个表,不免让人晕眩,原来经常打到实体里面,然后用IList返回,自认为是比较快的办法了,但是要写一堆的实体文件,虽然有现成的映射代码生成器可以用,原来也用CodeSmith写过快速生成的模板,但是总觉得不爽,而且对于用户量在一百人的小系统,的确有些小题大做。于是乎寻觅到了一个加速的办法,一看代码,老外2003年就写了,感叹,我这2008年才用。原来也有一些大鸟级别的人用过,而且用得都很绚丽,不得不承认自己井底之蛙。
   下面归入正题:众所周知,DataSet里面存放的实际上就是一堆的xml,当然,光说xml的文件宽泛了,实际上是用Xml描述的DataTable, DataColumn,, DataSet。在没有动作以前,DataSet里面的东西都是XmlSchema,当我们在WebService里面调用的时候,那么一堆的XmlSchema带着数据,就一起在网络上传输了,这样拖家带口的,肯定就慢,而且还有不少的冗余信息,所以在老外经过对DataSet里面进行解剖以后,就发现了一个比较有效的方法,分离。
   至于分离,主要分离的是Schema和data,这样拆开来做,不仅减少了数据的冗余,而且提高了传输的效率,打成流的东西,想不快都难。谁叫网络上传输的都是0和1。当然,里面也要注意还原性,有些网站上给例子,竟然把序列化以后的流给toArray()转化,试想,序列化以后的东西如果进行了转化,那么数据进行了填充,本来用八位表示一个一的,转化后用十六位表示了,那传输过去,反序列化肯定失败,因为本质变了,数据被拆分填充了,再怎么变肯定也变不回来了,当然,你bt一点还是可以变回来,但是你要付出一个怎样的代价,也就不好估计了。所以说,对于序列化后的在流里面的东西,你最好别动。
   还有一个要注意的地方是在分离和重组的时候,Table的强制性约束和只读属性一定要撤了,读完或者写完在加上,要不然你就只有抱着异常去哭了。
  好了,这样就把该搞定的搞定了,最后加个二进制序列化器,打成流就可以了,然后就开始传输精神和肉体分离的数据了,到接收端再反序列化,然后进行组装,这样的话就很ok了,微软的demo里面有个程序可以马上进行测试,速度的确快了不少。我现在也贴点源码,感觉对DataSet,DataTable的结构理解还是很有帮助的。至于序列化的,
  
  下面是两个方法的调用,一个是序列化发,一个是反序列化收的。

  public byte[] GetBinaryFormatData(DataSet m_ds)
   {
   byte[] binaryDataResult = null;
   MemoryStream memStream = new MemoryStream();
   IFormatter m_formatter = new BinaryFormatter();
  
   DataSetSurrogate dss = new DataSetSurrogate(m_ds);
  
   m_formatter.Serialize(memStream, dss);
   memStream.Write(binaryDataResult, 0, memStream.Length);
   memStream.Close();
   memStream.Dispose();
  
   return binaryDataResult;
   }
  
   public DataSet RetrieveDataSet(byte[] binaryData)
   {
   DataSet dataSetResult=null;
   DataSetSurrogate dataSurrogateSet = null;
   MemoryStream memStream = new MemoryStream(binaryData);
   IFormatter brFormatter = new BinaryFormatter();
  
   object obj = brFormatter.Deserialize(memStream);
   dataSurrogateSet = obj as DataSetSurrogate;
  
   dataSurrogateSet.ReadDataIntoDataSet(dataSetResult);
   return dataSetResult;
   }

  最后附上类的实现代码,注:是个老外03年写的,牛人啊。

  ――――――――
  using System;
  using System.IO;
  using System.ComponentModel;
  using System.Collections;
  using System.Globalization;
  using System.Xml;
  using System.Data;
  using System.Runtime.Serialization;
  using System.Diagnostics;
  
  /*
   Author : Ravinder Vuppula.
   Purpose : To implement binary serialization of the DataSet through a Surrogate object.
   Notes:
   1. All the surrogate objects DataSetSurrogate, DataTableSurrogate, DataColumnSurrogate are marked [Serializable] and hence will get automatically serialized by the remoting framework.
   2. The data is serialized in binary "column" wise.
   3. This class can be used as a wrapper around DataSet. A DataSetSurrogate object can be constructed from DataSet and vice-versa. This helps if the user wants to wrap the DataSet in DataSetSurrogate and serialize and deserialize DataSetSurrogate instead.
   History:
   05/10/04 - Fix for the issue of serializing default values.
  */
  
  [Serializable]
  public class DataSetSurrogate {
   //DataSet properties
   private string _datasetName;
   private string _namespace;
   private string _prefix;
   private bool _caseSensitive;
   private CultureInfo _locale;
   private bool _enforceConstraints;
  
   //ForeignKeyConstraints
   private ArrayList _fkConstraints;//An ArrayList of foreign key constraints : [constraintName]->[parentTableIndex, parentcolumnIndexes]->[childTableIndex, childColumnIndexes]->[AcceptRejectRule, UpdateRule, Delete]->[extendedProperties]
  
   //Relations
   private ArrayList _relations;//An ArrayList of foreign key constraints : [relationName]->[parentTableIndex, parentcolumnIndexes]->[childTableIndex, childColumnIndexes]->[Nested]->[extendedProperties]
  
   //ExtendedProperties
   private Hashtable _extendedProperties;
  
   //Columns and Rows
   private DataTableSurrogate[] _dataTableSurrogates;
  
  /*
   Constructs a DataSetSurrogate object from a DataSet.
  */
   public DataSetSurrogate(DataSet ds) {
   if (ds == null) {
   throw new ArgumentNullException("The parameter dataset is null");
   }
  
   //DataSet properties
   _datasetName = ds.DataSetName;
   _namespace = ds.Namespace;
   _prefix = ds.Prefix;
   _caseSensitive = ds.CaseSensitive;
   _locale = ds.Locale;
   _enforceConstraints = ds.EnforceConstraints;
  
   //Tables, Columns, Rows
   _dataTableSurrogates = new DataTableSurrogate[ds.Tables.Count];
   for (int i = 0; i < ds.tables.count;="" i++)="" {="">
   _dataTableSurrogates[i] = new DataTableSurrogate(ds.Tables[i]);
   }
  
   //ForeignKeyConstraints
   _fkConstraints = GetForeignKeyConstraints(ds);
  
   //Relations
   _relations = GetRelations(ds);
  
   //ExtendedProperties
   _extendedProperties = new Hashtable();
   if (ds.ExtendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in ds.ExtendedProperties.Keys) {
   _extendedProperties.Add(propertyKey, ds.ExtendedProperties[propertyKey]);
   }
   }
   }
  
  /*
   Constructs a DataSet from the DataSetSurrogate object. This can be used after the user recieves a Surrogate object over the wire and wished to construct a DataSet from it.
  */
   public DataSet ConvertToDataSet() {
   DataSet ds = new DataSet();
   ReadSchemaIntoDataSet(ds);
   ReadDataIntoDataSet(ds);
   return ds;
   }
  
  /*
   Reads the schema into the dataset from the DataSetSurrogate object.
  */
   public void ReadSchemaIntoDataSet(DataSet ds) {
   if (ds == null) {
   throw new ArgumentNullException("The dataset parameter cannot be null");
   }
  
   //DataSet properties
   ds.DataSetName = _datasetName;
   ds.Namespace = _namespace;
   ds.Prefix = _prefix;
   ds.CaseSensitive = _caseSensitive;
   ds.Locale = _locale;
   ds.EnforceConstraints = _enforceConstraints;
  
   //Tables, Columns
   Debug.Assert(_dataTableSurrogates != null);
   foreach (DataTableSurrogate dataTableSurrogate in _dataTableSurrogates) {
   DataTable dt = new DataTable();
   dataTableSurrogate.ReadSchemaIntoDataTable(dt);
   ds.Tables.Add(dt);
   }
  
   //ForeignKeyConstraints
   SetForeignKeyConstraints(ds, _fkConstraints);
  
   //Relations
   SetRelations(ds, _relations);
  
   //Set ExpressionColumns
   Debug.Assert(_dataTableSurrogates != null);
   Debug.Assert(ds.Tables.Count == _dataTableSurrogates.Length);
   for (int i = 0; i < ds.tables.count;="" i++)="">
   DataTable dt = ds.Tables[i];
   DataTableSurrogate dataTableSurrogate = _dataTableSurrogates[i];
   dataTableSurrogate.SetColumnExpressions(dt);
   }
  
   //ExtendedProperties
   Debug.Assert(_extendedProperties != null);
   if (_extendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in _extendedProperties.Keys) {
   ds.ExtendedProperties.Add(propertyKey, _extendedProperties[propertyKey]);
   }
   }
   }
  
  /*
   Reads the data into the dataset from the DataSetSurrogate object.
  */
   public void ReadDataIntoDataSet(DataSet ds) {
   if (ds == null) {
   throw new ArgumentNullException("The dataset parameter cannot be null");
   }
  
   //Suppress read-only columns and constraint rules when loading the data
   ArrayList readOnlyList = SuppressReadOnly(ds);
   ArrayList constraintRulesList = SuppressConstraintRules(ds);
  
   //Rows
   Debug.Assert(IsSchemaIdentical(ds));
   Debug.Assert(_dataTableSurrogates != null);
   Debug.Assert(ds.Tables.Count == _dataTableSurrogates.Length);
   bool enforceConstraints = ds.EnforceConstraints;
   ds.EnforceConstraints = false;
   for (int i = 0; i < ds.tables.count;="" i++)="">
   DataTable dt = ds.Tables[i];
   DataTableSurrogate dataTableSurrogate = _dataTableSurrogates[i];
   dataTableSurrogate.ReadDataIntoDataTable(ds.Tables[i], false);
   }
   ds.EnforceConstraints = enforceConstraints;
  
   //Reset read-only columns and constraint rules back after loading the data
   ResetReadOnly(ds, readOnlyList);
   ResetConstraintRules(ds, constraintRulesList);
   }
  
  /*
   Gets foreignkey constraints availabe on the tables in the dataset.
   ***Serialized foreign key constraints format : [constraintName]->[parentTableIndex, parentcolumnIndexes]->[childTableIndex, childColumnIndexes]->[AcceptRejectRule, UpdateRule, Delete]->[extendedProperties]***
  */
   private ArrayList GetForeignKeyConstraints(DataSet ds) {
   Debug.Assert(ds != null);
  
   ArrayList constraintList = new ArrayList();
   for (int i = 0; i < ds.tables.count;="" i++)="">
   DataTable dt = ds.Tables[i];
   for (int j = 0; j < dt.constraints.count;="" j++)="">
   Constraint c = dt.Constraints[j];
   ForeignKeyConstraint fk = c as ForeignKeyConstraint;
   if (fk != null) {
   string constraintName = c.ConstraintName;
   int[] parentInfo = new int[fk.RelatedColumns.Length + 1];
   parentInfo[0] = ds.Tables.IndexOf(fk.RelatedTable);
   for (int k = 1; k < parentinfo.length;="" k++)="">
   parentInfo[k] = fk.RelatedColumns[k - 1].Ordinal;
   }
  
   int[] childInfo = new int[fk.Columns.Length + 1];
   childInfo[0] = i;//Since the constraint is on the current table, this is the child table.
   for (int k = 1; k < childinfo.length;="" k++)="">
   childInfo[k] = fk.Columns[k - 1].Ordinal;
   }
  
   ArrayList list = new ArrayList();
   list.Add(constraintName);
   list.Add(parentInfo);
   list.Add(childInfo);
   list.Add(new int[] { (int) fk.AcceptRejectRule, (int) fk.UpdateRule, (int) fk.DeleteRule });
   Hashtable extendedProperties = new Hashtable();
   if (fk.ExtendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in fk.ExtendedProperties.Keys) {
   extendedProperties.Add(propertyKey, fk.ExtendedProperties[propertyKey]);
   }
   }
   list.Add(extendedProperties);
  
   constraintList.Add(list);
   }
   }
   }
   return constraintList;
   }
  
  /*
   Adds foreignkey constraints to the tables in the dataset. The arraylist contains the serialized format of the foreignkey constraints.
   ***Deserialize the foreign key constraints format : [constraintName]->[parentTableIndex, parentcolumnIndexes]->[childTableIndex, childColumnIndexes]->[AcceptRejectRule, UpdateRule, Delete]->[extendedProperties]***
  */
   private void SetForeignKeyConstraints(DataSet ds, ArrayList constraintList) {
   Debug.Assert(ds != null);
   Debug.Assert(constraintList != null);
  
   foreach (ArrayList list in constraintList) {
   Debug.Assert(list.Count == 5);
   string constraintName = (string) list[0];
   int[] parentInfo = (int[]) list[1];
   int[] childInfo = (int[]) list[2];
   int[] rules = (int[]) list[3];
   Hashtable extendedProperties = (Hashtable) list[4];
  
   //ParentKey Columns.
   Debug.Assert(parentInfo.Length >= 1);
   DataColumn[] parentkeyColumns = new DataColumn[parentInfo.Length - 1];
   for (int i = 0; i < parentkeycolumns.length;="" i++)="">
   Debug.Assert(ds.Tables.Count > parentInfo[0]);
   Debug.Assert(ds.Tables[parentInfo[0]].Columns.Count > parentInfo[i + 1]);
   parentkeyColumns[i] = ds.Tables[parentInfo[0]].Columns[parentInfo[i + 1]];
   }
  
   //ChildKey Columns.
   Debug.Assert(childInfo.Length >= 1);
   DataColumn[] childkeyColumns = new DataColumn[childInfo.Length - 1];
   for (int i = 0; i < childkeycolumns.length;="" i++)="">
   Debug.Assert(ds.Tables.Count > childInfo[0]);
   Debug.Assert(ds.Tables[childInfo[0]].Columns.Count > childInfo[i + 1]);
   childkeyColumns[i] = ds.Tables[childInfo[0]].Columns[childInfo[i + 1]];
   }
  
   //Create the Constraint.
   ForeignKeyConstraint fk = new ForeignKeyConstraint(constraintName, parentkeyColumns, childkeyColumns);
   Debug.Assert(rules.Length == 3);
   fk.AcceptRejectRule = (AcceptRejectRule) rules[0];
   fk.UpdateRule = (Rule) rules[1];
   fk.DeleteRule = (Rule) rules[2];
  
   //Extended Properties.
   Debug.Assert(extendedProperties != null);
   if (extendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in extendedProperties.Keys) {
   fk.ExtendedProperties.Add(propertyKey, extendedProperties[propertyKey]);
   }
   }
  
   //Add the constraint to the child datatable.
   Debug.Assert(ds.Tables.Count > childInfo[0]);
   ds.Tables[childInfo[0]].Constraints.Add(fk);
   }
   }
  
  /*
   Gets relations from the dataset.
   ***Serialized relations format : [relationName]->[parentTableIndex, parentcolumnIndexes]->[childTableIndex, childColumnIndexes]->[Nested]->[extendedProperties]***
  */
   private ArrayList GetRelations(DataSet ds) {
   Debug.Assert(ds != null);
  
   ArrayList relationList = new ArrayList();
   foreach (DataRelation rel in ds.Relations) {
   string relationName = rel.RelationName;
   int[] parentInfo = new int[rel.ParentColumns.Length + 1];
   parentInfo[0] = ds.Tables.IndexOf(rel.ParentTable);
   for (int j = 1; j < parentinfo.length;="" j++)="">
   parentInfo[j] = rel.ParentColumns[j - 1].Ordinal;
   }
  
   int[] childInfo = new int[rel.ChildColumns.Length + 1];
   childInfo[0] = ds.Tables.IndexOf(rel.ChildTable);
   for (int j = 1; j < childinfo.length;="" j++)="">
   childInfo[j] = rel.ChildColumns[j - 1].Ordinal;
   }
  
   ArrayList list = new ArrayList();
   list.Add(relationName);
   list.Add(parentInfo);
   list.Add(childInfo);
   list.Add(rel.Nested);
   Hashtable extendedProperties = new Hashtable();
   if (rel.ExtendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in rel.ExtendedProperties.Keys) {
   extendedProperties.Add(propertyKey, rel.ExtendedProperties[propertyKey]);
   }
   }
   list.Add(extendedProperties);
  
   relationList.Add(list);
   }
   return relationList;
   }
  
  /*
   Adds relations to the dataset. The arraylist contains the serialized format of the relations.
   ***Deserialize the relations format : [relationName]->[parentTableIndex, parentcolumnIndexes]->[childTableIndex, childColumnIndexes]->[Nested]->[extendedProperties]***
  */
   private void SetRelations(DataSet ds, ArrayList relationList) {
   Debug.Assert(ds != null);
   Debug.Assert(relationList != null);
  
   foreach (ArrayList list in relationList) {
   Debug.Assert(list.Count == 5);
   string relationName = (string) list[0];
   int[] parentInfo = (int[]) list[1];
   int[] childInfo = (int[]) list[2];
   bool isNested = (bool) list[3];
   Hashtable extendedProperties = (Hashtable) list[4];
  
   //ParentKey Columns.
   Debug.Assert(parentInfo.Length >= 1);
   DataColumn[] parentkeyColumns = new DataColumn[parentInfo.Length - 1];
   for (int i = 0; i < parentkeycolumns.length;="" i++)="">
   Debug.Assert(ds.Tables.Count > parentInfo[0]);
   Debug.Assert(ds.Tables[parentInfo[0]].Columns.Count > parentInfo[i + 1]);
   parentkeyColumns[i] = ds.Tables[parentInfo[0]].Columns[parentInfo[i + 1]];
   }
  
   //ChildKey Columns.
   Debug.Assert(childInfo.Length >= 1);
   DataColumn[] childkeyColumns = new DataColumn[childInfo.Length - 1];
   for (int i = 0; i < childkeycolumns.length;="" i++)="">
   Debug.Assert(ds.Tables.Count > childInfo[0]);
   Debug.Assert(ds.Tables[childInfo[0]].Columns.Count > childInfo[i + 1]);
   childkeyColumns[i] = ds.Tables[childInfo[0]].Columns[childInfo[i + 1]];
   }
  
   //Create the Relation, without any constraints[Assumption: The constraints are added earlier than the relations]
   DataRelation rel = new DataRelation(relationName, parentkeyColumns, childkeyColumns, false);
   rel.Nested = isNested;
  
   //Extended Properties.
   Debug.Assert(extendedProperties != null);
   if (extendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in extendedProperties.Keys) {
   rel.ExtendedProperties.Add(propertyKey, extendedProperties[propertyKey]);
   }
   }
  
   //Add the relations to the dataset.
   ds.Relations.Add(rel);
   }
   }
  
  /*
   Suppress the read-only property and returns an arraylist of read-only columns.
  */
   private ArrayList SuppressReadOnly(DataSet ds) {
   Debug.Assert(ds != null);
  
   ArrayList readOnlyList = new ArrayList();
   for (int i = 0; i < ds.tables.count;="" i++)="">
   DataTable dt = ds.Tables[i];
   for (int j = 0; j < dt.columns.count;="" j++)="">
   if (dt.Columns[j].Expression == String.Empty && dt.Columns[j].ReadOnly == true) {
   dt.Columns[j].ReadOnly = false;
   readOnlyList.Add(new int[] { i, j });
   }
   }
   }
   return readOnlyList;
   }
  
  /*
   Suppress the foreign key constraint rules and returns an arraylist of the existing foreignkey constraint rules.
  */
   private ArrayList SuppressConstraintRules(DataSet ds) {
   Debug.Assert(ds != null);
  
   ArrayList constraintRulesList = new ArrayList();
   for (int i = 0; i < ds.tables.count;="" i++)="">
   DataTable dtChild = ds.Tables[i];
   for (int j = 0; j < dtchild.constraints.count;="" j++)="">
   Constraint c = dtChild.Constraints[j];
   if (c is ForeignKeyConstraint) {
   ForeignKeyConstraint fk = (ForeignKeyConstraint) c;
   ArrayList list = new ArrayList();
   list.Add(new int[] { i, j });
   list.Add(new int[] { (int) fk.AcceptRejectRule, (int) fk.UpdateRule, (int) fk.DeleteRule });
   constraintRulesList.Add(list);
  
   fk.AcceptRejectRule = AcceptRejectRule.None;
   fk.UpdateRule = Rule.None;
   fk.DeleteRule = Rule.None;
   }
   }
   }
   return constraintRulesList;
   }
  
  /*
   Resets the read-only columns on the datatable based on the input readOnly list.
  */
   private void ResetReadOnly(DataSet ds, ArrayList readOnlyList) {
   Debug.Assert(ds != null);
   Debug.Assert(readOnlyList != null);
  
   foreach (object o in readOnlyList) {
   int[] indicesArr = (int[]) o;
  
   Debug.Assert(indicesArr.Length == 2);
   int tableIndex = indicesArr[0];
   int columnIndex = indicesArr[1];
  
   Debug.Assert(ds.Tables.Count > tableIndex);
   Debug.Assert(ds.Tables[tableIndex].Columns.Count > columnIndex);
  
   DataColumn dc = ds.Tables[tableIndex].Columns[columnIndex];
   Debug.Assert(dc != null);
  
   dc.ReadOnly = true;
   }
   }
  
  /*
   Resets the foreignkey constraint rules on the dataset based on the input constraint rules list.
  */
   private void ResetConstraintRules(DataSet ds, ArrayList constraintRulesList) {
   Debug.Assert(ds != null);
   Debug.Assert(constraintRulesList != null);
  
   foreach (ArrayList list in constraintRulesList) {
   Debug.Assert(list.Count == 2);
   int[] indicesArr = (int[]) list[0];
   int[] rules = (int[]) list[1];
  
   Debug.Assert(indicesArr.Length == 2);
   int tableIndex = indicesArr[0];
   int constraintIndex = indicesArr[1];
  
   Debug.Assert(ds.Tables.Count > tableIndex);
   DataTable dtChild = ds.Tables[tableIndex];
  
   Debug.Assert(dtChild.Constraints.Count > constraintIndex);
   ForeignKeyConstraint fk = (ForeignKeyConstraint) dtChild.Constraints[constraintIndex];
  
   Debug.Assert(rules.Length == 3);
   fk.AcceptRejectRule = (AcceptRejectRule) rules[0];
   fk.UpdateRule = (Rule) rules[1];
   fk.DeleteRule = (Rule) rules[2];
   }
   }
  
  /*
   Checks whether the dataset name and namespaces are as expected and the tables count is right.
  */
   private bool IsSchemaIdentical(DataSet ds) {
   Debug.Assert(ds != null);
   if (ds.DataSetName != _datasetName || ds.Namespace != _namespace) {
   return false;
   }
   Debug.Assert(_dataTableSurrogates != null);
   if (ds.Tables.Count != _dataTableSurrogates.Length) {
   return false;
   }
   return true;
   }
  }
  
  [Serializable]
  class DataTableSurrogate {
   //DataTable properties
   private string _tableName;
   private string _namespace;
   private string _prefix;
   private bool _caseSensitive;
   private CultureInfo _locale;
   private string _displayExpression;
   private int _minimumCapacity;
  
   //Columns
   private DataColumnSurrogate[] _dataColumnSurrogates;
  
   //Constraints
   private ArrayList _uniqueConstraints; //An ArrayList of unique constraints : [constraintName]->[columnIndexes]->[IsPrimaryKey]->[extendedProperties]
  
   //ExtendedProperties
   private Hashtable _extendedProperties;
  
   //Rows
   private BitArray _rowStates; //The 4 rowstates[Unchanged, Added, Modified, Deleted] are represented with 2 bits. The length of the BitArray will be twice the size of the number of rows.
   private object[][] _records; //As many object[] as there are number of columns. Always send 2 records for 1 row. TradeOff between memory vs. performance. Time intensive to find which records are modified.
   private Hashtable _rowErrors = new Hashtable(); //Keep a map between the row index and the row error
   private Hashtable _colErrors = new Hashtable(); //Keep a map between the row index and the Arraylist of columns that are in error and the error strings.
  
  /*
   Constructs a DataTableSurrogate from a DataTable.
  */
   public DataTableSurrogate(DataTable dt) {
   if (dt == null) {
   throw new ArgumentNullException("The parameter dt is null");
   }
  
   _tableName = dt.TableName;
   _namespace = dt.Namespace;
   _prefix = dt.Prefix;
   _caseSensitive = dt.CaseSensitive;
   _locale = dt.Locale;
   _displayExpression = dt.DisplayExpression;
   _minimumCapacity = dt.MinimumCapacity;
  
   //Columns
   _dataColumnSurrogates = new DataColumnSurrogate[dt.Columns.Count];
   for (int i = 0; i < dt.columns.count;="" i++)="">
   _dataColumnSurrogates[i] = new DataColumnSurrogate(dt.Columns[i]);
   }
  
   //Constraints
   _uniqueConstraints = GetUniqueConstraints(dt);
  
   //ExtendedProperties
   _extendedProperties = new Hashtable();
   if (dt.ExtendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in dt.ExtendedProperties.Keys) {
   _extendedProperties.Add(propertyKey, dt.ExtendedProperties[propertyKey]);
   }
   }
  
   //Rows
   if (dt.Rows.Count > 0) {
   _rowStates = new BitArray(dt.Rows.Count <>
   _records = new object[dt.Columns.Count] [];
   for (int i = 0; i < dt.columns.count;="" i++)="">
   _records[i] = new object[dt.Rows.Count <>
   }
   for (int i = 0; i < dt.rows.count;="" i++)="">
   GetRecords(dt.Rows[i], i <>
   }
   }
   }
  
  /*
   Constructs a DataTable from DataTableSurrogate.
  */
   public DataTable ConvertToDataTable() {
   DataTable dt = new DataTable();
   ReadSchemaIntoDataTable(dt);
   ReadDataIntoDataTable(dt);
   return dt;
   }
  
  /*
   Reads the schema into the datatable from DataTableSurrogate.
  */
   public void ReadSchemaIntoDataTable(DataTable dt) {
   if (dt == null) {
   throw new ArgumentNullException("The datatable parameter cannot be null");
   }
  
   dt.TableName = _tableName;
   dt.Namespace = _namespace;
   dt.Prefix = _prefix;
   dt.CaseSensitive = _caseSensitive;
   dt.Locale = _locale;
   dt.DisplayExpression = _displayExpression;
   dt.MinimumCapacity = _minimumCapacity;
  
   Debug.Assert(_dataColumnSurrogates != null);
   for (int i = 0; i < _datacolumnsurrogates.length;="" i++)="">
   DataColumnSurrogate dataColumnSurrogate = _dataColumnSurrogates[i];
   DataColumn dc = dataColumnSurrogate.ConvertToDataColumn();
   dt.Columns.Add(dc);
   }
  
   //UniqueConstraints
   SetUniqueConstraints(dt, _uniqueConstraints);
  
   //Extended properties
   Debug.Assert(_extendedProperties != null);
   if (_extendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in _extendedProperties.Keys) {
   dt.ExtendedProperties.Add(propertyKey, _extendedProperties[propertyKey]);
   }
   }
   }
  
  /*
   Reads the data into a DataTable from DataTableSurrogate.
  */
   public void ReadDataIntoDataTable(DataTable dt) {
   ReadDataIntoDataTable(dt, true);
   }
  
  /*
   Copies the rows into a DataTable from DataTableSurrogate.
  */
   internal void ReadDataIntoDataTable(DataTable dt, bool suppressSchema) {
   if (dt == null) {
   throw new ArgumentNullException("The datatable parameter cannot be null");
   }
   Debug.Assert(IsSchemaIdentical(dt));
  
   //Suppress read-only and constraint rules while loading the data.
   ArrayList readOnlyList = null;
   ArrayList constraintRulesList = null;
   if (suppressSchema) {
   readOnlyList = SuppressReadOnly(dt);
   constraintRulesList = SuppressConstraintRules(dt);
   }
  
   //Read the rows
   if (_records != null && dt.Columns.Count > 0) {
   Debug.Assert(_records.Length > 0);
   int rowCount = _records[0].Length >> 1;
   for (int i = 0; i < rowcount;="" i++)="">
   ConvertToDataRow(dt, i <>
   }
   }
  
   //Reset read-only column and constraint rules back after loading the data.
   if (suppressSchema) {
   ResetReadOnly(dt, readOnlyList);
   ResetConstraintRules(dt, constraintRulesList);
   }
   }
  
  /*
   Gets unique constraints availabe on the datatable.
   ***Serialized unique constraints format : [constraintName]->[columnIndexes]->[IsPrimaryKey]->[extendedProperties]***
  */
   private ArrayList GetUniqueConstraints(DataTable dt) {
   Debug.Assert(dt != null);
  
   ArrayList constraintList = new ArrayList();
   for (int i = 0; i < dt.constraints.count;="" i++)="">
   Constraint c = dt.Constraints[i];
   UniqueConstraint uc = c as UniqueConstraint;
   if (uc != null) {
   string constraintName = c.ConstraintName;
   int[] colInfo = new int[uc.Columns.Length];
   for (int j = 0; j < colinfo.length;="" j++)="">
   colInfo[j] = uc.Columns[j].Ordinal;
   }
  
   ArrayList list = new ArrayList();
   list.Add(constraintName);
   list.Add(colInfo);
   list.Add(uc.IsPrimaryKey);
   Hashtable extendedProperties = new Hashtable();
   if (uc.ExtendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in uc.ExtendedProperties.Keys) {
   extendedProperties.Add(propertyKey, uc.ExtendedProperties[propertyKey]);
   }
   }
   list.Add(extendedProperties);
  
   constraintList.Add(list);
   }
   }
   return constraintList;
   }
  
  /*
   Adds unique constraints to the table. The arraylist contains the serialized format of the unique constraints.
   ***Deserialize the unique constraints format : [constraintName]->[columnIndexes]->[IsPrimaryKey]->[extendedProperties]***
  */
   private void SetUniqueConstraints(DataTable dt, ArrayList constraintList) {
   Debug.Assert(dt != null);
   Debug.Assert(constraintList != null);
  
   foreach (ArrayList list in constraintList) {
   Debug.Assert(list.Count == 4);
   string constraintName = (string) list[0];
   int[] keyColumnIndexes = (int[]) list[1];
   bool isPrimaryKey = (bool) list[2];
   Hashtable extendedProperties = (Hashtable) list[3];
  
   DataColumn[] keyColumns = new DataColumn[keyColumnIndexes.Length];
   for (int i = 0; i < keycolumnindexes.length;="" i++)="">
   Debug.Assert(dt.Columns.Count > keyColumnIndexes[i]);
   keyColumns[i] = dt.Columns[keyColumnIndexes[i]];
   }
   //Create the constraint.
   UniqueConstraint uc = new UniqueConstraint(constraintName, keyColumns, isPrimaryKey);
   //Extended Properties.
   Debug.Assert(extendedProperties != null);
   if (extendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in extendedProperties.Keys) {
   uc.ExtendedProperties.Add(propertyKey, extendedProperties[propertyKey]);
   }
   }
   dt.Constraints.Add(uc);
   }
   }
  
  /*
   Sets expression on the columns.
  */
   internal void SetColumnExpressions(DataTable dt) {
   Debug.Assert(dt != null);
  
   Debug.Assert(_dataColumnSurrogates != null);
   Debug.Assert(dt.Columns.Count == _dataColumnSurrogates.Length);
   for (int i = 0; i < dt.columns.count;="" i++)="">
   DataColumn dc = dt.Columns[i];
   DataColumnSurrogate dataColumnSurrogate = _dataColumnSurrogates[i];
   dataColumnSurrogate.SetColumnExpression(dc);
   }
   }
  
  /*
   Gets the records from the rows.
  */
   private void GetRecords(DataRow row, int bitIndex) {
   Debug.Assert(row != null);
  
   ConvertToSurrogateRowState(row.RowState, bitIndex);
   ConvertToSurrogateRecords(row, bitIndex);
   ConvertToSurrogateRowError(row, bitIndex >> 1);
   }
  
  /*
   Constructs the row, rowError and columnErrors.
  */
   public DataRow ConvertToDataRow(DataTable dt, int bitIndex) {
   DataRowState rowState = ConvertToRowState(bitIndex);
   DataRow row = ConstructRow(dt, rowState, bitIndex);
   ConvertToRowError(row, bitIndex >> 1);
   return row;
   }
  
  /*
   Sets the two bits in the bitArray to represent the DataRowState.
   The 4 rowstates[Unchanged, Added, Modified, Deleted] are represented with 2 bits. The length of the BitArray will be twice the size of the number of rows.
   Serialozed rowstate format : [00]->UnChanged, [01]->Added, [10]->Modified, [11]->Deleted.
  */
   private void ConvertToSurrogateRowState(DataRowState rowState, int bitIndex) {
   Debug.Assert(_rowStates != null);
   Debug.Assert(_rowStates.Length > bitIndex);
  
   switch (rowState) {
   case DataRowState.Unchanged:
   _rowStates[bitIndex] = false;
   _rowStates[bitIndex + 1] = false;
   break;
   case DataRowState.Added:
   _rowStates[bitIndex] = false;
   _rowStates[bitIndex + 1] = true;
   break;
   case DataRowState.Modified:
   _rowStates[bitIndex] = true;
   _rowStates[bitIndex + 1] = false;
   break;
   case DataRowState.Deleted:
   _rowStates[bitIndex] = true;
   _rowStates[bitIndex + 1] = true;
   break;
   default:
   throw new InvalidEnumArgumentException(String.Format("Unrecognized row state {0}", rowState));
   }
   }
  
  /*
   Constructs the RowState from the two bits in the bitarray.
   Deserialize rowstate format : [00]->UnChanged, [01]->Added, [10]->Modified, [11]->Deleted.
  */
   private DataRowState ConvertToRowState(int bitIndex) {
   Debug.Assert(_rowStates != null);
   Debug.Assert(_rowStates.Length > bitIndex);
  
   bool b1 = _rowStates[bitIndex];
   bool b2 = _rowStates[bitIndex + 1];
  
   if (!b1 && !b2) {
   return DataRowState.Unchanged;
   } else if (!b1 && b2) {
   return DataRowState.Added;
   } else if (b1 && !b2) {
   return DataRowState.Modified;
   } else if (b1 && b2) {
   return DataRowState.Deleted;
   } else {
   throw new ArgumentException("Unrecognized bitpattern");
   }
   }
  
  /*
   Constructs surrogate records from the DataRow.
  */
   private void ConvertToSurrogateRecords(DataRow row, int bitIndex) {
   Debug.Assert(row != null);
   Debug.Assert(_records != null);
  
   int colCount = row.Table.Columns.Count;
   DataRowState rowState = row.RowState;
  
   Debug.Assert(_records.Length == colCount);
   if (rowState != DataRowState.Added) {//Unchanged, modified, deleted
   for (int i = 0; i < colcount;="" i++)="">
   Debug.Assert(_records[i].Length > bitIndex);
   _records[i][bitIndex] = row[i, DataRowVersion.Original];
   }
   }
  
   if (rowState != DataRowState.Unchanged && rowState != DataRowState.Deleted) {//Added, modified state
   for (int i = 0; i < colcount;="" i++)="">
   Debug.Assert(_records[i].Length > bitIndex + 1);
   _records[i][bitIndex + 1] = row[i, DataRowVersion.Current];
   }
   }
   }
  
  /*
   Constructs a DataRow from records[original and current] and adds the row to the DataTable rows collection.
  */
   private DataRow ConstructRow(DataTable dt, DataRowState rowState, int bitIndex) {
   Debug.Assert(dt != null);
   Debug.Assert(_records != null);
  
   DataRow row = dt.NewRow();
   int colCount = dt.Columns.Count;
  
   Debug.Assert(_records.Length == colCount);
   switch (rowState) {
   case DataRowState.Unchanged:
   for (int i = 0; i < colcount;="" i++)="">
   Debug.Assert(_records[i].Length > bitIndex);
   row[i] = _records[i][bitIndex]; //Original Record
   }
   dt.Rows.Add(row);
   row.AcceptChanges();
   break;
   case DataRowState.Added:
   for (int i = 0; i < colcount;="" i++)="">
   Debug.Assert(_records[i].Length > bitIndex + 1);
   row[i] = _records[i][bitIndex + 1]; //Current Record
   }
   dt.Rows.Add(row);
   break;
   case DataRowState.Modified:
   for (int i = 0; i < colcount;="" i++)="">
   Debug.Assert(_records[i].Length > bitIndex);
   row[i] = _records[i][bitIndex]; //Original Record
   }
   dt.Rows.Add(row);
   row.AcceptChanges();
   row.BeginEdit();
   for (int i = 0; i < colcount;="" i++)="">
   Debug.Assert(_records[i].Length > bitIndex + 1);
   row[i] = _records[i][bitIndex + 1]; //Current Record
   }
   row.EndEdit();
   break;
   case DataRowState.Deleted:
   for (int i = 0; i < colcount;="" i++)="">
   Debug.Assert(_records[i].Length > bitIndex);
   row[i] = _records[i][bitIndex]; //Original Record
   }
   dt.Rows.Add(row);
   row.AcceptChanges();
   row.Delete();
   break;
   default:
   throw new InvalidEnumArgumentException(String.Format("Unrecognized row state {0}", rowState));
   }
   return row;
   }
  
  /*
   Constructs the surrogate rowerror, columnsInError and columnErrors.
  */
   private void ConvertToSurrogateRowError(DataRow row, int rowIndex) {
   Debug.Assert(row != null);
   Debug.Assert(_rowErrors != null);
   Debug.Assert(_colErrors != null);
  
   if (row.HasErrors) {
   _rowErrors.Add(rowIndex, row.RowError);
   DataColumn[] dcArr = row.GetColumnsInError();
   if (dcArr.Length > 0) {
   int[] columnsInError = new int[dcArr.Length];
   string[] columnErrors = new string[dcArr.Length];
   for (int i = 0; i < dcarr.length;="" i++)="">
   columnsInError[i] = dcArr[i].Ordinal;
   columnErrors[i] = row.GetColumnError(dcArr[i]);
   }
   ArrayList list = new ArrayList();
   list.Add(columnsInError);
   list.Add(columnErrors);
   _colErrors.Add(rowIndex, list);
   }
   }
   }
  
  /*
   Set the row and columns in error.
  */
   private void ConvertToRowError(DataRow row, int rowIndex) {
   Debug.Assert(row != null);
   Debug.Assert(_rowErrors != null);
   Debug.Assert(_colErrors != null);
  
   if (_rowErrors.ContainsKey(rowIndex)) {
   row.RowError = (string) _rowErrors[rowIndex];
   }
   if (_colErrors.ContainsKey(rowIndex)) {
   ArrayList list = (ArrayList) _colErrors[rowIndex];
   int[] columnsInError = (int[]) list[0];
   string[] columnErrors = (string[]) list[1];
   Debug.Assert(columnsInError.Length == columnErrors.Length);
   for (int i = 0; i < columnsinerror.length;="" i++)="">
   row.SetColumnError(columnsInError[i], columnErrors[i]);
   }
   }
   }
  
  /*
   Suppress the read-only property and returns an arraylist of read-only columns.
  */
   private ArrayList SuppressReadOnly(DataTable dt) {
   Debug.Assert(dt != null);
   ArrayList readOnlyList = new ArrayList();
   for (int j = 0; j < dt.columns.count;="" j++)="">
   if (dt.Columns[j].Expression == String.Empty && dt.Columns[j].ReadOnly == true) {
   readOnlyList.Add(j);
   }
   }
   return readOnlyList;
   }
  
  /*
   Suppress the foreign key constraint rules and returns an arraylist of the existing foreignkey constraint rules.
  */
   private ArrayList SuppressConstraintRules(DataTable dt) {
   Debug.Assert(dt != null);
   ArrayList constraintRulesList = new ArrayList();
   DataSet ds = dt.DataSet;
   if (ds != null) {
   for (int i = 0; i < ds.tables.count;="" i++)="">
   DataTable dtChild = ds.Tables[i];
   for (int j = 0; j < dtchild.constraints.count;="" j++)="">
   Constraint c = dtChild.Constraints[j];
   if (c is ForeignKeyConstraint) {
   ForeignKeyConstraint fk = (ForeignKeyConstraint) c;
   if (fk.RelatedTable == dt) {
   ArrayList list = new ArrayList();
   list.Add(new int[] { i, j });
   list.Add(new int[] { (int) fk.AcceptRejectRule, (int) fk.UpdateRule, (int) fk.DeleteRule });
   constraintRulesList.Add(list);
  
   fk.AcceptRejectRule = AcceptRejectRule.None;
   fk.UpdateRule = Rule.None;
   fk.DeleteRule = Rule.None;
   }
   }
   }
   }
   }
   return constraintRulesList;
   }
  
  /*
   Resets the read-only columns on the datatable based on the input readOnly list.
  */
   private void ResetReadOnly(DataTable dt, ArrayList readOnlyList) {
   Debug.Assert(dt != null);
   Debug.Assert(readOnlyList != null);
  
   DataSet ds = dt.DataSet;
   foreach (object o in readOnlyList) {
   int columnIndex = (int) o;
   Debug.Assert(dt.Columns.Count > columnIndex);
   dt.Columns[columnIndex].ReadOnly = true;
   }
   }
  
  /*
   Reset the foreignkey constraint rules on the datatable based on the input constraintRules list.
  */
   private void ResetConstraintRules(DataTable dt, ArrayList constraintRulesList) {
   Debug.Assert(dt != null);
   Debug.Assert(constraintRulesList != null);
  
   DataSet ds = dt.DataSet;
   foreach (ArrayList list in constraintRulesList) {
   Debug.Assert(list.Count == 2);
   int[] indicesArr = (int[]) list[0];
   int[] rules = (int[]) list[1];
  
   Debug.Assert(indicesArr.Length == 2);
   int tableIndex = indicesArr[0];
   int constraintIndex = indicesArr[1];
  
   Debug.Assert(ds.Tables.Count > tableIndex);
   DataTable dtChild = ds.Tables[tableIndex];
  
   Debug.Assert(dtChild.Constraints.Count > constraintIndex);
   ForeignKeyConstraint fk = (ForeignKeyConstraint) dtChild.Constraints[constraintIndex];
  
   Debug.Assert(rules.Length == 3);
   fk.AcceptRejectRule = (AcceptRejectRule) rules[0];
   fk.UpdateRule = (Rule) rules[1];
   fk.DeleteRule = (Rule) rules[2];
   }
   }
  
  /*
   Checks whether the datatable schema matches with the surrogate schema.
  */
   private bool IsSchemaIdentical(DataTable dt) {
   Debug.Assert(dt != null);
  
   if (dt.TableName != _tableName || dt.Namespace != _namespace) {
   return false;
   }
  
   Debug.Assert(_dataColumnSurrogates != null);
   if (dt.Columns.Count != _dataColumnSurrogates.Length) {
   return false;
   }
   for (int i = 0; i < dt.columns.count;="" i++)="">
   DataColumn dc = dt.Columns[i];
   DataColumnSurrogate dataColumnSurrogate = _dataColumnSurrogates[i];
   if (!dataColumnSurrogate.IsSchemaIdentical(dc)) {
   return false;
   }
   }
   return true;
   }
  }
  
  [Serializable]
  class DataColumnSurrogate {
   private string _columnName;
   private string _namespace;
   private string _prefix;
   private MappingType _columnMapping;
   private bool _allowNull;
   private bool _autoIncrement;
   private long _autoIncrementStep;
   private long _autoIncrementSeed;
   private string _caption;
   private object _defaultValue;
   private bool _readOnly;
   private int _maxLength;
   private Type _dataType;
   private string _expression;
  
   //ExtendedProperties
   private Hashtable _extendedProperties;
  
  /*
   Constructs a DataColumnSurrogate from a DataColumn.
  */
   public DataColumnSurrogate(DataColumn dc) {
   if (dc == null) {
   throw new ArgumentNullException("The datacolumn parameter is null");
   }
   _columnName = dc.ColumnName;
   _namespace = dc.Namespace;
   _dataType = dc.DataType;
   _prefix = dc.Prefix;
   _columnMapping = dc.ColumnMapping;
   _allowNull = dc.AllowDBNull;
   _autoIncrement = dc.AutoIncrement;
   _autoIncrementStep = dc.AutoIncrementStep;
   _autoIncrementSeed = dc.AutoIncrementSeed;
   _caption = dc.Caption;
   _defaultValue = dc.DefaultValue;
   _readOnly = dc.ReadOnly;
   _maxLength = dc.MaxLength;
   _expression = dc.Expression;
  
   //ExtendedProperties
   _extendedProperties = new Hashtable();
   if (dc.ExtendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in dc.ExtendedProperties.Keys) {
   _extendedProperties.Add(propertyKey, dc.ExtendedProperties[propertyKey]);
   }
   }
   }
  
  /*
   Constructs a DataColumn from DataColumnSurrogate.
  */
   public DataColumn ConvertToDataColumn() {
   DataColumn dc = new DataColumn();
   dc.ColumnName = _columnName;
   dc.Namespace = _namespace;
   dc.DataType = _dataType;
   dc.Prefix = _prefix;
   dc.ColumnMapping = _columnMapping;
   dc.AllowDBNull = _allowNull;
   dc.AutoIncrement = _autoIncrement;
   dc.AutoIncrementStep = _autoIncrementStep;
   dc.AutoIncrementSeed = _autoIncrementSeed;
   dc.Caption = _caption;
   dc.DefaultValue = _defaultValue;
   dc.ReadOnly = _readOnly;
   dc.MaxLength = _maxLength;
   //dc.Expression = _expression;
  
   //Extended properties
   Debug.Assert(_extendedProperties != null);
   if (_extendedProperties.Keys.Count > 0) {
   foreach (object propertyKey in _extendedProperties.Keys) {
   dc.ExtendedProperties.Add(propertyKey, _extendedProperties[propertyKey]);
   }
   }
   return dc;
   }
  
  /*
   Set expression on the DataColumn.
  */
   internal void SetColumnExpression(DataColumn dc) {
   Debug.Assert(dc != null);
  
   if (_expression != null && !_expression.Equals(String.Empty)) {
   dc.Expression = _expression;
   }
   }
  
  /*
   Checks whether the column schema is identical. Marked internal as the DataTableSurrogate objects needs to have access to this object.
   Note: ReadOnly is not checked here as we suppress readonly when reading data.
  */
   internal bool IsSchemaIdentical(DataColumn dc) {
   Debug.Assert(dc != null);
   if ((dc.ColumnName != _columnName) || (dc.Namespace != _namespace) || (dc.DataType != _dataType) ||
   (dc.Prefix != _prefix) || (dc.ColumnMapping != _columnMapping) ||
   (dc.ColumnMapping != _columnMapping) || (dc.AllowDBNull != _allowNull) ||
   (dc.AutoIncrement != _autoIncrement) || (dc.AutoIncrementStep != _autoIncrementStep) ||
   (dc.AutoIncrementSeed != _autoIncrementSeed) || (dc.Caption != _caption) ||
   (!(AreDefaultValuesEqual(dc.DefaultValue, _defaultValue))) || (dc.MaxLength != _maxLength) ||
   (dc.Expression != _expression)) {
   return false;
   }
   return true;
   }
  
  /*
   Checks whether the default boxed objects are equal.
  */
   internal static bool AreDefaultValuesEqual(object o1, object o2) {
   if (o1 == null && o2 == null) {
   return true;
   } else if (o1 == null || o2 == null) {
   return false;
   } else {
   return o1.Equals(o2);
   }
   }
  }