using System; using System.Collections.Generic; using System.Data; using System.Data.OleDb; using System.IO; using System.Linq; using System.Reflection; namespace Deltares.Soilbase { public class Database { public Database(string name) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("name"); } if (!File.Exists(name)) { throw new FileNotFoundException("File not found", name); } Name = name; } /// /// Gets or sets the name. /// /// /// The name. /// public string Name { get; private set; } /// /// Creates a new database using the initial database which is stored as an embedded resource in this assembly. /// /// File name and path to write the database to. /// An initialized database object public static Database Create(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) { throw new ArgumentNullException("fileName"); } if (File.Exists(fileName)) { throw new ArgumentException("File already exists"); } Type type = typeof(Database); CreateFile(type.Assembly, type.Namespace + ".blank.mdb", fileName); return new Database(fileName); } /// /// Gets the update command. /// /// Name of the table. /// The column names. /// public Action>> GetUpdateCommand(string tableName, params string[] columnNames) { ThrowIfTableNameIsEmpty(tableName); if (columnNames.Length <= 1) { throw new ArgumentException("No attributes specified. There should be at least two attributes. One column and one id field and their values"); } var commandText = CreateUpdateCommandText(tableName, columnNames); return attributes => { ThrowIfAtrributesDontMatch(columnNames, attributes); ExecuteNonQuery((cmd) => { cmd.CommandText = commandText; // add named parameters cmd.Parameters.AddRange(CreateParameterArray(attributes)); }); }; } /// /// Gets the insert command. /// /// /// Name of the table. /// The column names. /// public Func>, T> GetInsertCommand(string tableName, params string[] columnNames) { ThrowIfTableNameIsEmpty(tableName); if (columnNames.Length == 0) { throw new ArgumentException("No column names specified. There should be at least one table column name specified."); } var commandText = CreateInsertCommandText(tableName, columnNames); return attributes => { ThrowIfAtrributesDontMatch(columnNames, attributes); object id = null; ExecuteOleDbCommand((cmd) => { cmd.CommandText = commandText; // add named parameters cmd.Parameters.AddRange(CreateParameterArray(attributes)); cmd.ExecuteNonQuery(); cmd.CommandText = "SELECT @@identity"; OleDbDataReader dr = cmd.ExecuteReader(); while (dr != null && dr.Read()) { id = dr[0]; } }); return (T) id; }; } public int GetRecordCount(string tableName) { int count = 0; ExecuteOleDbCommand(cmd => { cmd.CommandText = string.Format("SELECT COUNT(*) FROM [{0}]", tableName); cmd.ExecuteNonQuery(); OleDbDataReader dr = cmd.ExecuteReader(); while (dr != null && dr.Read()) { count = (int) dr[0]; } }); return count; } /// /// Gets the connection string. /// protected internal virtual string ConnectionString { get { return string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}", Name); } } /// /// Executes a command on the database. /// /// The action. protected internal virtual void ExecuteCommand(Action action) { using (var conn = new OleDbConnection(ConnectionString)) { conn.Open(); action(conn); } } /// /// Executes a ole db command. /// /// The action. protected internal virtual void ExecuteOleDbCommand(Action action) { ExecuteCommand((conn) => { using (OleDbCommand cmd = conn.CreateCommand()) { action(cmd); } }); } /// /// Executes a non query oledb command. /// /// The action. protected internal virtual void ExecuteNonQuery(Action action) { ExecuteOleDbCommand(cmd => { action(cmd); cmd.ExecuteNonQuery(); }); } /// /// Executes the non query batch. (not tested yet...) /// /// The actions. protected internal virtual void ExecuteNonQueryBatch(IEnumerable> actions) { ExecuteCommand((conn) => { foreach (var action in actions) { using (OleDbCommand cmd = conn.CreateCommand()) { action(cmd); cmd.ExecuteNonQuery(); } } }); } /// /// Gets the data table. /// /// Name of the table. /// protected internal DataTable GetDataTable(string tableName) { ThrowIfTableNameIsEmpty(tableName); var dataTable = new DataTable(tableName); ExecuteCommand((conn) => { var query = CreateSelectCommandText(tableName); var adapter = new OleDbDataAdapter(query, conn); adapter.Fill(dataTable); }); return dataTable; } /// /// Throws if table name is empty. /// /// Name of the table. internal static void ThrowIfTableNameIsEmpty(string tableName) { if (string.IsNullOrWhiteSpace(tableName)) { throw new ArgumentException("tableName"); } } /// /// Throws if atrributes dont match. /// /// The column names. /// The attributes. internal static void ThrowIfAtrributesDontMatch(string[] columnNames, IEnumerable> attributes) { int attrCount = 0; foreach (var attribute in attributes) { if (!columnNames.Contains(attribute.Key)) { string message = string.Format("Invalid attribute list. The attribute '{0}' is not specified in the set of column names", attribute.Key); throw new ArgumentException(message); } attrCount++; } if (attrCount != columnNames.Length) { throw new ArgumentException( "Invalid attribute list. The length of the attribute list is not the same as list length of the column names"); } } /// /// Creates the select command text. /// /// Name of the table. /// internal static string CreateSelectCommandText(string tableName) { return "SELECT * FROM [" + tableName + "]"; } /// /// Creates the update command text. /// /// Name of the table. /// The column names. /// internal static string CreateUpdateCommandText(string tableName, string[] columnNames) { // create sql update part with placeholders // sql template: UPDATE [tableName] SET [col1] = ?, [col2] = ? WHERE [idCol] = ? IEnumerable columns = columnNames.Select(attr => attr).Take(columnNames.Length - 1); var formattedColumns = CreateSqlColumnsPart(columns, "[{0}] = ?"); string idField = columnNames.Last(); return string.Format("UPDATE [{0}] SET {1} WHERE [{2}] = ?", tableName, formattedColumns, idField); } /// /// Creates the insert command text. /// /// Name of the table. /// The column names. /// internal static string CreateInsertCommandText(string tableName, string[] columnNames) { // create sql update part with placeholders // sql template: INSERT INTO [tableName] ([col1], [col2]) VALUES (@col1, @col2) var columns = CreateSqlColumnsPart(columnNames.Select(attr => attr), "[{0}]"); var valueParams = CreateSqlColumnsPart(columnNames.Select(attr => attr), "@{0}"); return string.Format("INSERT INTO [{0}] ({1}) VALUES ({2})", tableName, columns, valueParams); } /// /// Converts a string list to a csv string line using the specified string format pattern /// /// The attributes. /// The pattern. /// internal static string CreateSqlColumnsPart(IEnumerable attributes, string pattern) { return string.Join(", ", attributes.Select(attr => string.Format(pattern, attr)).ToArray()); } /// /// Converts an key value pair list to an oledb parameter array. /// /// The attributes. /// internal static OleDbParameter[] CreateParameterArray(IEnumerable> attributes) { return attributes.Select(attr => new OleDbParameter("@" + attr.Key, attr.Value)).ToArray(); } private static void CopyStream(Stream input, Stream output) { // Insert null checking here for production var buffer = new byte[8192]; int bytesRead; while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0) { output.Write(buffer, 0, bytesRead); } } private static void CreateFile(Assembly assembly, string resourceName, string path) { using (Stream input = assembly.GetManifestResourceStream(resourceName)) { using (Stream output = File.Create(path)) { CopyStream(input, output); } } } } }