views:

483

answers:

2

My current project is to take information from an OleDbDatabase and .CSV files and place it all into a larger OleDbDatabase.

I have currently read in all the information I need from both .CSV files, and the OleDbDatabase into DataTables.... Where it is getting hairy is writing all of the information back to another OleDbDatabase.

Right now my current method is to do something like this:

    OleDbTransaction myTransaction = null;

    try
    {
        OleDbConnection conn = new OleDbConnection("PROVIDER=Microsoft.Jet.OLEDB.4.0;" +
                                                   "Data Source=" + Database);
        conn.Open();
        OleDbCommand command = conn.CreateCommand();
        string strSQL;

        command.Transaction = myTransaction;

        strSQL = "Insert into TABLE " +
                 "(FirstName, LastName) values ('" +
                 FirstName + "', '" + LastName + "')";

        command.CommandType = CommandType.Text;
        command.CommandText = strSQL;

        command.ExecuteNonQuery();

        conn.close();

    catch (Exception)
        {
            // IF invalid data is entered, rolls back the database
            myTransaction.Rollback();
        }

Of course, this is very basic and I'm using an SQL command to commit my transactions to a connection. My problem is I could do this, but I have about 200 fields that need inserted over several tables. I'm willing to do the leg work if that's the only way to go. But I feel like there is an easier method. Is there anything in LINQ that could help me out with this?

+1  A: 

If the column names in the DataTable match exactly to the column names in the destination table, then you might be able to use a OleDbCommandBuilder (Warning: I haven't tested this yet). One area you may run into problems is if the data types of the source data table do not match those of the destination table (e.g if the source column data types are all strings).

EDIT I revised my original code in a number of ways. First, I switched to using the Merge method on a DataTable. This allowed me to skip using the LoadDataRow in a loop.

using ( var conn = new OleDbConnection( destinationConnString ) )
{
    //query off the destination table. Could also use Select Col1, Col2..
    //if you were not going to insert into all columns.
    const string selectSql = "Select * From [DestinationTable]";

    using ( var adapter = new OleDbDataAdapter( selectSql, conn ) )
    {
        using ( var builder = new OleDbCommandBuilder( adapter ) )
        {
            conn.Open();

            var destinationTable = new DataTable();
            adapter.Fill( destinationTable );

            //if the column names do not match exactly, then they 
            //will be skipped
            destinationTable.Merge( sourceDataTable, true, MissingSchemaAction.Ignore );

            //ensure that all rows are marked as Added.
            destinationTable.AcceptChanges();
            foreach ( DataRow row in destinationTable.Rows )
                row.SetAdded();

            builder.QuotePrefix = "[";
            builder.QuoteSuffix= "]";

            //forces the builder to rebuild its insert command
            builder.GetInsertCommand();
            adapter.Update( destinationTable );
        }
    }
}

ADDITION An alternate solution would be to use a framework like FileHelpers to read the CSV file and post it into your database. It does have an OleDbStorage DataLink for posting into OleDb sources. See the SqlServerStorage InsertRecord example to see how (in the end substitute OleDbStorage for SqlServerStorage).

Thomas
Currently I have a method that is similar to this, but working slightly better. This still seems sloppy, but it's similar to the solution I have come to. I'm trying to figure out something a bit cleaner.
jsmith
@jsmith - Why do you consider it sloppy? Without using an external library, what would a "cleaner" solution look like? Any other method is going to entail looping through the columns, building an insert statement then looping through the columns and rows and doing the insert. IMO, that is far more cumbersome and requires more code. I have updated my post to suggest FileHelpers which you might find "cleaner". It certainly should make parsing the CSV easier.
Thomas
@Thomas- I do not know why I consider it sloppy. "This seems sloppy" is what I said. I do not know if it is or not, that's why I'm asking the question. If I knew a "cleaner" solution, I wouldn't be asking for one. I'm simply trying to find the best solution. Right now, this is it, but it simply feels like it could be better. However, I think I am wrong in feeling that.
jsmith
@jsmith - Ok. Again you might look at FileHelpers which is designed to parse files and push it into other sources.
Thomas
@Thomas- I plan on accepting your answer, but can you correct your code a bit? Some of the syntax/code here does not work. I planned on adding an answer myself so you could see how it works. But if I accept this answer I want people in the future to not have to go through the work of figuring out what I had to, just about everywhere online had something a little wrong that didn't work. So it took a bit to figure it out.
jsmith
@jsmith - Sure. I'll update it today.
Thomas
@jsmith - Updated. I switched to using the Merge method as a means of combining the schema from the destination with the schema from the source.
Thomas
A: 

It sounds like you have many .mdb and .csv that you need to merge into a single .mdb. This answer is running with that assumption, and that you have SQL Server available to you. If you don't, then consider downloading SQL Express.

Use SQL Server to act as the broker between your multiple datasources and your target datastore. Script each datasource as an insert into a SQL Server holding table. When all data is loaded into the holding table, perform a final push into your target Access datastore.

Consider these steps:

  • In SQL Server, create a holding table for the imported CSV data.

CREATE TABLE CsvImport (CustomerID smallint, LastName varchar(40), BirthDate smalldatetime)

  • Create a stored proc whose job will be to read a given CSV filepath, and insert into a SQL Server table.

CREATE PROC ReadFromCSV @CsvFilePath varchar(1000) AS BULK INSERT CsvImport FROM @CsvFilePath --'c:\some.csv' WITH ( FIELDTERMINATOR = ',', --your own specific terminators should go here ROWTERMINATOR = '\n' ) GO

  • Create a script to call this stored proc for each .csv file you have on disk. Perhaps some Excel trickery or filesystem dir piped commands can help you create these statements.


exec ReadFromCSV 'c:\1.csv

  • For each .mdb datasource, create a temp linked server.


DECLARE @MdbFilePath varchar(1000); SELECT @MdbFilePath = 'C:\MyMdb1.mdb'; EXEC master.dbo.sp_addlinkedserver @server = N'MY_ACCESS_DB_', @srvproduct=N'Access', @provider=N'Microsoft.Jet.OLEDB.4.0', @datasrc=@MdbFilePath

-- grab the relevant data
--your data's now in the table...
INSERT CsvImport(CustomerID, 

SELECT [CustomerID]
  ,[LastName]
  ,[BirthDate]
FROM [MY_ACCESS_DB_]...[Customers]

--remove the linked server 
EXEC master.dbo.sp_dropserver @server=N'MY_ACCESS_DB_', @droplogins='droplogins'
  • When you're done importing data into that holding table, create a Linked Server in your SQL Server instance. This is the target datastore. SELECT the data from SQL Server into Access.


EXEC master.dbo.sp_addlinkedserver @server = N'MY_ACCESS_TARGET', @srvproduct=N'Access', @provider=N'Microsoft.Jet.OLEDB.4.0', @datasrc='C:\Target.mdb'

INSERT INTO [MY_ACCESS_TARGET]...[Customer]
           ([CustomerID]
           ,[LastName]
           ,[BirthDate])
SELECT   Customer,
         LastName,
         BirthDate
FROM     CsvImport
p.campbell