What does MissingSchemaAction.AddWithKey really do? - c#

The default value of SqlDataAdapter.MissingSchemaAction is MissingSchemaAction.Add, but when I specify the AddWithKey I can't understand what it really do ?
System.Data.SqlClient.SqlDataAdapter da = new System.Data.SqlClient.SqlDataAdapter();
da.MissingSchemaAction = MissingSchemaAction.AddWithKey;
DataSet ds = new DataSet();
da.Fill(ds, "mytable");
When the use of AddWithKey can be useful ?

Documentation here says, it "adds the necessary columns and primary key information to complete the schema"
It states the primary function of AddWithKey as: "This ensures that incoming records that match existing records are updated instead of appended."
A little reverse engineering reveals the following:
When you invoke DbDataAdapter.Fill(DataSet, string) it executes the DbCommand.ExecuteReader with CommandBehavior set to SequentialAccess
If you specify MissingSchemaAction = MissingSchemaAction.AddWithKey; the CommandBehavior.KeyInfo is added to the behavior.
This causes the DbCommand.ExecuteReader invoked internally to add the following on top of your query:
SET NO_BROWSETABLE ON;
Which is documented here by Microsoft (as below)
The browse mode lets you scan the rows in your SQL Server table and
update the data in your table one row at a time. To access a SQL
Server table in your application in the browse mode, you must use one
of the following two options:
The SELECT statement that you use to access the data from your SQL
Server table must end with the keywords FOR BROWSE. When you turn on
the FOR BROWSE option to use browse mode, temporary tables are
created.
You must run the following Transact-SQL statement to turn on the
browse mode by using the NO_BROWSETABLE option:
SET NO_BROWSETABLE ON
When you turn on the NO_BROWSETABLE option, all the SELECT statements
behave as if the FOR BROWSE option is appended to the statements.
However, the NO_BROWSETABLE option does not create the temporary
tables that the FOR BROWSE option generally uses to send the results
to your application.

Related

How to obtain a view's structure/query using OleDb?

I have an ASP.Net application that reads data from an Access 2010 database file (.mdb).
I can easily list all tables and views in the database like this:
string sql = "select name from msysobjects where type in (1,5)";
OleDbCommand cmd = new OleDbCommand(sql,con);
OleDbDataAdapter da = new OleDbDataAdapter(cmd);
da.Fill(dataTable); // now dataTable contains all ojbects' names I have created in access
What I would like to inquire about is the structure of the tables/views without relying on Access. I.E, using external OleDb commands.
In Oracle, I could do so by running the command
describe table_name;
describe view_name;
describe procedure_name;
but how can it be done in MS Access?
As with all relational databases there are tables of tables (reflection data). These are normally hidden, but you can Select * From msysObjects. Filtering on Type will allow you to distinguish tables from queries from forms from reports, but also allows distinguishing internal tables from mapped tables.
If you want them listed in the Navigation Pane, then right-click on the pane, select Navigation Options, and click the Show System Objects check box.

Error returning SQL Server result set using ODBC

I am converting an existing app from Sybase to SQL Server 2008. The app uses ODBC to connect to the database. Simply changing the connection string has worked for most of the apps functionality but there is one bug that has me puzzled. I have created a simple example that shows the bug.
I have a stored procedure:
create procedure dbo.test_sel
as
begin
create table #cfg
(
Name varchar(100) NULL,
Dscr varchar(50) NULL
)
insert #cfg(Name, Dscr)
values('Config', 'Config description')
select COUNT(*) as [Count] from #cfg
return 0
end
and code that executes the stored procedure and loads the results into a DataTable:
{
OdbcCommand cmd = new OdbcCommand("test_sel", new OdbcConnection("Driver={SQL Server};Server=xxx;Database=xxx;Uid=xxx;Pwd=xxx"));
DataTable dt = new DataTable();
OdbcDataAdapter da = new OdbcDataAdapter(cmd);
using (cmd.Connection)
{
da.Fill(dt);
}
MessageBox.Show(dt.Rows.Count.ToString());
}
I would expect the message to show the value 1 and the DataTable to contain a single row. However the DataTable is empty (no result set is returned).
Now, if I comment out the insert statement in the stored procedure and rerun the code then I do get a result set returned containing one row, one column named count with a value of 0.
I have tried many variants of the stored procedure, using a permanent table rather than temporary, for example but each time the same thing happens - having the insert stops the stored procedure from returning a result set.
Changing the ODBC driver to SQL Server 2008 makes no difference. The code works as expected when using Sybase and when run in the MS SQL Server Management Studio.
After a google search I tried the following code:
{
SqlCommand cmd = new SqlCommand("test_sel", new SqlConnection("Server=xxx,5100;Database=xxx;User ID=xxx;Password=xxx"));
DataTable dt = new DataTable();
SqlDataAdapter da = new SqlDataAdapter(cmd);
using (cmd.Connection)
{
da.Fill(dt);
}
MessageBox.Show(dt.Rows.Count.ToString());
}
Now this works but I would rather not make code changes for this if I can help it because the app needs to work on both databases.
Can anyone suggest why the original code does not work? It really seems like something that simple should work OK.
There are 2 options you can use to make this work (to me this seems like bug since Fill for both OdbcDataAdapter and SqlDataAdapter should work equally):
1) in the beginning of the procedure after the begin keyword add
set nocount on
2) Use the overload of Fill method that takes DataSet as parameter rather then the one that takes DataTable.
MSDN states: The overload of Fill that takes DataTable as a parameter only obtains the first result.
When INSERT is executed and NOCOUNT is off, information on how many rows were affected by the statement gets returned. So when you have this option off, count of rows will be reported 2 times (you can check that in SSMS, 'Messages' window), once for INSERT, second time for SELECT. It seems that first row count (from INSERT) somehow gets interpreted as first result set, this row count refers to INSERT statement not SELECT so there are no rows to return.

Decimal passed incorrectly from C# to SQL Server with TableAdapters

Yesterday I noticed an odd behaviour when using TableAdapters, for some reason when passing a decimal < 0.1 it makes it into an integer. For example if I pass 1.0123, I can see 1.0123 in SQL Profiler, but if I pass 0.0123 I will get 123. Is there a known issue? You can do the following steps to reproduce the problem:
Create a new database TestDatabase, and create the following stored procedure
create proc DecimalParametersSelect
(
#Foo decimal(10,5)
)
as
select #Foo
Create a new project and add a new DataSet file SampleDataset. Add a new TableAdapter and add DecimalParametersSelect as Select procedure (it should be the only one in your db).
Run your project and try to select some data, e.g.
using (SampleDatasetTableAdapters.DecimalParametersSelectTableAdapter dta = new SampleDatasetTableAdapters.DecimalParametersSelectTableAdapter())
{
var table = dta.GetData(0.01588M);
}
In profiler you should see that the value passed in is 1588 (interestingly the value returned is recognized correctly in C# as 0.01588)
This appears to be a display bug in SQL Profiler when the TextData column is not included in the trace and the text of the RPC command is reconstructed from another source (presumably BinaryData).
I followed your steps and was able to repo on SQL 2008 R2 using a default trace in SQL profiler.
However, when the trace properties are changed to include the TextData column for RPC:Completed, the correct command is displayed.

SQL Server 2000 - Bulk insert from dataset/or C# collection

I know SQL Server 2000 has a bulk insert. Does it support bulk insert from a C# collection, such as a dataset?
I need to insert 30 rows at a time, fairly regularly. I don't want to create 30 DB connections for this if I don't have to.
Have a look at SqlBulkCopy (has according to forums SQL 2000 support). It's easy to use. Basically just provide it with a data table (or data reader) and it will copy the rows from that source to your destination table.
You can insert using a DataSet in SQL 2000, I've never tried because I never use DataSets.
http://www.dotnet247.com/247reference/msgs/3/16570.aspx has a good post on it:
(From the article)
Steps involved
1.Create SqlDataAdapter with proper select statement
2.Create dataset and fill dataset with SqlDataAdapter
3.Add rows to the table in the dataset (for all the actions you said above, like radio
button selected, check box enabled)
4.Use SqlCommandBuilder helper object to generate the
UpdateStatements. Its very easy to use command builder. Just a one
call to the SqlCommandBuilder constructor.
5.Once your are done adding rows to the datatable int the dataset call
SqlDataAdapter.update and pass the modified dataset as a parameter.
This should automatically add the rows from dataset to the
database.(if no database error occurs)
Have you considered XML?
Working with XML in SQL 2000 isn't as nice as in 2008, but it is still doable:
http://www.codeproject.com/KB/database/insxmldatasqlsvr.aspx
http://www.codeproject.com/KB/database/generic_OpenXml.aspx
http://support.microsoft.com/default.aspx?scid=kb;en-us;315968
Another option you could look at would be to:
Open Connection.
Iterate through the inserts
Close Connection.

SMO scripting objects and security

I have a customer that has a SQL database on a hosted server; call the db "myDatabase".
The hosting co. has locked down object explorer - I can't see myDatabase in the database listed (I see tempdb and master). However, if I "use myDatabase" and then "select * from myTable", all works fine.
Since we have no access to object explorer, I can't right click and generate scripts. I thought that I might be able to use SMO to accomplish what I want, but when I attempt something similar to this:
Server myServer = new Server(conn);
Database myDB = server.Databases["myDatabase"];
Table myTbl = myDB.Tables["myTable"];
It fails - myDB is null (when I iterate through the databases collection, as expected, I only see master and tempdb - the db's I can see in object explorer). It obviously has to do with security - if I can't see the table in object explorer, it won't let me access it through SMO. Anyone have any ideas of a workaround or alternate method to allow me to generate a script?
Thx!
I haven't looked at the SMO code, but have you tried using the constructor on the database object? Maybe you can access it directly.
Database myDB = new Database(myServer, "myDatabase");
Is the myDb.Tables collection empty? Could it be that you are referencing it using the wrong name?
One option you could try is to use Linq2Sql to generate a model of the database. You can then use the model to create a new database that should be more or less identical to the original. Look up the DataContext.CreateDatabase method for more info.
Another option would be to list all tables using the following query:
select * from sys.tables
And then listing all columns in the tables using the following:
select * from sys.columns where object_id = (object id from the previous query)
This will give you all tables and columns defined in your database and should be enough to create the database structure. In addition you have system views for other objects defined as well.

Categories

Resources