Вопрос

I'm trying to perform a "bulk" insert a set of rules into a table of rules. The table has the following structure:

RuleId int,
Rule nvarchar(256),
Domain nvarchar(256),
RuleTypeId int,
ExpirationDate DateTime

In my insertion function I try to set the columns of a row after I've obtained the schema of the table, but it appears that the row does not contain the specified column (note that I don't specify the RuleId since it's auto-generated):

private void InsertRulesXml(List<Rule> rules)
{
    using (DataSet ds = new DataSet())
    {
        using (DataTable rulesTable = GetSchemeForTable("RulesTable"))
        {
            // Add the rules to the table
            foreach (Rule rule in rules)
            {
                DataRow row = rulesTable.NewRow();

                // When I try to set the specific column it tells me that the column doesn't exist
                row["Rule"] = rule.Rule;
                row["Domain"] = rule.Domain;
                row["RuleTypeId"] = rule.RuleTypeId;
                row["ExpirationDate"] = rule.Expiration;

                rulesTable.Rows.Add(row);
            }
            ds.Tables.Add(rulesTable);

            // Convert the data to XML
            StringBuilder sb = new StringBuilder();
            using (StringWriter sw = new StringWriter(sb))
            {
                rulesTable.WriteXml(sw, System.Data.XmlWriteMode.WriteSchema);
            }

            // Insert the XML rules
            InsertUpdateRulesXml(sb.ToString());
        }
    }
}

Here is the function that gets the schema for the specified table:

private DataTable GetSchemeForTable(string tableName)
{
    DataTable ret = null;

    using (SqlConnection connection = GetContentScraperSqlConnection())
    {
        try
        {
            connection.Open();
            SqlCommand command = connection.CreateCommand();
            command.CommandText = string.Format("SELECT TOP 0 [{0}].* FROM [{0}] WHERE 1 = 2;", tableName);
            using (IDataReader reader = command.ExecuteReader(CommandBehavior.SchemaOnly))
            {
                ret = reader.GetSchemaTable();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception in GetSchemeForTable: " + ex.ToString());
        }
        finally
        {
            if (connection.State == ConnectionState.Open)
            {
                connection.Close();
            }
            connection.Dispose();
        }
    }

    return ret;
}

Problem: When I try to set the specific column it tells me that the column doesn't exist.
Exception Message:

An unhandled exception of type 'System.ArgumentException' occurred in System.Data.dll

Additional information: Column 'Rule' does not belong to table SchemaTable.

I suspect that the getting the table schema does not actually give me Rule table, but the SchemaTable. What I really want to get is the DataTable which corresponds to the Rule table (i.e. it has the same schema).

Question: What's the proper way to set the column values in a row given a list of records?

Это было полезно?

Решение

GetSchemaTable does not do what it looks like you're expecting it to do. It returns a DataTable where the rows are the columns of the source table and the columns are metadata about those columns.

If you want to get an "empty" DataTable with the schema of the given table, change your function to:

private DataTable GetSchemeForTable(string tableName)
{
    DataTable ret = new DataTable();

    try
    {
        connection.Open();
        SqlCommand command = connection.CreateCommand();
        command.CommandText = string.Format("SELECT * FROM [{0}] WHERE 1 = 2;", tableName);
        using (IDataReader reader = command.ExecuteReader(CommandBehavior.SchemaOnly))
        {
            ret.Load(reader);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Exception in GetSchemeForTable: " + ex.ToString());
    }
    finally
    {
        if (connection.State == ConnectionState.Open)
        {
            connection.Close();
        }
        connection.Dispose();
    }

    return ret;
}

notice I took out the using block since you're disposing of the connection in your finally block anyways (which is essentially what using does).

Другие советы

Maybe you can use something like this to generate your table:

GenerateDataTable(typeof(Rule));

private DataTable GenerateRulesDataTable(Type type)
{
    DataTable table = new DataTable();

    // build table columns
    foreach (PropertyInfo propInfo in type.GetProperties())
    {
        Type colType = Nullable.GetUnderlyingType(propInfo.PropertyType) ?? propInfo.PropertyType;
        DataColumn col = new DataColumn(propInfo.Name, colType);
        table.Columns.Add(col);
    }

    return table;
}

GetSchemaTable doesn't return DataTable with structure identical as one returned by DataReader select, it returs a table with same schema (ColumnName, ColumnSize, etc) and your schema is in DataTable rows.

In your case you should get DataTable with this select and then you will get empty RulesTable. That way you will be able to insert new rows into a returned DataTable (rulesTable in your case) and do a Update back to database.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top