/// <summary>Add a new column to the DB.</summary> /// <remarks> /// <para>The column must exist in the record's type.</para> /// <para>If you're creating a column that's no longer in the schema (because you're doing a sequential upgrade step to the non-latest version), /// you better leave the column + attribute in the record class, but mark it with <see cref="System.ObsoleteAttribute">[Obsolete]</see> attribute.</para> /// </remarks> /// <typeparam name="tRow">Record type</typeparam> /// <param name="name">Name of the ESENT column to add</param> public void AddColumn <tRow>(string name) where tRow : new() { TypeSerializer ser = serializerForType(typeof(tRow)); TypeSerializer.ColumnInfo ci = ser.getColumnsSchema().FirstOrDefault(c => c.columnName == name); if (null == ci) { throw new SerializationException("The column '" + name + "' was not found on row type '" + typeof(tRow).Name + "'."); } Action act = () => { JET_TABLEID idTable; Api.JetOpenTable(this.session.idSession, this.session.idDatabase, ser.tableName, null, 0, OpenTableGrbit.None, out idTable); try { JET_COLUMNID idColumn; Api.JetAddColumn(this.session.idSession, idTable, ci.columnName, ci.attrib.getColumnDef(), null, 0, out idColumn); if (!ci.attrib.bFieldNullable) { // The new column is not nullable. // It means attempts to de-serialize the column will throw saying "Nullable object must have a value" // To fix, we update all records of the table, setting the default value of the column. // TODO: instead of creating default value for type, use reflection to find out is there a default value in the record class object objVal = Activator.CreateInstance(ci.tpValue); Cursor <tRow> cur = new Cursor <tRow>(this.session, ser, idTable, false); if (cur.TryMoveFirst()) { do { cur.SaveSingleField(ci.columnName, objVal); // TODO [low]: pulse the transaction here.. The version store can be exhausted for large tables. }while(cur.tryMoveNext()); } } } finally { Api.JetCloseTable(this.session.idSession, idTable); } }; this.actUpgrade += act; }
int Import(TextReader tr) { string line; // Header; we also trim that extra trailing tabs being appended by Excel when saving documents in the TSV format. line = tr.ReadLine().TrimEnd(s_cTab); if (line != this.GetType().FullName) { throw new SerializationException("Wrong signature: stored " + line + ", must be " + this.GetType().FullName + "."); } line = tr.ReadLine().TrimEnd(s_cTab); int cols = int.Parse(line); if (schema.Count != cols) { throw new SerializationException("Wrong columns count: stored " + cols + ", must be " + schema.Count + "."); } // Validate the stored columns schema. JET_COLUMNDEF[] arrCd = new JET_COLUMNDEF[cols]; string[] arr; for (int i = 0; i < cols; i++) { TypeSerializer.ColumnInfo ci = schema[i]; JET_COLUMNDEF cd = ci.attrib.getColumnDef(); arrCd[i] = cd; line = tr.ReadLine(); arr = line.Split(new char[] { '\t' }); if (arr[0] != ci.columnName) { throw new SerializationException("Wrong column name: stored '" + arr[0] + "', must be '" + ci.columnName + "'."); } eColumnKind ck = (eColumnKind)(Enum.Parse(typeof(eColumnKind), arr[1])); if (ck != ci.attrib.getColumnKind()) { throw new SerializationException("Wrong column kind: stored '" + ck.ToString() + "', must be '" + ci.attrib.getColumnKind().ToString() + "'."); } if (cd.coltyp != (JET_coltyp)(int.Parse(arr[2]))) { throw new SerializationException("Mismatched type of column '" + ci.columnName + "'"); } if (cd.cp != (JET_CP)(int.Parse(arr[3]))) { throw new SerializationException("Mismatched codepage of column '" + ci.columnName + "'"); } if (cd.grbit != (ColumndefGrbit)(int.Parse(arr[4]))) { throw new SerializationException("Mismatched bit flags of column '" + ci.columnName + "'"); } } // Import the rows. return(ImportData ( ( int nRecord ) => { line = tr.ReadLine(); return line != null; }, ( int nRecord ) => { if (line == "") { return false; } arr = line.Split(s_cTab); if (arr.Length < cols) { throw new SerializationException("Too few columns in the line '" + line + "'"); } for (int i = 0; i < cols; i++) { JET_COLUMNDEF cd = arrCd[i]; if (0 != (cd.grbit & ColumndefGrbit.ColumnAutoincrement)) { // TODO: look for a way to restore ColumnAutoincrement values. continue; } if (!isMultiValued(cd)) { LoadVal(arr[i], schema[i].idColumn, cd.coltyp, cd.cp); } else { string[] arr2 = arr[i].Split(s_cSlash); foreach (string item in arr2) { LoadVal(item, schema[i].idColumn, cd.coltyp, cd.cp); } } } return true; } )); }
int Export(TextWriter tw) { int cols = schema.Count; // Header tw.WriteLine(this.GetType().FullName); tw.WriteLine(cols); // Columns schema JET_COLUMNDEF[] arrCd = new JET_COLUMNDEF[cols]; string[] arr; for (int i = 0; i < cols; i++) { TypeSerializer.ColumnInfo ci = schema[i]; JET_COLUMNDEF cd = ci.attrib.getColumnDef(); arrCd[i] = cd; arr = new string[] { ci.columnName, ci.attrib.getColumnKind().ToString(), ((int)cd.coltyp).ToString(), ((int)cd.cp).ToString(), ((int)cd.grbit).ToString(), }; tw.WriteLine(string.Join("\t", arr)); } // Rows arr = new string[cols]; return(ExportData ( ( int nRecord ) => { for (int i = 0; i < cols; i++) { JET_COLUMNDEF cd = arrCd[i]; if (isMultiValued(cd)) { for (int j = 1; true; j++) { string val = getStringValue(tw, schema[i].idColumn, j, cd.coltyp, cd.cp); if (val == "") { break; } if (j > 1) { tw.Write('/'); } tw.Write(val); } } else { string val = getStringValue(tw, schema[i].idColumn, 1, cd.coltyp, cd.cp); tw.Write(val); } if (i + 1 < cols) { tw.Write('\t'); } } tw.WriteLine(); } )); }