├── .gitattributes ├── .gitignore ├── EseDump ├── App.config ├── EseDump.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── EseView.sln ├── EseView ├── App.config ├── App.xaml ├── App.xaml.cs ├── DBColumnRetriever.cs ├── DBReader.cs ├── DBRow.cs ├── DatabaseVirtualizedProvider.cs ├── EseView.csproj ├── IVirtualizedProvider.cs ├── MainViewModel.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── VirtualizedReadOnlyList.cs └── XmlDump.cs ├── LICENSE.txt ├── ManagedEsent ├── Esent.Interop.dll └── Esent.Interop.xml └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # Build results 11 | [Dd]ebug/ 12 | [Dd]ebugPublic/ 13 | [Rr]elease/ 14 | [Rr]eleases/ 15 | x64/ 16 | x86/ 17 | build/ 18 | bld/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | 22 | # Roslyn cache directories 23 | *.ide/ 24 | 25 | # MSTest test Results 26 | [Tt]est[Rr]esult*/ 27 | [Bb]uild[Ll]og.* 28 | 29 | #NUNIT 30 | *.VisualState.xml 31 | TestResult.xml 32 | 33 | # Build Results of an ATL Project 34 | [Dd]ebugPS/ 35 | [Rr]eleasePS/ 36 | dlldata.c 37 | 38 | *_i.c 39 | *_p.c 40 | *_i.h 41 | *.ilk 42 | *.meta 43 | *.obj 44 | *.pch 45 | *.pdb 46 | *.pgc 47 | *.pgd 48 | *.rsp 49 | *.sbr 50 | *.tlb 51 | *.tli 52 | *.tlh 53 | *.tmp 54 | *.tmp_proj 55 | *.log 56 | *.vspscc 57 | *.vssscc 58 | .builds 59 | *.pidb 60 | *.svclog 61 | *.scc 62 | 63 | # Chutzpah Test files 64 | _Chutzpah* 65 | 66 | # Visual C++ cache files 67 | ipch/ 68 | *.aps 69 | *.ncb 70 | *.opensdf 71 | *.sdf 72 | *.cachefile 73 | 74 | # Visual Studio profiler 75 | *.psess 76 | *.vsp 77 | *.vspx 78 | 79 | # TFS 2012 Local Workspace 80 | $tf/ 81 | 82 | # Guidance Automation Toolkit 83 | *.gpState 84 | 85 | # ReSharper is a .NET coding add-in 86 | _ReSharper*/ 87 | *.[Rr]e[Ss]harper 88 | *.DotSettings.user 89 | 90 | # JustCode is a .NET coding addin-in 91 | .JustCode 92 | 93 | # TeamCity is a build add-in 94 | _TeamCity* 95 | 96 | # DotCover is a Code Coverage Tool 97 | *.dotCover 98 | 99 | # NCrunch 100 | _NCrunch_* 101 | .*crunch*.local.xml 102 | 103 | # MightyMoose 104 | *.mm.* 105 | AutoTest.Net/ 106 | 107 | # Web workbench (sass) 108 | .sass-cache/ 109 | 110 | # Installshield output folder 111 | [Ee]xpress/ 112 | 113 | # DocProject is a documentation generator add-in 114 | DocProject/buildhelp/ 115 | DocProject/Help/*.HxT 116 | DocProject/Help/*.HxC 117 | DocProject/Help/*.hhc 118 | DocProject/Help/*.hhk 119 | DocProject/Help/*.hhp 120 | DocProject/Help/Html2 121 | DocProject/Help/html 122 | 123 | # Click-Once directory 124 | publish/ 125 | 126 | # Publish Web Output 127 | *.[Pp]ublish.xml 128 | *.azurePubxml 129 | # TODO: Comment the next line if you want to checkin your web deploy settings 130 | # but database connection strings (with potential passwords) will be unencrypted 131 | *.pubxml 132 | *.publishproj 133 | 134 | # NuGet Packages 135 | *.nupkg 136 | # The packages folder can be ignored because of Package Restore 137 | **/packages/* 138 | # except build/, which is used as an MSBuild target. 139 | !**/packages/build/ 140 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 141 | #!**/packages/repositories.config 142 | 143 | # Windows Azure Build Output 144 | csx/ 145 | *.build.csdef 146 | 147 | # Windows Store app package directory 148 | AppPackages/ 149 | 150 | # Others 151 | sql/ 152 | *.Cache 153 | ClientBin/ 154 | [Ss]tyle[Cc]op.* 155 | ~$* 156 | *~ 157 | *.dbmdl 158 | *.dbproj.schemaview 159 | *.pfx 160 | *.publishsettings 161 | node_modules/ 162 | 163 | # RIA/Silverlight projects 164 | Generated_Code/ 165 | 166 | # Backup & report files from converting an old project file 167 | # to a newer Visual Studio version. Backup files are not needed, 168 | # because we have git ;-) 169 | _UpgradeReport_Files/ 170 | Backup*/ 171 | UpgradeLog*.XML 172 | UpgradeLog*.htm 173 | 174 | # SQL Server files 175 | *.mdf 176 | *.ldf 177 | 178 | # Business Intelligence projects 179 | *.rdl.data 180 | *.bim.layout 181 | *.bim_*.settings 182 | 183 | # Microsoft Fakes 184 | FakesAssemblies/ 185 | 186 | # ========================= 187 | # Operating System Files 188 | # ========================= 189 | 190 | # OSX 191 | # ========================= 192 | 193 | .DS_Store 194 | .AppleDouble 195 | .LSOverride 196 | 197 | # Thumbnails 198 | ._* 199 | 200 | # Files that might appear on external disk 201 | .Spotlight-V100 202 | .Trashes 203 | 204 | # Directories potentially created on remote AFP share 205 | .AppleDB 206 | .AppleDesktop 207 | Network Trash Folder 208 | Temporary Items 209 | .apdisk 210 | 211 | # Windows 212 | # ========================= 213 | 214 | # Windows image file caches 215 | Thumbs.db 216 | ehthumbs.db 217 | 218 | # Folder config file 219 | Desktop.ini 220 | 221 | # Recycle Bin used on file shares 222 | $RECYCLE.BIN/ 223 | 224 | # Windows Installer files 225 | *.cab 226 | *.msi 227 | *.msm 228 | *.msp 229 | 230 | # Windows shortcuts 231 | *.lnk 232 | -------------------------------------------------------------------------------- /EseDump/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EseDump/EseDump.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4224123C-33C5-4CA5-9413-9AF9566C6A64} 8 | Exe 9 | Properties 10 | EseDump 11 | EseDump 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\ManagedEsent\Esent.Interop.dll 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {0110503b-fd68-494e-96ef-1945519cef30} 57 | EseView 58 | 59 | 60 | 61 | 68 | -------------------------------------------------------------------------------- /EseDump/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace EseDump 8 | { 9 | class Program 10 | { 11 | static void Usage() 12 | { 13 | var assembly = System.Reflection.Assembly.GetEntryAssembly().GetName(); 14 | Console.WriteLine("{0} version {1}", assembly.Name, assembly.Version); 15 | Console.WriteLine("usage: {0}.exe [/recover] [[/] [...]]", assembly.Name); 16 | } 17 | 18 | static async Task Run(IEnumerable args) 19 | { 20 | var vm = new EseView.MainViewModel(); 21 | bool recover = false; 22 | 23 | string dbPath = args.First(); 24 | args = args.Skip(1); 25 | 26 | if (args.FirstOrDefault() == "/recover") 27 | { 28 | recover = true; 29 | args = args.Skip(1); 30 | } 31 | 32 | try 33 | { 34 | await vm.OpenDatabaseAsync(dbPath, recover); 35 | } 36 | catch (Microsoft.Isam.Esent.Interop.EsentDatabaseDirtyShutdownException) 37 | { 38 | Console.WriteLine("The database was not shut down cleanly."); 39 | Console.WriteLine("Use the /recover flag to enable recovery."); 40 | Usage(); 41 | return -1; 42 | } 43 | catch (Exception ex) 44 | { 45 | Console.WriteLine("Error loading database: " + ex.Message); 46 | return -2; 47 | } 48 | 49 | List tables = args.ToList(); 50 | if (tables.Count == 0) 51 | { 52 | tables = vm.Tables; 53 | } 54 | 55 | using (var output = Console.OpenStandardOutput()) 56 | { 57 | vm.DumpTable(tables, output); 58 | } 59 | 60 | return 0; 61 | } 62 | 63 | static void Main(string[] args) 64 | { 65 | if (args.Length >= 1) 66 | { 67 | Environment.Exit(Run(args).Result); 68 | } 69 | else 70 | { 71 | Usage(); 72 | Environment.Exit(1); 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /EseDump/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("EseDump")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("EseDump")] 13 | [assembly: AssemblyCopyright("Copyright © William R. Fraser 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4224123c-33c5-4ca5-9413-9af9566c6a64")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.1.3.0")] 36 | [assembly: AssemblyFileVersion("1.1.3.0")] 37 | -------------------------------------------------------------------------------- /EseView.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.22823.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EseView", "EseView\EseView.csproj", "{0110503B-FD68-494E-96EF-1945519CEF30}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EseDump", "EseDump\EseDump.csproj", "{4224123C-33C5-4CA5-9413-9AF9566C6A64}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {0110503B-FD68-494E-96EF-1945519CEF30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {0110503B-FD68-494E-96EF-1945519CEF30}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {0110503B-FD68-494E-96EF-1945519CEF30}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {0110503B-FD68-494E-96EF-1945519CEF30}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {4224123C-33C5-4CA5-9413-9AF9566C6A64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {4224123C-33C5-4CA5-9413-9AF9566C6A64}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {4224123C-33C5-4CA5-9413-9AF9566C6A64}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {4224123C-33C5-4CA5-9413-9AF9566C6A64}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /EseView/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EseView/App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /EseView/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace EseView 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /EseView/DBColumnRetriever.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Isam.Esent.Interop; 7 | using Esent = Microsoft.Isam.Esent.Interop.Api; 8 | 9 | namespace EseView 10 | { 11 | public delegate object DBColumnRetriever(JET_SESID sesid, JET_TABLEID table, JET_COLUMNID column); 12 | 13 | public static class ColumnRetrievers 14 | { 15 | private static Dictionary s_fetchers = new Dictionary 16 | { 17 | { typeof(string), Esent.RetrieveColumnAsString }, 18 | { typeof(long?), (s,t,c) => Esent.RetrieveColumnAsInt64(s,t,c) }, 19 | { typeof(ulong?), (s,t,c) => Esent.RetrieveColumnAsUInt64(s,t,c) }, 20 | { typeof(int?), (s,t,c) => Esent.RetrieveColumnAsInt32(s,t,c) }, 21 | { typeof(uint?), (s,t,c) => Esent.RetrieveColumnAsUInt32(s,t,c) }, 22 | { typeof(short?), (s,t,c) => Esent.RetrieveColumnAsInt16(s,t,c) }, 23 | { typeof(ushort?), (s,t,c) => Esent.RetrieveColumnAsUInt16(s,t,c) }, 24 | { typeof(byte?), (s,t,c) => Esent.RetrieveColumnAsByte(s,t,c) }, 25 | { typeof(bool?), (s,t,c) => Esent.RetrieveColumnAsBoolean(s,t,c) }, 26 | { typeof(double?), (s,t,c) => Esent.RetrieveColumnAsDouble(s,t,c) }, 27 | { typeof(float?), (s,t,c) => Esent.RetrieveColumnAsFloat(s,t,c) }, 28 | 29 | { typeof(DateTime?), (s,t,c) => Esent.RetrieveColumnAsDateTime(s,t,c) }, 30 | { typeof(Guid?), (s,t,c) => Esent.RetrieveColumnAsGuid(s,t,c) }, 31 | { typeof(byte[]), Esent.RetrieveColumn }, 32 | 33 | }; 34 | 35 | public static DBColumnRetriever ForType(Type t) 36 | { 37 | if (!s_fetchers.ContainsKey(t)) 38 | { 39 | return (x, y, z) => "Error: unhandled type."; 40 | } 41 | 42 | return s_fetchers[t]; 43 | } 44 | 45 | public static T Retrieve(JET_SESID sesid, JET_TABLEID table, JET_COLUMNID column) 46 | { 47 | return (T)ForType(typeof(T))(sesid, table, column); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /EseView/DBReader.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Isam.Esent.Interop; 2 | using Microsoft.Isam.Esent.Interop.Vista; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using Esent = Microsoft.Isam.Esent.Interop.Api; 7 | 8 | namespace EseView 9 | { 10 | public class DBReader : IDisposable 11 | { 12 | private Instance m_jetInstance; 13 | private Session m_sesid; 14 | private JET_DBID m_dbid; 15 | private string m_filename; 16 | private Dictionary> m_tableDefs; 17 | private Dictionary, IEnumerable> m_indexDefs; 18 | 19 | struct ColSpec 20 | { 21 | public string Name; 22 | public Type Type; 23 | public DBColumnRetriever Retriever; 24 | public JET_COLUMNID ColumnId; 25 | } 26 | 27 | public DBReader(string filename) 28 | { 29 | m_filename = filename; 30 | m_tableDefs = new Dictionary>(); 31 | m_indexDefs = new Dictionary, IEnumerable>(); 32 | m_dbid = JET_DBID.Nil; 33 | } 34 | 35 | public void Init(bool recoveryEnabled) 36 | { 37 | int pageSize; 38 | Esent.JetGetDatabaseFileInfo(m_filename, out pageSize, JET_DbInfo.PageSize); 39 | 40 | string dir = Path.GetDirectoryName(m_filename) + Path.DirectorySeparatorChar; 41 | Esent.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, JET_param.DatabasePageSize, pageSize, null); 42 | Esent.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, JET_param.LogFilePath, 0, dir); 43 | Esent.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, JET_param.SystemPath, 0, dir); 44 | 45 | // Put the temp DB in our working directory. 46 | Esent.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, JET_param.TempPath, 0, Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar); 47 | 48 | // Set recovery option. 49 | Esent.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, JET_param.Recovery, 0, recoveryEnabled ? "On" : "Off"); 50 | 51 | m_jetInstance = new Instance("ESEVIEW"); 52 | m_jetInstance.Init(); 53 | 54 | m_sesid = new Session(m_jetInstance); 55 | 56 | Esent.JetAttachDatabase(m_sesid, m_filename, recoveryEnabled ? AttachDatabaseGrbit.None : AttachDatabaseGrbit.ReadOnly); 57 | Esent.JetOpenDatabase(m_sesid, m_filename, null, out m_dbid, OpenDatabaseGrbit.ReadOnly); 58 | } 59 | 60 | ~DBReader() 61 | { 62 | Close(); 63 | } 64 | 65 | public void Dispose() 66 | { 67 | Close(); 68 | } 69 | 70 | public void Close() 71 | { 72 | if ((m_sesid != null) && (m_sesid.JetSesid != JET_SESID.Nil)) 73 | { 74 | if (!m_dbid.Equals(JET_DBID.Nil)) 75 | { 76 | Esent.JetCloseDatabase(m_sesid, m_dbid, CloseDatabaseGrbit.None); 77 | } 78 | m_sesid.End(); 79 | } 80 | 81 | if ((m_jetInstance != null) && !m_jetInstance.Equals(JET_INSTANCE.Nil)) 82 | { 83 | m_jetInstance.Close(); 84 | } 85 | } 86 | 87 | public IEnumerable Tables 88 | { 89 | get 90 | { 91 | return Esent.GetTableNames(m_sesid, m_dbid); 92 | } 93 | } 94 | 95 | public int GetRowCount(string tableName, string indexName = null) 96 | { 97 | if (string.IsNullOrEmpty(tableName)) 98 | return 0; 99 | 100 | using (var table = new Table(m_sesid, m_dbid, tableName, OpenTableGrbit.ReadOnly)) 101 | { 102 | if (!string.IsNullOrEmpty(indexName)) 103 | { 104 | // This is needed because an index can be over a nullable column and exclude 105 | // the nulls, resulting in fewer records than when not using the index. 106 | Esent.JetSetCurrentIndex2(m_sesid, table, indexName, SetCurrentIndexGrbit.MoveFirst); 107 | } 108 | 109 | int numRecords; 110 | Esent.JetIndexRecordCount(m_sesid, table, out numRecords, 0); 111 | return numRecords; 112 | } 113 | } 114 | 115 | private void LoadTableDef(string tableName) 116 | { 117 | using (var table = new Table(m_sesid, m_dbid, tableName, OpenTableGrbit.ReadOnly)) 118 | { 119 | m_tableDefs[tableName] = GetTableDef(table); 120 | } 121 | } 122 | 123 | private List GetTableDef(JET_TABLEID table) 124 | { 125 | var columns = new List(Esent.GetTableColumns(m_sesid, table)); 126 | var tableDef = new List(); 127 | 128 | foreach (var column in columns) 129 | { 130 | var colspec = new ColSpec(); 131 | colspec.Name = column.Name; 132 | colspec.ColumnId = column.Columnid; 133 | colspec.Retriever = null; 134 | 135 | switch (column.Coltyp) 136 | { 137 | case JET_coltyp.Text: 138 | case JET_coltyp.LongText: 139 | colspec.Type = typeof(string); 140 | break; 141 | case JET_coltyp.Long: 142 | colspec.Type = typeof(int?); 143 | break; 144 | case JET_coltyp.Short: 145 | colspec.Type = typeof(short?); 146 | break; 147 | case JET_coltyp.UnsignedByte: 148 | colspec.Type = typeof(byte?); 149 | break; 150 | case JET_coltyp.Bit: 151 | colspec.Type = typeof(bool?); 152 | break; 153 | case JET_coltyp.DateTime: 154 | colspec.Type = typeof(DateTime?); 155 | break; 156 | case JET_coltyp.IEEEDouble: 157 | colspec.Type = typeof(double?); 158 | break; 159 | case JET_coltyp.IEEESingle: 160 | colspec.Type = typeof(float?); 161 | break; 162 | case JET_coltyp.Binary: 163 | case JET_coltyp.LongBinary: 164 | colspec.Retriever = (s, t, c) => 165 | { 166 | byte[] value = Esent.RetrieveColumn(s, t, c); 167 | if (value != null) 168 | return Convert.ToBase64String(value); 169 | else 170 | return value; 171 | }; 172 | colspec.Type = typeof(string); 173 | break; 174 | case VistaColtyp.UnsignedLong: 175 | colspec.Type = typeof(UInt32?); 176 | break; 177 | case VistaColtyp.LongLong: 178 | colspec.Type = typeof(Int64?); 179 | break; 180 | case VistaColtyp.GUID: 181 | colspec.Type = typeof(Guid?); 182 | break; 183 | case VistaColtyp.UnsignedShort: 184 | colspec.Type = typeof(UInt16?); 185 | break; 186 | default: 187 | colspec.Retriever = (s, t, c) => "ERROR: unhandled type " + Enum.GetName(typeof(JET_coltyp), column.Coltyp) + "(" + (int)column.Coltyp + ")"; 188 | colspec.Type = typeof(string); 189 | break; 190 | } 191 | 192 | if (colspec.Retriever == null) 193 | { 194 | colspec.Retriever = ColumnRetrievers.ForType(colspec.Type); 195 | } 196 | 197 | tableDef.Add(colspec); 198 | } 199 | 200 | return tableDef; 201 | } 202 | 203 | public IEnumerable> GetColumnNamesAndTypes(string tableName) 204 | { 205 | if (string.IsNullOrEmpty(tableName)) 206 | { 207 | yield break; 208 | } 209 | 210 | if (!m_tableDefs.ContainsKey(tableName)) 211 | { 212 | LoadTableDef(tableName); 213 | } 214 | 215 | foreach (var colspec in m_tableDefs[tableName]) 216 | { 217 | yield return new KeyValuePair(colspec.Name, colspec.Type); 218 | } 219 | } 220 | 221 | public IEnumerable> GetRows(string tableName, string indexName = null, int startRow = 0, int rowCount = -1) 222 | { 223 | if (!m_tableDefs.ContainsKey(tableName)) 224 | LoadTableDef(tableName); 225 | 226 | using (var table = new Table(m_sesid, m_dbid, tableName, OpenTableGrbit.ReadOnly)) 227 | { 228 | var colspec = new List>(); 229 | 230 | try 231 | { 232 | Esent.JetSetCurrentIndex2(m_sesid, table, indexName, SetCurrentIndexGrbit.MoveFirst); 233 | 234 | if (startRow != 0) 235 | { 236 | Esent.JetMove(m_sesid, table, startRow, MoveGrbit.None); 237 | } 238 | } 239 | catch (EsentNoCurrentRecordException) 240 | { 241 | // Return an empty set. 242 | yield break; 243 | } 244 | 245 | int rowNumber = 0; 246 | do 247 | { 248 | var values = new List(); 249 | 250 | object value = null; 251 | 252 | foreach (var column in m_tableDefs[tableName]) 253 | { 254 | try 255 | { 256 | value = column.Retriever(m_sesid, table, column.ColumnId); 257 | } 258 | catch (Microsoft.Isam.Esent.Interop.EsentNoCurrentRecordException) 259 | { 260 | // If we get this on row 0, it means there are no rows. 261 | if (rowNumber == 0) 262 | yield break; 263 | else 264 | throw; 265 | } 266 | values.Add(value); 267 | } 268 | 269 | yield return values; 270 | 271 | rowNumber++; 272 | if (rowNumber == rowCount) 273 | yield break; 274 | } 275 | while (Esent.TryMoveNext(m_sesid, table)); 276 | } 277 | } // GetRows 278 | 279 | public IEnumerable GetIndexes(string tableName) 280 | { 281 | foreach (IndexInfo info in Esent.GetTableIndexes(m_sesid, m_dbid, tableName)) 282 | { 283 | yield return info.Name; 284 | } 285 | } 286 | 287 | private IEnumerable GetIndexTableDef(string tableName, string indexName) 288 | { 289 | JET_INDEXLIST indexList; 290 | Esent.JetGetIndexInfo(m_sesid, m_dbid, tableName, indexName, out indexList, JET_IdxInfo.List); 291 | 292 | // Unfortunately, Esent.GetTableColumns doesn't work on the temporary table returned by 293 | // JetGetIndexInfo, but the JET_INDEXLIST and the documentation have all the 294 | // information we need. 295 | var tableDef = new ColSpec[] { 296 | new ColSpec 297 | { 298 | Name = "Columns", 299 | Type = typeof(Int32?), 300 | ColumnId = indexList.columnidcColumn 301 | }, 302 | new ColSpec 303 | { 304 | Name = "Entries", 305 | Type = typeof(Int32?), 306 | ColumnId = indexList.columnidcEntry 307 | }, 308 | new ColSpec 309 | { 310 | Name = "UniqueKeys", 311 | Type = typeof(Int32?), 312 | ColumnId = indexList.columnidcKey 313 | }, 314 | new ColSpec { 315 | Name = "Coltyp", 316 | Type = typeof(Int32?), 317 | ColumnId = indexList.columnidcoltyp 318 | }, 319 | new ColSpec { 320 | Name = "ColumnId", 321 | Type = typeof(Int32?), 322 | ColumnId = indexList.columnidcolumnid 323 | }, 324 | new ColSpec { 325 | Name = "ColumnName", 326 | Type = typeof(string), 327 | ColumnId = indexList.columnidcolumnname 328 | }, 329 | new ColSpec { 330 | Name = "CodePage", 331 | Type = typeof(Int16?), 332 | ColumnId = indexList.columnidCp 333 | }, 334 | new ColSpec { 335 | Name = "NumPages", 336 | Type = typeof(Int32?), 337 | ColumnId = indexList.columnidcPage 338 | }, 339 | new ColSpec { 340 | Name = "grbitColumn", 341 | Type = typeof(Int32?), 342 | ColumnId = indexList.columnidgrbitColumn 343 | }, 344 | new ColSpec { 345 | Name = "grbitIndex", 346 | Type = typeof(Int32?), 347 | ColumnId = indexList.columnidgrbitIndex 348 | }, 349 | new ColSpec { 350 | Name = "iColumn", 351 | Type = typeof(Int32?), 352 | ColumnId = indexList.columnidiColumn 353 | }, 354 | new ColSpec { 355 | Name = "IndexName", 356 | Type = typeof(string), 357 | ColumnId = indexList.columnidindexname 358 | }, 359 | new ColSpec { 360 | Name = "LangId", 361 | Type = typeof(Int16?), 362 | ColumnId = indexList.columnidLangid 363 | }, 364 | new ColSpec { 365 | Name = "LCMapFlags", 366 | Type = typeof(Int32?), 367 | ColumnId = indexList.columnidLCMapFlags 368 | } 369 | }; 370 | 371 | for (int i = 0, n = tableDef.Length; i < n; i++) 372 | { 373 | tableDef[i].Retriever = ColumnRetrievers.ForType(tableDef[i].Type); 374 | } 375 | 376 | Esent.JetCloseTable(m_sesid, indexList.tableid); 377 | 378 | return tableDef; 379 | } 380 | 381 | public IEnumerable> GetIndexColumnNamesAndTypes(string tableName, string indexName) 382 | { 383 | var key = new Tuple(tableName, indexName); 384 | 385 | if (!m_indexDefs.ContainsKey(new Tuple(tableName, indexName))) 386 | { 387 | IEnumerable tableDef = GetIndexTableDef(tableName, indexName); 388 | m_indexDefs.Add(key, tableDef); 389 | } 390 | 391 | foreach (ColSpec column in m_indexDefs[key]) 392 | { 393 | yield return new KeyValuePair(column.Name, column.Type); 394 | } 395 | } 396 | 397 | public IEnumerable GetIndexInfo(string tableName, string indexName) 398 | { 399 | IEnumerable tableDef = GetIndexTableDef(tableName, indexName); 400 | 401 | JET_INDEXLIST indexList; 402 | Esent.JetGetIndexInfo(m_sesid, m_dbid, tableName, indexName, out indexList, JET_IdxInfo.List); 403 | 404 | var columnsByName = new Dictionary(); 405 | int i = 0; 406 | foreach (ColSpec column in tableDef) 407 | { 408 | columnsByName.Add(column.Name, i++); 409 | } 410 | 411 | Esent.JetMove(m_sesid, indexList.tableid, JET_Move.First, MoveGrbit.None); 412 | 413 | i = 0; 414 | do 415 | { 416 | var row = new List(); 417 | 418 | foreach (ColSpec column in tableDef) 419 | { 420 | row.Add(column.Retriever(m_sesid, indexList.tableid, column.ColumnId)); 421 | } 422 | 423 | yield return new DBRow(columnsByName, row, i++); 424 | } 425 | while (Esent.TryMoveNext(m_sesid, indexList.tableid)); 426 | 427 | Esent.JetCloseTable(m_sesid, indexList.tableid); 428 | } 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /EseView/DBRow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace EseView 5 | { 6 | public class DBRow 7 | { 8 | public DBRow(Dictionary columnIndexByName, List data, int rowIndex) 9 | { 10 | m_columnIndexByName = columnIndexByName; 11 | m_data = data; 12 | m_rowIndex = rowIndex; 13 | } 14 | 15 | public DBRow(IEnumerable> data, int rowIndex) 16 | { 17 | m_data = new List(); 18 | m_columnIndexByName = new Dictionary(); 19 | foreach (var pair in data) 20 | { 21 | m_data.Add(pair.Value); 22 | m_columnIndexByName.Add(pair.Key, m_columnIndexByName.Count); 23 | } 24 | m_rowIndex = rowIndex; 25 | } 26 | 27 | public object GetValue(string columnName) 28 | { 29 | int index = m_columnIndexByName[columnName]; 30 | return m_data[index]; 31 | } 32 | 33 | public object this[int index] 34 | { 35 | get 36 | { 37 | return m_data[index]; 38 | } 39 | } 40 | 41 | public int NumColumns 42 | { 43 | get 44 | { 45 | return m_data.Count; 46 | } 47 | } 48 | 49 | public int RowIndex 50 | { 51 | get 52 | { 53 | return m_rowIndex; 54 | } 55 | } 56 | 57 | private Dictionary m_columnIndexByName; 58 | private List m_data; 59 | private int m_rowIndex; 60 | } 61 | 62 | public class DBRowValueConverter : System.Windows.Data.IValueConverter 63 | { 64 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 65 | { 66 | var columnName = parameter as string; 67 | var row = value as DBRow; 68 | 69 | return row.GetValue(columnName); 70 | } 71 | 72 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 73 | { 74 | throw new NotImplementedException(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /EseView/DatabaseVirtualizedProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace EseView 5 | { 6 | public class DatabaseVirtualizedProvider : IVirtualizedProvider 7 | { 8 | public DatabaseVirtualizedProvider(DBReader db, string tableName, string indexName) 9 | { 10 | m_db = db; 11 | m_tableName = tableName; 12 | m_indexName = indexName; 13 | m_count = new Lazy(() => db.GetRowCount(tableName, indexName)); 14 | 15 | m_columnIndexByName = new Dictionary(); 16 | 17 | int i = 0; 18 | foreach (var col in m_db.GetColumnNamesAndTypes(tableName)) 19 | { 20 | m_columnIndexByName.Add(col.Key, i); 21 | i++; 22 | } 23 | } 24 | 25 | public int Count 26 | { 27 | get { return m_count.Value; } 28 | } 29 | 30 | public IEnumerable FetchRange(int startIndex, int count) 31 | { 32 | int i = 0; 33 | foreach (List row in m_db.GetRows(m_tableName, m_indexName, startIndex, count)) 34 | { 35 | yield return new DBRow(m_columnIndexByName, row, startIndex + i); 36 | i++; 37 | } 38 | } 39 | 40 | private DBReader m_db; 41 | private string m_tableName; 42 | private string m_indexName; 43 | private Lazy m_count; 44 | private Dictionary m_columnIndexByName; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /EseView/EseView.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {0110503B-FD68-494E-96EF-1945519CEF30} 8 | WinExe 9 | Properties 10 | EseView 11 | EseView 12 | v4.5 13 | 512 14 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 4 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\ManagedEsent\Esent.Interop.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 4.0 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | MSBuild:Compile 57 | Designer 58 | 59 | 60 | 61 | 62 | 63 | 64 | MSBuild:Compile 65 | Designer 66 | 67 | 68 | App.xaml 69 | Code 70 | 71 | 72 | 73 | 74 | 75 | 76 | MainWindow.xaml 77 | Code 78 | 79 | 80 | 81 | 82 | Code 83 | 84 | 85 | True 86 | True 87 | Resources.resx 88 | 89 | 90 | True 91 | Settings.settings 92 | True 93 | 94 | 95 | ResXFileCodeGenerator 96 | Resources.Designer.cs 97 | 98 | 99 | README.md 100 | 101 | 102 | SettingsSingleFileGenerator 103 | Settings.Designer.cs 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 118 | -------------------------------------------------------------------------------- /EseView/IVirtualizedProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace EseView 4 | { 5 | public interface IVirtualizedProvider 6 | { 7 | int Count 8 | { 9 | get; 10 | } 11 | 12 | IEnumerable FetchRange(int startIndex, int count); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /EseView/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace EseView 6 | { 7 | public class MainViewModel 8 | { 9 | public MainViewModel() 10 | { 11 | m_db = null; 12 | m_tables = new Lazy>(); 13 | m_indexes = new Dictionary>(); 14 | } 15 | 16 | public Task OpenDatabaseAsync(string filename, bool recoveryEnabled = false) 17 | { 18 | return Task.Run(() => 19 | { 20 | if (m_db != null) 21 | { 22 | m_db.Close(); 23 | } 24 | 25 | m_db = new DBReader(filename); 26 | m_db.Init(recoveryEnabled); 27 | m_tables = new Lazy>(() => new List(m_db.Tables)); 28 | }); 29 | } 30 | 31 | public List Tables 32 | { 33 | get { return m_tables.Value; } 34 | } 35 | 36 | public IEnumerable Rows(string tableName, string indexName) 37 | { 38 | if (m_db == null) 39 | { 40 | yield break; 41 | } 42 | else 43 | { 44 | var columnIndexByName = new Dictionary(); 45 | 46 | int i = 0; 47 | foreach (var col in m_db.GetColumnNamesAndTypes(tableName)) 48 | { 49 | columnIndexByName.Add(col.Key, i++); 50 | } 51 | 52 | i = 0; 53 | foreach (List row in m_db.GetRows(tableName, indexName)) 54 | { 55 | yield return new DBRow(columnIndexByName, row, i++); 56 | } 57 | } 58 | } 59 | 60 | public VirtualizedReadOnlyList VirtualRows(string tableName, string indexName) 61 | { 62 | return new VirtualizedReadOnlyList(new DatabaseVirtualizedProvider(m_db, tableName, indexName)); 63 | } 64 | 65 | public int GetRowCount(string tableName, string indexName) 66 | { 67 | return m_db.GetRowCount(tableName, indexName); 68 | } 69 | 70 | public IEnumerable> GetColumnNamesAndTypes(string tableName) 71 | { 72 | if (m_db == null || string.IsNullOrEmpty(tableName)) 73 | { 74 | return null; 75 | } 76 | return m_db.GetColumnNamesAndTypes(tableName); 77 | } 78 | 79 | public IEnumerable GetIndexes(string tableName) 80 | { 81 | if (!m_indexes.ContainsKey(tableName)) 82 | { 83 | m_indexes.Add(tableName, new List(m_db.GetIndexes(tableName))); 84 | } 85 | 86 | return m_indexes[tableName]; 87 | } 88 | 89 | public IEnumerable GetIndexInfo(string tableName, string indexName) 90 | { 91 | if (m_db == null) 92 | { 93 | return null; 94 | } 95 | return m_db.GetIndexInfo(tableName, indexName); 96 | } 97 | 98 | public IEnumerable> GetIndexColumnNamesAndTypes(string tableName, string indexName) 99 | { 100 | if (m_db == null) 101 | { 102 | return null; 103 | } 104 | return m_db.GetIndexColumnNamesAndTypes(tableName, indexName); 105 | } 106 | 107 | public void DumpTable(IEnumerable tableNames, System.IO.Stream output) 108 | { 109 | XmlDump dump = null; 110 | if (output.CanSeek) 111 | { 112 | try 113 | { 114 | output.Seek(0, System.IO.SeekOrigin.Begin); 115 | output.Flush(); 116 | dump = XmlDump.Deserialize(output); 117 | } 118 | catch (Exception) 119 | { 120 | // ignore 121 | } 122 | finally 123 | { 124 | output.SetLength(0); 125 | } 126 | } 127 | if (dump == null) 128 | { 129 | dump = new XmlDump(); 130 | } 131 | 132 | foreach (string tableName in tableNames) 133 | { 134 | dump.AddTable(m_db, tableName); 135 | } 136 | 137 | dump.Serialize(output); 138 | } 139 | 140 | public void DumpTable(string tableName, System.IO.Stream output) 141 | { 142 | DumpTable(new List { tableName }, output); 143 | } 144 | 145 | private DBReader m_db; 146 | private Lazy> m_tables; 147 | private Dictionary> m_indexes; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /EseView/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 12 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 55 | 59 | 63 | 67 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | (primary key) 82 | 83 | 84 | View Index Info 85 | 86 | 87 | 88 | 89 | 90 | 91 | View Column Info 92 | 93 | 94 | 95 | 96 | 97 | 98 | 102 | 103 | 107 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 120 | 121 | 126 | 127 | 128 | 133 | 134 | 135 | 136 | 137 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 163 | Loading Database... 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /EseView/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | 11 | namespace EseView 12 | { 13 | /// 14 | /// Interaction logic for MainWindow.xaml 15 | /// 16 | public partial class MainWindow : Window 17 | { 18 | private MainViewModel m_viewModel; 19 | private string m_filename; 20 | private string m_selectedIndex; 21 | private string m_selectedTable; 22 | 23 | private enum Mode 24 | { 25 | Data, 26 | IndexInfo, 27 | ColumnInfo 28 | } 29 | 30 | private Mode m_mode; 31 | 32 | public MainWindow() 33 | { 34 | m_selectedTable = null; 35 | m_selectedIndex = null; 36 | m_mode = Mode.Data; 37 | 38 | m_viewModel = new MainViewModel(); 39 | InitializeComponent(); 40 | 41 | TableList.DataContext = m_viewModel; 42 | TableList.SelectionChanged += TableList_SelectionChanged; 43 | 44 | StatusText.Text = "No database loaded."; 45 | 46 | var args = Environment.GetCommandLineArgs(); 47 | if (args.Length == 2) 48 | { 49 | OpenDatabase(args[1]); 50 | } 51 | else if (args.Length > 1) 52 | { 53 | MessageBox.Show("Invalid command line arguments.\n" 54 | + "usage: EseView.exe [database filename]", 55 | "Invalid command line arguments", 56 | MessageBoxButton.OK, 57 | MessageBoxImage.Error); 58 | } 59 | } 60 | 61 | void UpdateIndexList() 62 | { 63 | IndexSelector.Items.Clear(); 64 | NoIndex.FontWeight = FontWeights.Bold; 65 | IndexSelector.Items.Add(NoIndex); 66 | if (m_selectedTable != null) 67 | { 68 | foreach (string indexName in m_viewModel.GetIndexes(m_selectedTable)) 69 | { 70 | var item = new ComboBoxItem(); 71 | item.Content = indexName; 72 | IndexSelector.Items.Add(item); 73 | } 74 | } 75 | IndexSelector.SelectedItem = NoIndex; 76 | } 77 | 78 | void TableList_SelectionChanged(object sender, SelectionChangedEventArgs e) 79 | { 80 | string tableName = TableList.SelectedItem as string; 81 | m_selectedTable = tableName; 82 | m_selectedIndex = null; 83 | 84 | SetMode(Mode.Data); 85 | 86 | UpdateIndexList(); 87 | UpdateStatusText(m_selectedTable, m_selectedIndex); 88 | } 89 | 90 | void UpdateColumnDefinitions(IEnumerable> columnNamesAndTypes) 91 | { 92 | if (m_selectedTable == null) 93 | { 94 | RowGrid.Columns.Clear(); 95 | RowData.DataContext = null; 96 | return; 97 | } 98 | 99 | IEnumerable indexInfo; 100 | if (m_selectedIndex != null) 101 | { 102 | indexInfo = m_viewModel.GetIndexInfo(m_selectedTable, m_selectedIndex); 103 | } 104 | else 105 | { 106 | indexInfo = new List(); // empty 107 | } 108 | 109 | RowGrid.Columns.Clear(); 110 | 111 | foreach (KeyValuePair colspec in columnNamesAndTypes) 112 | { 113 | var cellBinding = new Binding(); 114 | cellBinding.Converter = new DBRowValueConverter(); 115 | cellBinding.ConverterParameter = colspec.Key; 116 | cellBinding.Mode = BindingMode.OneTime; 117 | 118 | FrameworkElementFactory cellFactory; 119 | if (colspec.Value == typeof(bool?)) 120 | { 121 | cellFactory = new FrameworkElementFactory(typeof(CheckBox)); 122 | cellFactory.SetBinding(CheckBox.IsCheckedProperty, cellBinding); 123 | 124 | // HACK: Don't allow changes, but don't gray it out either like IsEnabled does. 125 | cellFactory.SetValue(CheckBox.IsHitTestVisibleProperty, false); 126 | cellFactory.SetValue(CheckBox.FocusableProperty, false); 127 | } 128 | else 129 | { 130 | //cellFactory = new FrameworkElementFactory(typeof(ContentControl)); 131 | //cellFactory.SetBinding(ContentControl.ContentProperty, cellBinding); 132 | 133 | //cellFactory = new FrameworkElementFactory(typeof(TextBlock)); 134 | //cellFactory.SetBinding(TextBlock.TextProperty, cellBinding); 135 | 136 | cellFactory = new FrameworkElementFactory(typeof(TextBox)); 137 | cellFactory.SetBinding(TextBox.TextProperty, cellBinding); 138 | cellFactory.SetValue(TextBox.IsReadOnlyProperty, true); 139 | cellFactory.SetValue(TextBox.StyleProperty, FindResource("SelectableTextBlock")); 140 | } 141 | 142 | var template = new DataTemplate(); 143 | template.VisualTree = cellFactory; 144 | 145 | var gridColumn = new GridViewColumn(); 146 | gridColumn.Header = colspec.Key; 147 | gridColumn.CellTemplate = template; 148 | 149 | // Bold the column header if it's part of the current index. 150 | if (indexInfo.Any(o => o.GetValue("ColumnName").Equals(colspec.Key))) 151 | { 152 | var style = new Style(typeof(GridViewColumnHeader)); 153 | style.Setters.Add(new Setter(GridViewColumnHeader.FontWeightProperty, FontWeights.Bold)); 154 | gridColumn.HeaderContainerStyle = style; 155 | 156 | // Set the column cells to bold as well. 157 | cellFactory.SetValue(ContentControl.FontWeightProperty, FontWeights.Bold); 158 | } 159 | 160 | RowGrid.Columns.Add(gridColumn); 161 | } 162 | } 163 | 164 | private void Open_Click(object sender, RoutedEventArgs e) 165 | { 166 | var dialog = new Microsoft.Win32.OpenFileDialog(); 167 | bool? result = dialog.ShowDialog(); 168 | 169 | if (result == true) 170 | { 171 | OpenDatabase(dialog.FileName); 172 | } 173 | } 174 | 175 | private async void OpenDatabase(string fileName, bool recoveryEnabled = false) 176 | { 177 | bool retryWithRecovery = false; 178 | 179 | try 180 | { 181 | LoadingScreen.Visibility = Visibility.Visible; 182 | 183 | m_filename = fileName; 184 | await m_viewModel.OpenDatabaseAsync(fileName, recoveryEnabled); 185 | 186 | TableList.DataContext = m_viewModel.Tables; 187 | TableList.SelectedIndex = -1; 188 | 189 | Title = "EseView: " + m_filename; 190 | UpdateStatusText(null, null); 191 | 192 | m_selectedTable = null; 193 | m_selectedIndex = null; 194 | IndexInfoToggle.IsChecked = false; 195 | 196 | UpdateIndexList(); 197 | SetMode(Mode.Data); 198 | } 199 | catch (Microsoft.Isam.Esent.Interop.EsentDatabaseDirtyShutdownException) 200 | { 201 | MessageBoxResult result = MessageBox.Show( 202 | "The database was not shut down cleanly. Would you like to recover it?", 203 | "Error loading database", 204 | MessageBoxButton.YesNo, 205 | MessageBoxImage.Error); 206 | 207 | if (result == MessageBoxResult.Yes) 208 | { 209 | retryWithRecovery = true; 210 | } 211 | } 212 | catch(Exception ex) 213 | { 214 | MessageBox.Show("Error loading database: " + ex.Message, "Error loading database", MessageBoxButton.OK, MessageBoxImage.Error); 215 | } 216 | finally 217 | { 218 | LoadingScreen.Visibility = Visibility.Collapsed; 219 | } 220 | 221 | if (retryWithRecovery) 222 | { 223 | OpenDatabase(fileName, true); 224 | } 225 | } 226 | 227 | private void UpdateStatusText(string tableName, string indexName) 228 | { 229 | string text = string.Format("{0} tables.", m_viewModel.Tables.Count); 230 | if (!string.IsNullOrEmpty(tableName)) 231 | { 232 | text += string.Format(" {0} rows in current table.", m_viewModel.GetRowCount(tableName, indexName)); 233 | } 234 | StatusText.Text = text; 235 | } 236 | 237 | private void About_Click(object sender, RoutedEventArgs e) 238 | { 239 | MessageBox.Show( 240 | "EseView by Bill Fraser \n" 241 | + "\n" 242 | + "Note: this is not a Microsoft product;\n" 243 | + " this is supported in my own free time.\n" 244 | + "\n" 245 | + "https://github.com/wfraser/EseView\n" 246 | + "\n" 247 | + "Version " + System.Reflection.Assembly.GetEntryAssembly().GetName().Version 248 | , 249 | "About EseView", MessageBoxButton.OK, MessageBoxImage.Information); 250 | } 251 | 252 | private void SetIndex_Click(object sender, RoutedEventArgs e) 253 | { 254 | if (m_selectedTable == null) 255 | return; 256 | 257 | foreach (var item in IndexSelector.Items.OfType()) 258 | { 259 | item.FontWeight = FontWeights.Regular; 260 | } 261 | 262 | var selected = IndexSelector.SelectedItem as ComboBoxItem; 263 | selected.FontWeight = FontWeights.Bold; 264 | 265 | if (selected == NoIndex) 266 | { 267 | m_selectedIndex = null; 268 | } 269 | else 270 | { 271 | m_selectedIndex = selected.Content as string; 272 | } 273 | 274 | UpdateStatusText(m_selectedTable, m_selectedIndex); 275 | 276 | if (m_mode == Mode.Data || m_mode == Mode.IndexInfo) 277 | { 278 | // Update the display 279 | SetMode(m_mode); 280 | } 281 | } 282 | 283 | private void IndexInfo_Click(object sender, RoutedEventArgs e) 284 | { 285 | if (m_selectedTable == null) 286 | { 287 | IndexInfoToggle.IsChecked = false; 288 | return; 289 | } 290 | 291 | if (ColumnInfoToggle.IsChecked.GetValueOrDefault(false)) 292 | { 293 | ColumnInfoToggle.IsChecked = false; 294 | } 295 | 296 | if (IndexInfoToggle.IsChecked.GetValueOrDefault(false)) 297 | { 298 | SetMode(Mode.IndexInfo); 299 | } 300 | else 301 | { 302 | SetMode(Mode.Data); 303 | } 304 | } 305 | 306 | private void ColumnInfo_Click(object sender, RoutedEventArgs e) 307 | { 308 | if (m_selectedTable == null) 309 | { 310 | ColumnInfoToggle.IsChecked = false; 311 | return; 312 | } 313 | 314 | if (IndexInfoToggle.IsChecked.GetValueOrDefault(false)) 315 | { 316 | IndexInfoToggle.IsChecked = false; 317 | } 318 | 319 | if (ColumnInfoToggle.IsChecked.GetValueOrDefault(false)) 320 | { 321 | SetMode(Mode.ColumnInfo); 322 | } 323 | else 324 | { 325 | SetMode(Mode.Data); 326 | } 327 | } 328 | 329 | void SetMode(Mode mode, bool virtualizing = true) 330 | { 331 | m_mode = mode; 332 | switch (mode) 333 | { 334 | case Mode.Data: 335 | IndexInfoToggle.IsChecked = false; 336 | ColumnInfoToggle.IsChecked = false; 337 | UpdateColumnDefinitions(m_viewModel.GetColumnNamesAndTypes(m_selectedTable)); 338 | if (virtualizing) 339 | RowData.DataContext = m_viewModel.VirtualRows(m_selectedTable, m_selectedIndex); 340 | else 341 | RowData.DataContext = m_viewModel.Rows(m_selectedTable, m_selectedIndex); 342 | break; 343 | case Mode.IndexInfo: 344 | IndexInfoToggle.IsChecked = true; 345 | ColumnInfoToggle.IsChecked = false; 346 | UpdateColumnDefinitions(m_viewModel.GetIndexColumnNamesAndTypes(m_selectedTable, m_selectedIndex)); 347 | RowData.DataContext = m_viewModel.GetIndexInfo(m_selectedTable, m_selectedIndex); 348 | break; 349 | case Mode.ColumnInfo: 350 | IndexInfoToggle.IsChecked = false; 351 | ColumnInfoToggle.IsChecked = true; 352 | //TODO 353 | break; 354 | } 355 | } 356 | 357 | private void Search_Click(object sender, RoutedEventArgs e) 358 | { 359 | Search(); 360 | } 361 | 362 | private void Search() 363 | { 364 | bool caseSensitive = SearchCaseSensitive.IsChecked.GetValueOrDefault(false); 365 | bool isRegex = SearchRegex.IsChecked.GetValueOrDefault(false); 366 | 367 | string searchTerm = SearchBox.Text; 368 | if (!isRegex && !caseSensitive) 369 | { 370 | searchTerm = searchTerm.ToLower(); 371 | } 372 | 373 | for (int rowIndex = RowData.SelectedIndex + 1; rowIndex < RowData.Items.Count; rowIndex++) 374 | { 375 | DBRow row = (DBRow)RowData.Items[rowIndex]; 376 | 377 | for (int col = 0; col < row.NumColumns; col++) 378 | { 379 | object value = row[col]; 380 | if (value == null) 381 | continue; 382 | 383 | string strValue = value.ToString(); 384 | 385 | bool match = false; 386 | if (SearchRegex.IsChecked.GetValueOrDefault(false)) 387 | { 388 | match = System.Text.RegularExpressions.Regex.Match( 389 | strValue, 390 | searchTerm, 391 | caseSensitive 392 | ? System.Text.RegularExpressions.RegexOptions.None 393 | : System.Text.RegularExpressions.RegexOptions.IgnoreCase 394 | ).Success; 395 | } 396 | else 397 | { 398 | string haystack = strValue; 399 | if (!caseSensitive) 400 | { 401 | haystack = haystack.ToLower(); 402 | } 403 | 404 | match = haystack.Contains(searchTerm); 405 | } 406 | 407 | if (match) 408 | { 409 | ListViewItem viewItem = (ListViewItem)RowData.ItemContainerGenerator.ContainerFromItem(row); 410 | if (viewItem == null) 411 | { 412 | RowData.ScrollIntoView(row); 413 | viewItem = (ListViewItem)RowData.ItemContainerGenerator.ContainerFromItem(row); 414 | } 415 | 416 | if (viewItem != null) 417 | { 418 | RowData.SelectedItem = null; 419 | viewItem.IsSelected = true; 420 | } 421 | 422 | return; 423 | } 424 | } 425 | } 426 | 427 | MessageBox.Show("No match."); 428 | } 429 | 430 | private void SearchBox_PreviewKeyUp(object sender, System.Windows.Input.KeyEventArgs e) 431 | { 432 | if (e.Key == System.Windows.Input.Key.Enter) 433 | { 434 | e.Handled = true; 435 | Search(); 436 | } 437 | } 438 | 439 | private void Dump_Click(object sender, RoutedEventArgs e) 440 | { 441 | if (string.IsNullOrEmpty(m_selectedTable)) 442 | return; 443 | 444 | var dialog = new Microsoft.Win32.SaveFileDialog(); 445 | dialog.FileName = "db.xml"; 446 | dialog.DefaultExt = "xml"; 447 | dialog.Filter = "XML (*.xml)|*.xml|All Files (*.*)|*.*"; 448 | dialog.OverwritePrompt = false; 449 | bool? result = dialog.ShowDialog(); 450 | 451 | if (result.HasValue && result.Value) 452 | { 453 | using (var output = new System.IO.FileStream(dialog.FileName, System.IO.FileMode.OpenOrCreate)) 454 | { 455 | m_viewModel.DumpTable(m_selectedTable, output); 456 | } 457 | } 458 | } 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /EseView/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("EseView")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("EseView")] 15 | [assembly: AssemblyCopyright("Copyright © William R. Fraser 2015")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.1.3.0")] 55 | [assembly: AssemblyFileVersion("1.1.3.0")] 56 | -------------------------------------------------------------------------------- /EseView/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34014 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace EseView.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EseView.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /EseView/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /EseView/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34014 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace EseView.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /EseView/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /EseView/VirtualizedReadOnlyList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace EseView 6 | { 7 | public class VirtualizedReadOnlyList : IList, IList 8 | { 9 | public VirtualizedReadOnlyList(IVirtualizedProvider provider) 10 | { 11 | m_provider = provider; 12 | m_pageMap = new Dictionary, List>>(); 13 | m_pageList = new LinkedList(); 14 | } 15 | 16 | public int Count 17 | { 18 | get { return m_provider.Count; } 19 | } 20 | 21 | public T this[int index] 22 | { 23 | get 24 | { 25 | int pageIndex = index / PageSize; 26 | int pageOffset = index % PageSize; 27 | 28 | if (m_pageMap.ContainsKey(pageIndex)) 29 | { 30 | LinkedListNode node = m_pageMap[pageIndex].Item1; 31 | m_pageList.Remove(node); 32 | m_pageList.AddFirst(node); 33 | 34 | return m_pageMap[pageIndex].Item2[pageOffset]; 35 | } 36 | 37 | if (m_pageMap.Count == MaxPagesCached) 38 | { 39 | // Evict a cached page. 40 | LinkedListNode node = m_pageList.Last; 41 | m_pageList.Remove(node); 42 | m_pageMap.Remove(node.Value); 43 | } 44 | 45 | { 46 | List page = new List(m_provider.FetchRange(pageIndex * PageSize, PageSize)); 47 | LinkedListNode node = m_pageList.AddFirst(pageIndex); 48 | m_pageMap.Add(pageIndex, new Tuple, List>(node, page)); 49 | return page[pageOffset]; 50 | } 51 | } 52 | set { throw new NotSupportedException(); } 53 | } 54 | 55 | #region Unsupported IList, IList, ICollection, ICollection methods 56 | 57 | object IList.this[int index] 58 | { 59 | get { return this[index]; } 60 | set { throw new NotSupportedException(); } 61 | } 62 | 63 | public int IndexOf(T value) 64 | { 65 | // Hack alert: special case for DBRow, which knows its own index. 66 | if (typeof(T) == typeof(DBRow)) 67 | return (value as DBRow).RowIndex; 68 | 69 | // Otherwise, this is expensive to compute. Don't bother. 70 | return -1; 71 | } 72 | 73 | int IList.IndexOf(object value) 74 | { 75 | return IndexOf((T)value); 76 | } 77 | 78 | public void Insert(int index, T value) 79 | { 80 | throw new NotSupportedException(); 81 | } 82 | 83 | void IList.Insert(int index, object value) 84 | { 85 | Insert(index, (T)value); 86 | } 87 | 88 | public void RemoveAt(int index) 89 | { 90 | throw new NotSupportedException(); 91 | } 92 | 93 | public void Add(T value) 94 | { 95 | throw new NotSupportedException(); 96 | } 97 | 98 | int IList.Add(object value) 99 | { 100 | throw new NotSupportedException(); 101 | } 102 | 103 | public void Clear() 104 | { 105 | throw new NotSupportedException(); 106 | } 107 | 108 | public bool Contains(T value) 109 | { 110 | return false; 111 | } 112 | 113 | bool IList.Contains(object value) 114 | { 115 | return Contains((T)value); 116 | } 117 | 118 | public bool Remove(T value) 119 | { 120 | throw new NotSupportedException(); 121 | } 122 | 123 | void IList.Remove(object value) 124 | { 125 | Remove((T)value); 126 | } 127 | 128 | public void CopyTo(T[] array, int arrayIndex) 129 | { 130 | throw new NotSupportedException(); 131 | } 132 | 133 | void ICollection.CopyTo(Array array, int index) 134 | { 135 | throw new NotSupportedException(); 136 | } 137 | 138 | #endregion 139 | 140 | #region Misc Properties 141 | 142 | public bool IsReadOnly 143 | { 144 | get { return true; } 145 | } 146 | 147 | public bool IsFixedSize 148 | { 149 | get { return true; } 150 | } 151 | 152 | public bool IsSynchronized 153 | { 154 | get { return false; } 155 | } 156 | 157 | public object SyncRoot 158 | { 159 | get { return this; } 160 | } 161 | 162 | #endregion 163 | 164 | #region IEnumerable, IEnumerable 165 | 166 | public IEnumerator GetEnumerator() 167 | { 168 | for (int i = 0, n = Count; i < n; i++) 169 | { 170 | yield return this[i]; 171 | } 172 | } 173 | 174 | IEnumerator IEnumerable.GetEnumerator() 175 | { 176 | return GetEnumerator(); 177 | } 178 | 179 | #endregion 180 | 181 | private IVirtualizedProvider m_provider; 182 | private Dictionary, List>> m_pageMap; 183 | private LinkedList m_pageList; 184 | 185 | public const int PageSize = 100; 186 | private const int MaxPagesCached = 10; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /EseView/XmlDump.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml; 8 | using System.Xml.Serialization; 9 | 10 | namespace EseView 11 | { 12 | [XmlRoot(Namespace=XmlDump.Namespace)] 13 | public class XmlDump 14 | { 15 | public const string Namespace = "http://www.github.com/wfraser/EseView/XmlDump/1/0"; 16 | 17 | public XmlDump() 18 | { 19 | } 20 | 21 | public void AddTable(DBReader database, string tableName) 22 | { 23 | string indexName = null; 24 | int slashIndex = tableName.IndexOf('/'); 25 | if (slashIndex != -1) 26 | { 27 | indexName = tableName.Substring(slashIndex + 1); 28 | tableName = tableName.Substring(0, slashIndex); 29 | } 30 | 31 | var tableXml = new Table(); 32 | tableXml.Rows = new List(); 33 | 34 | tableXml.Name = tableName; 35 | tableXml.RowCount = database.GetRowCount(tableName, indexName); 36 | 37 | var columns = database.GetColumnNamesAndTypes(tableName).ToList(); 38 | foreach (var row in database.GetRows(tableName, indexName)) 39 | { 40 | var rowXml = new Row(); 41 | rowXml.Columns = new List(); 42 | 43 | for (int i = 0; i < columns.Count; i++) 44 | { 45 | var columnXml = new Column(); 46 | 47 | columnXml.Name = columns[i].Key; 48 | 49 | if (row[i] != null) 50 | { 51 | columnXml.Value = row[i].ToString(); 52 | 53 | // Strip off trailing embedded nulls. 54 | // These are caused by applications that insert C-strings into the database with their null terminator included. 55 | if (columnXml.Value.Last() == '\0') 56 | { 57 | columnXml.Value = columnXml.Value.Substring(0, columnXml.Value.Length - 1); 58 | } 59 | } 60 | 61 | rowXml.Columns.Add(columnXml); 62 | } 63 | 64 | tableXml.Rows.Add(rowXml); 65 | } 66 | 67 | if (Tables == null) 68 | Tables = new List
(); 69 | 70 | Tables.Add(tableXml); 71 | } 72 | 73 | public void Serialize(Stream stream) 74 | { 75 | var settings = new XmlWriterSettings(); 76 | settings.Indent = true; 77 | 78 | // Strings can contain binary data, so escape all the things. 79 | settings.NewLineHandling = NewLineHandling.Entitize; 80 | 81 | // Without this, things like embedded nulls cause an exception. With this, it entitizes them. 82 | settings.CheckCharacters = false; 83 | 84 | using (var writer = XmlWriter.Create(stream, settings)) 85 | { 86 | var serializer = new XmlSerializer(typeof(XmlDump), Namespace); 87 | serializer.Serialize(writer, this); 88 | } 89 | } 90 | 91 | public static XmlDump Deserialize(Stream stream) 92 | { 93 | var settings = new XmlReaderSettings(); 94 | 95 | // Don't choke on "�" caused by embedded nulls. 96 | settings.CheckCharacters = false; 97 | 98 | using (var reader = XmlReader.Create(stream, settings)) 99 | { 100 | var serializer = new XmlSerializer(typeof(XmlDump)); 101 | var dump = (XmlDump)serializer.Deserialize(reader); 102 | return dump; 103 | } 104 | } 105 | 106 | [XmlElement("Table")] 107 | public List
Tables 108 | { 109 | get; 110 | set; 111 | } 112 | 113 | public class Table 114 | { 115 | [XmlAttribute] 116 | public string Name 117 | { 118 | get; 119 | set; 120 | } 121 | 122 | [XmlAttribute] 123 | public int RowCount 124 | { 125 | get; 126 | set; 127 | } 128 | 129 | [XmlElement("Row")] 130 | public List Rows 131 | { 132 | get; 133 | set; 134 | } 135 | } 136 | 137 | public class Row 138 | { 139 | [XmlElement("Column")] 140 | public List Columns 141 | { 142 | get; 143 | set; 144 | } 145 | } 146 | 147 | public class Column 148 | { 149 | [XmlAttribute] 150 | public string Name 151 | { 152 | get; 153 | set; 154 | } 155 | 156 | [XmlAttribute] 157 | public string Value 158 | { 159 | get; 160 | set; 161 | } 162 | 163 | [XmlIgnore] 164 | public bool ValueSpecified 165 | { 166 | get { return (Value != null); } 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 William R. Fraser 2 | 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /ManagedEsent/Esent.Interop.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wfraser/EseView/af7519bf216bd3e23d6a79918a42c522c1b7b10b/ManagedEsent/Esent.Interop.dll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EseView 2 | ======= 3 | 4 | EseView is a graphical database browser for Windows, written in C# and using WPF. 5 | 6 | EseView is designed to display databases made using the Microsoft Extensible Storage Engine (ESE), also known as JET. 7 | (See [MSDN](https://msdn.microsoft.com/en-us/library/gg269259%28v=exchg.10%29.aspx) for more information about ESE.) 8 | 9 | The ESENT Managed Interop library ([hosted on CodePlex](https://managedesent.codeplex.com/)) is used to access the ESE API from C#. 10 | 11 | Implemented Features 12 | -------------------- 13 | * Basic functionality! :) 14 | * Data virtualized display, so even opening a massive database is quite snappy. 15 | * Browsing by indexes 16 | * Viewing index specifications 17 | 18 | Planned Features 19 | ---------------- 20 | * Viewing column specifications 21 | * Enabling/Disabling columns 22 | 23 | This program is not supported or endorsed by Microsoft Corporation in any way. 24 | No warranty, express or implied, exists or arises from you using this program. 25 | Use at your own risk. --------------------------------------------------------------------------------