I am coding in C# against an Access database...
I am able to get all of the tables in my access database with the following:
DataTable dataTbl;
DataView dvCols;
DataRowView drvCols;
...
dataTbl = m_connection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables,
new Object[] { null, null, null, "TABLE" });
dvCols = new DataView(dataTbl);
for (int j = 0; j < dvCols.Count; j++)
{
drvCols = dvCols[j];
tableName = drvCols.Row["TABLE_NAME"].ToString();
// ...
}
The problem is that this code gets the table names in alphabetical order. Now if I were reading the columns from particular table I could sort by ordinal position using
dvCols.Sort = "ORDINAL_POSITION";
However this is not possible with tables, there really is no ordinal position I guess. My tables have relationships, e.g. Table A has a one to many relationship to Table B which has a one to many relationship to Table C, etc... So I need to get the table names in that order, e.g. A then B then C.
Is there any way to do this?
Do I need to somehow examine the primary and foreign keys and figure out this information myself?
Ok..figured it out.
What I ended up doing was the following:
dataTable_ForeignKeys =
m_connection.GetOleDbSchemaTable(OleDbSchemaGuid.Foreign_Keys, null);
//Check if a DataTable object is initialized
if (dataTable_ForeignKeys != null)
{
// iterate and get the names
foreach (DataRow row in dataTable_ForeignKeys.Rows)
{
tableName = row["PK_TABLE_NAME"].ToString();
tblNames.Add(tableName);
To get a list of the foreign key relationships. This returned the tables in the order I needed, i.e. 'parent' tables first. If a table is not the 'parent' of another table, i.e. it does not have a field that is the foreign key in another table then it will not be returned. I reconciled this by also getting all database table names as I did in my original post.
If you want the tables ordered according to their relationships, then, yes, you will have to implement that yourself (or find a third-party tool; I'm not aware of any).
Note that you will need some strategy for handling cycles in the relationships.
Related
I have got a single function using OleDb:
I think maybe the use of a list to find out if a specific table exist could be made better, like this:
Check for MS Access database table if not exist create it
But I also want to understand the old code given, to learn something.
Questions:
1) What does the exact restrictionValues mean in the example code below? ( not solved)
2) Why row.ItemArray[2] of all cells is containing the table names? (solved)
3) Is there a better way to get table names out of a database? (solved)
This is the code I have got:
public List<string> GetTableNames(string tableName, string[] field_names)
{
List<string> retTableNames = new List<string>();
if (dbConnection != null)
{
dbConnection.Open();
string strSQL = "SELECT * ";
string[] restrictionValues = new string[4] { null, null, null, "TABLE" };
OleDbCommand cmd = new OleDbCommand(strSQL, dbConnection);
try
{
/*search after all possible tables in dataset*/
DataTable schemaInformation = dbConnection.GetSchema("Tables", restrictionValues);
foreach (DataRow row in schemaInformation.Rows)
{
retTableNames.Add(row.ItemArray[2].ToString());
}
}
catch
{
retTableNames = null;
}
}
return retTableNames;
}
(I just noticed that you said you already get this first part, but I'll leave it in anyway. There's a little more below about "TABLE".)
Some of the explanation of the restrictions is in the Main program in the link that #jdweng gave.
// You can specify the Catalog, Schema, Table Name, Table Type to get
// the specified table(s).
// You can use four restrictions for Table, so you should create a 4 members array.
String[] tableRestrictions = new String[4];
// For the array, 0-member represents Catalog; 1-member represents Schema;
// 2-member represents Table Name; 3-member represents Table Type.
// Now we specify the Table Name of the table what we want to get schema information.
tableRestrictions[2] = "Course";
DataTable courseTableSchemaTable = conn.GetSchema("Tables", tableRestrictions);
The rest of the explanation is in the overload of GetSchema: GetSchema Method (String, String[]), which is what you're using.
Verbatim:
In order to set values on a given restriction, and not set the values of other restrictions, you need to set the preceding restrictions to null and then put the appropriate value in for the restriction that you would like to specify a value for.
An example of this is the "Tables" collection. If the "Tables" collection has three restrictions -- database, owner, and table name--and you want to get back only the tables associated with the owner "Carl", you need to pass in the following values: null, "Carl". If a restriction value is not passed in, the default values are used for that restriction. This is the same mapping as passing in null, which is different from passing in an empty string for the parameter value. In that case, the empty string ("") is considered to be the value for the specified parameter.
A more complete article on schema restrictions.
It looks like you can omit some parameters, which is why the example above only lists 3 restrictions.
I've been looking for an explanations of the "TABLE" parameter but it's hard to find. It's either a default that gets all tables, or it's ignored, or something else. The easiest way to get all table types might be to do a basic DataTable table = connection.GetSchema("Tables"); then get the types of each table to see what the options are. Otherwise, sticking to "TABLE" will no doubt get the commonly used tables, not system tables or anything like that.
Hope your ears are ok.
I was hoping someone could point me in the right direction here. I am building a c# ASP.net application with Entity Framework. I used the database first approach.
To keep things simple, assume I have 3 tables that are related. Report, a_report_vehicle and Vehicle. I have primary keys in each table. The a_report_vehicle has a foreign key to the Report table and Vehicle table.
So, I am attempting to relate many vehicles to a single report. I realize that I could simplify the design by adding the Report table key in the Vehicle table and eliminate the link table. However, the Vehicle table relates to other tables as well. So, I want to retain the a_report_vehicle link table.
Here is the most relevant code for this issue. Note that I have an object relating to the Report table, above this code. (newReport) I also have objects for newVehicle1 and newVehicle2.
Also note that if I add one vehicle with one row added to the a_report_vehicle table, this logic works fine. When I try to add 2 vehicle (2 separate Vehicle objects) with 2 rows in the link table, I receive an error 'Unable to determine principal end of relationship'.
I have looked at other posts and attempted to add dummy values to the vehicleID, but that did not work. In fact, if I set the first vehicleID to 1 and the second to 2, because those values actually exist in my Vehicle table, Entity Framework used those dummy values as if they were the real key values. Other posts led me to believe that Entity Framework just wants me to add placeholder values in the keys and it would resolve the real keys on database insert.
Here is the relevant code snippet. Any hints would be greatly appreciated.
using (I.IE ctx = new I.IE()){
int rID, vID1, vID2;
ctx.t_Report.AddObject(newReport);
rID = newReport.ID;
ctx.t_Vehicle.AddObject(newVehicle1);
vID1 = newVehicle1.ID;
ctx.a_Report_Vehicle.AddObject(new a_Report_Vehicle
{
reportID = rID,
association_type = "text",
remarks = "Test",
DC = DateTime.Now,
UC = "3845",
vehicleID = vID1
});
ctx.t_Vehicle.AddObject(newVehicle2);
vID2 = newVehicle2.ID;
ctx.a_Report_Vehicle.AddObject(new a_Report_Vehicle
{
reportID = rID,
association_type = "text",
remarks = "Test",
DC = DateTime.Now,
UC = "3845",
vehicleID = vID2
});}
With the assistance of Gert, I managed to get 2 vehicle added. Here is the modified code that appears to work. (Add new a_Report_Vehicle object to the appropriate t_Vehicle navigation property Entity Collection and Entity Framework will manage the keys)
ctx.t_Vehicle.AddObject(newVehicle1);
vID1 = newVehicle1.ID;
newVehicle1.a_Report_Vehicle.Add(new a_Report_Vehicle
{
reportID = rID,
association_type = "text",
remarks = "Test",
DC = DateTime.Now,
UC = "3845",
vehicleID = vID1
});
ctx.t_Vehicle.AddObject(newVehicle2);
vID2 = newVehicle2.ID;
newVehicle2.a_Report_Vehicle.Add(new a_Report_Vehicle
{
reportID = rID,
association_type = "text",
remarks = "Test",
DC = DateTime.Now,
UC = "3845",
vehicleID = vID2
});
The problem is that you are assigning the key values of the new Vehicles to the foreign keys. But at that moment these values are still 0.
You should assign the Vehicle objects to the t_Vehicle navigation properties. Then EF will insert all objects in the required order to be able to fetch the generated PK values and use these for the new Report_Vehicle records.
The same applies to the new Report object.
I will take a stab at this and say that I believe the problem is that you are trying to use the id's of your inserted objects before they have actually been inserted into the database by a call to SaveChanges().
Try adding:
ctx.SaveChanges();
after each call to AddObject(); and before you try to get the id of the inserted object.
Before you call SaveChanges(), nothing has actually been saved to the database, and your auto-incremented id's have not actually been generated and returned to the entities yet.
I have a non-typed dataset filled with data from user input (no database). There's no primary key column (my data had no need for primary key so far)! Is there any way to avoid "brute force" if i want to check if new row user is trying to insert already exists in my DataTable? How should i perform that check?
You can manually create unique constraints for your DataTable:
DataTable custTable = custDS.Tables["Customers"];
UniqueConstraint custUnique = new UniqueConstraint(new DataColumn[]
{custTable.Columns["CustomerID"],
custTable.Columns["CompanyName"]});
custDS.Tables["Customers"].Constraints.Add(custUnique);
For this example, you would get an exception (of type ConstraintException) if you tried to add a row to the table where the CustomerID and CompanyName were duplicates of another row with the same CustomerID and CompanyName.
I would just let the DataTable check these things for you internally - no point reinventing the wheel. As to how it does it (whether it is efficient or not), will have to be an exercise for you.
What you can do is use a DataView. Dataview allow you to use a where clause with the DataView's data.
Check it that way.
To check for any duplicates try
if (table.Rows.Contain(PriKeyTypeValue)) /*See if a Primary Key Value is in
the table already */
continue;
else
table.Row.Add(value1, value2, value3);
If you want to be able to insert duplicate rows but do not want to have an exception thrown set-up your primary key as a unique self-incrementing int then you can insert as many duplicates as you feel like without having to check to see if the table contains that value.you can set primary key value like the below....
DataTable table = new DataTable();
table.Columns.Add("Column", typeof(int));
DataColumn column = table.Columns["Column"];
column.Unique = true;
column.AutoIncrement = true;
column.AutoIncrementStep = 1; //change these to whatever works for you
column.AutoIncrementSeed = 1;
table.PrimaryKey = new DataColumn[] { column };
Much, much easier way:
datatable.Columns.Contais("ColumnName")
I need to write some code to insert around 3 million rows of data.
At the same time I need to insert the same number of companion rows.
I.e. schema looks like this:
Item
- Id
- Title
Property
- Id
- FK_Item
- Value
My first attempt was something vaguely like this:
BaseDataContext db = new BaseDataContext();
foreach (var value in values)
{
Item i = new Item() { Title = value["title"]};
ItemProperty ip = new ItemProperty() { Item = i, Value = value["value"]};
db.Items.InsertOnSubmit(i);
db.ItemProperties.InsertOnSubmit(ip);
}
db.SubmitChanges();
Obviously this was terribly slow so I'm now using something like this:
BaseDataContext db = new BaseDataContext();
DataTable dt = new DataTable("Item");
dt.Columns.Add("Title", typeof(string));
foreach (var value in values)
{
DataRow item = dt.NewRow();
item["Title"] = value["title"];
dt.Rows.Add(item);
}
using (System.Data.SqlClient.SqlBulkCopy sb = new System.Data.SqlClient.SqlBulkCopy(db.Connection.ConnectionString))
{
sb.DestinationTableName = "dbo.Item";
sb.ColumnMappings.Add(new SqlBulkCopyColumnMapping("Title", "Title"));
sb.WriteToServer(dt);
}
But this doesn't allow me to add the corresponding 'Property' rows.
I'm thinking the best solution might be to add a Stored Procedure like this one that generically lets me do a bulk insert (or at least multiple inserts, but I can probably disable logging in the stored procedure somehow for performance) and then returns the corresponding ids.
Can anyone think of a better (i.e. more succinct, near equal performance) solution?
To combine the previous best two answers and add in the missing piece for the IDs:
1) Use BCP to Load the data into a temporary "staging" table defined like this
CREATE TABLE stage(Title AS VARCHAR(??), value AS {whatever});
and you'll need the appropriate index for performance later:
CREATE INDEX ix_stage ON stage(Title);
2) Use SQL INSERT to load the Item table:
INSERT INTO Item(Title) SELECT Title FROM stage;
3) Finally load the Property table by joining stage with Item:
INSERT INTO Property(FK_ItemID, Value)
SELECT id, Value
FROM stage
JOIN Item ON Item.Title = stage.Title
The best way to move that much data into SQL Server is bcp. Assuming that the data starts in some sort of file, you'll need to write a small script to funnel the data into the two tables. Alternately you could use bcp to funnel the data into a single table and then use an SP to INSERT the data into the two tables.
Bulk copy the data into a temporary table, and then call a stored proc that splits the data into the two tables you need to populate.
You can bulk copy in code as well, using the .NET SqlBulkCopy class.
I am having creating a new record that will be inserted into the DB. I looked at the db.log but nothing gets printed. I have primary keys marked, but not sure what else needs to be done.
Have a many-to-many relationship between two tables (Member and RecForms). This is being down through a middle table of MemberRecForms that contains the ID for the other tables. In the MemberRecForm table the two keys are marked as compound primiary keys (it is also this way in the dbml).
DataContext db = new DataContext();
MemberRecForm r = new MemberRecForm();
r.RecFormID = 2;
this.MemberRecForms.Add(r);
try
{
db.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
db.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);
}
When I look at r after I do this.MemberRecForms.Add(r) I see that r was updated with the correct memberID.
Change "this.MemberRecForms.Add(r);" to "db.MemberRecForms.InsertOnSubmit(r);". Otherwise the datacontext will not have a reference to it, and will not insert it...