views:

493

answers:

4

NEWEST EDIT!!!

PLEASE... I have been through many permutations of this code. I am attempting to take some certain related records from a database (SQL server) and export to XML (which seems to work!), and take them to a disconnected copy of that database, and import them to that database, merging them by primary key (updating if the key exists and inserting if it doesn't).

There are many tables in the database, and I want all the tables that relate to a certain list of entities that have been changed. I figured out early on that I couldn't do a large complicated query with joins to get all the columns I wanted to fill my export DataSet, because I lost all the table structure. I also had the Visual Studio 2005 DataSet designer create a typed dataset, which I'm not sure I need to do, and please tell me if I shouldn't.

Here is what I'm doing to export the data to xml:



public void exportData(string filename, List sxOrgs) {

    MyGeneratedDataSet ds = new MyGeneratedDataSet();
    SqlDataAdapter adapter = new SqlDataAdapter();
    String query;
    String orgList = "(";
    //create the string query list of sxOrgs

    foreach (String sx in sxOrgs)
    {
        orgList += "'" + sx + "', ";
    }

    orgList = orgList.Remove(orgList.Length - 2);
    orgList += ")";

    try
        {

        //tblOrganization
        query = "select * from tblOrganization where tblOrganization.sxOrganization in " 
             + orgList;
        adapter.SelectCommand = new SqlCommand(query, Connectivity.Connection());
        adapter.Fill(ds.tblOrganization);

        //tblCategory
        query = "select * from tblCategory where sxCategory in " +
            "(select sxCategory from lnkOrganizationCategory " +
            "where lnkOrganizationCategory.sxOrganization in " +
            orgList + ")";
        adapter.SelectCommand = new SqlCommand(query, Connectivity.Connection());
        adapter.Fill(ds.tblCategory);

        //lnkOrganizationCategory
        query = "select * from lnkOrganizationCategory where sxOrganization in " + orgList;
            adapter.SelectCommand = new SqlCommand(query, Connectivity.Connection());
            adapter.Fill(ds.lnkOrganizationCategory);

        //tblContact
        query = "select * from tblContact where sxContact in " +
            "(select sxContact from lnkOrganizationContact " +
            "where lnkOrganizationContact.sxOrganization in " +
            orgList + ")";
        adapter.SelectCommand = new SqlCommand(query, Connectivity.Connection());
        adapter.Fill(ds.tblContact);

        //lnkOrganizationContact
        query = "select * from lnkOrganizationContact where lnkOrganizationContact.sxOrganization in " + orgList;
        adapter.SelectCommand = new SqlCommand(query, Connectivity.Connection());
        adapter.Fill(ds.lnkOrganizationContact);
    } catch(Exception ex) {
        Debug.WriteLine(ex.Message);
    }

    ds.WriteXml(filename, XmlWriteMode.IgnoreSchema);
}

As you can see, I'm reusing the same TableAdapeter over and over and the XML that is written at the end seems to look fine and nice, having a tag for each table, and within it a tag for each field, exactly what I want, and to be able to merge it back in. Here is the import code:



//reads data from an xml file and merges it into existing db
public static void ImportData(string data)
{
    try
    {

    MyGeneratedDataSet newData = new MyGeneratedDataSet();

    StreamWriter sw = new StreamWriter("newdata.xml", false);
    Debug.WriteLine(data);
    sw.Write(data);
    sw.Close();
    XmlTextReader reader = new XmlTextReader(new MemoryStream(ASCIIEncoding.Default.GetBytes(data)));
    newData.ReadXml("newData.xml");

    MyGeneratedDataSet currentData = new MyGeneratedDataSet();

    //tblOrganization
    SqlDataAdapter tblOrganizationDataAdapter = new SqlDataAdapter("select * from tblOrganization", Connectivity.Connection());
    SqlCommandBuilder tblOrganizationCommandBuilder = new SqlCommandBuilder(tblOrganizationDataAdapter);
    tblOrganizationDataAdapter.Fill(currentData, "tblOrganization");

    //tblContact
    SqlDataAdapter tblContactDataAdapter = new SqlDataAdapter("select * from tblContact", Connectivity.Connection());
    SqlCommandBuilder tblContactCommandBuilder = new SqlCommandBuilder(tblContactDataAdapter);
    tblContactDataAdapter.Fill(currentData, "tblContact");

    //tblCategory
    SqlDataAdapter tblCategoryDataAdapter = new SqlDataAdapter("select * from tblCategory", Connectivity.Connection());
    SqlCommandBuilder tblCategoryCommandBuilder = new SqlCommandBuilder(tblCategoryDataAdapter);
    tblCategoryDataAdapter.Fill(currentData, "tblCategory");

    //lnkOrganizationCategory
    SqlDataAdapter lnkOrganizationCategoryDataAdapter = new SqlDataAdapter("select * from lnkOrganizationCategory", Connectivity.Connection());
    SqlCommandBuilder lnkOrganizationCategoryCommandBuilder = new SqlCommandBuilder(lnkOrganizationCategoryDataAdapter);
    lnkOrganizationCategoryDataAdapter.Fill(currentData, "lnkOrganizationCategory");

    //lnkOrganizationContact
    SqlDataAdapter lnkOrganizationContactDataAdapter = new SqlDataAdapter("select * from lnkOrganizationContact", Connectivity.Connection());
    SqlCommandBuilder lnkOrganizationContactCommandBuilder = new SqlCommandBuilder(lnkOrganizationContactDataAdapter);
    lnkOrganizationContactDataAdapter.Fill(currentData, "lnkOrganizationContact");

    Debug.WriteLine(tblOrganizationDataAdapter.SelectCommand.CommandText);
    Debug.WriteLine(tblOrganizationDataAdapter.UpdateCommand.CommandText);

    currentData.Merge(newData);

    tblOrganizationDataAdapter.Update(currentData);
    tblContactDataAdapter.Update(currentData);
    tblCategoryDataAdapter.Update(currentData);
    lnkOrganizationCategoryDataAdapter.Update(currentData);
    lnkOrganizationContactDataAdapter.Update(currentData);


    } catch (Exception ex) {
        Debug.WriteLine(ex.Message);
    }

}

At the moment, the Debug.WriteLine near the end of the import function shows that the UpdateCommand of the tblOrganizationTableAdapter is null. The visual designer had told me that it created that for, though if all I have to do is create it, sure, I'll do that, but I've rewritten this so many times now (and there's lots more tables than this), and I still don't understand what's going on. How SHOULD I be doing this?!

THANK YOU SO MUCH! Joshua

A: 

update would need to be called at some point:

adapter.Update(currentData, "table1")

in general its probably easier to merge 2 tables directly in sql, but your app may need to process xml files..

examples of using Merge/Update:

http://msdn.microsoft.com/en-us/library/aa325628%28VS.71%29.aspx

in t-sql, you can do this in one statement with MERGE:

http://technet.microsoft.com/en-us/library/bb510625.aspx

tweak it to suit the conditions when you want the row changed / updated

jspcal
+1  A: 

Blah:

  • Load your dataset from the xml rows.
  • Make sure the RowState flag is correct for each row:
    • Unchanged, Modified, Added, or Deleted
  • Don't call AcceptChanges(), it sets the row sate for every row to Unchanged and removes rows marked Deleted(). This is done internally by...

Code:

using (SqlDataAdapter dap = new SqlDataAdapater(myConnection, "SELECT * FROM MyTable"))
    dap.Update(myDataTable)
sphereinabox
Okay, so I've loaded the newData dataset from the xml file, and when you refer to checking the RowState, that's in the newData, right?Then I call update on the currentData dataset adapter, right? Then when do I call merge? I'm sorry, I'm confused.
Joshua
The RowState flag is a property on a DataRow that determines whether the data adapter needs to insert, update, or delete the row in the database. I don't believe you will need to select the existing data into a dataset and merge in your modified data. (of course, I've been wrong... the update statement as written may depend on the old values (especially the timestamp if there is one) and thus try to write "update foo set col1 = newvalue where pk = 1 and col1 = oldvalue" which may not overwrite if the value has changed in between...) I don't have time to respond in more detail right now.
sphereinabox
+1  A: 

What you are doing sounds right. I'm not sure what your exact problem is, but here is a breakdown of the flow:

  1. Load new data into a DataSet (like you've done)
  2. Get current data from database into DataSet (like you've done)
  3. Call CurrentData.Merge(NewData) (like you've done)
  4. Save the CurrentData back to the database (I don't see this part)

When you perform step #4, the table adapters look at each table in the DataSet and find out which rows have changed. It knows because each DataRow in each DataTable will have its DataRowState set to Added, Modified or Deleted as a result of your call to Merge(). The table adapter then performs the requested change in the underlying database and sets the DataRowState to Unchanged for the DataRow (except in the case of Deleted, of course). When this process completes, the underlying database should have all the changes in it that the NewData dataset contained.

codekaizen
Okay, so step #4... this is the adapter.Update one? But the SqlDataAdapter is out of scope, I created new ones for each table, and did adapter.Fill(currentData, "tableX");once for each of the tables, then I am doing the merge at the end after all that. Should I merge each table individually?
Joshua
No, merge the whole DataSet at once.You should have a DataAdapter for each table in the DataSet however. Then you call DataAdapter.Update() on each table in the DataSet. You should make sure that the InsertCommand, UpdateCommand and DeleteCommand properties are set before you do this, however.Another thing to note is that this code is generated for you if you use the DataSet designer in Visual Studio. I think that it generates a method called Save() on the DataSet and all of this is taken care of in that method.
codekaizen
Okay, there is no Save() in the MyDataSet class generated by Visual Studio. So you're saying I should keep the SqlDataAdapter around for each table (not letting it go out of scope), and call adapter.Update() on each one in turn after the merge of the whole dataset?THANK YOU FOR RESPONDING!
Joshua
Okay, still it doesn't seem to be working. I'm going to edit the original post to add actual code, showing changes...
Joshua
I would use the DataAdapter.Update(DataTable) method instead of the DataAdapter.Update(DataSet) method.
codekaizen
Okay, I will try that. It seems that The tblOrganizationDataAdapter.UpdateCommand is null. Do I need to create all these manually?
Joshua
You can get the designer to do it for you unless you have a non-table source (e.g. a sql query or stored proc) in your SelectCommand statement. Otherwise, yea, create by hand.
codekaizen
Since I have had the visual designer create the whole DataSet object from the db, I would like to get it create the Update. So far, it seems that my update command is null, which seems to be my problem thus far. How can I get the visual designer to create it, or if I create it by hand, does it just need to a simple update statement with all the fields? Is there something specific to the merging I need to do with the update query?
Joshua
@Joshua - it depends on the query used to define the table. Is it a straight "SELECT * FROM tbl" or something more complex?
codekaizen
In this case, as I'm populating the currentData dataset (one table at a time), it's just exactly that, a simple select, so I have all records from the current db, and I need to merge in the newData from the xml file.
Joshua
When I do configure on the visual designer for each table, it SAYS that it's creating all these functions...
Joshua
Ok, I think I see. You need to use the custom-generated table adapters, not the generic SqlDataAdapters that you are using. That is where the *Command properties are generated.
codekaizen
A: 

This code almost works. It now doesn't fail anymore, however, I end up with all duplicate rows, instead of updating via the primary key comparison!

Joshua