C# how to transact Entity Framework and ADO.NET at a time - c#

I have a parent-child data windows form. My parent data is fetched by an entity class created with Entity Framework.
But my child datagridview datasource is bound with a datatable with multiply data table query from my database.
So I use Entity Framework to insert or update the ‘parent’ data, and I use ADO.NET to create and update the ‘child’ data records.
When I insert child data I have to get the Id of parent data as foreign key.
In this setup, how can I have a transaction over both?
Just simply use TransactionScope?

//Simpy use TransactionScope can solve this problem.
using (TransactionScope scope = new TransactionScope())
{
tblOrder tblOrder = new tblOrder() { FOrderNum = "12345", FOrderType = "OrderTYpe", FClientID = 1086, FOrderStatus = "ReadyToProduction" };
EFContext context = new EFContext();
context.tblOrders.Add(tblOrder);
context.SaveChanges();
scope.Dispose();
int orderID = tblOrder.FOrderID;
SqlConnection conn = new SqlConnection("****");
string sql = $"INSERT INTO tblOrderSub (FOrderID,FMaterialID,FQty,FQtyShip,FFinishSKU,FModelCust,FColorCust)" +
$" VALUES(#FOrderID,#FMaterialID,#FQty,#FQtyShip,#FFinishSKU,#FModelCust,#FColorCust)";
SqlParameter[] sqlParameters =
{
new SqlParameter("#FOrderID",orderID),
new SqlParameter("#FMaterialID",1),
new SqlParameter("#FQty",1),
new SqlParameter("#FQtyShip",1),
new SqlParameter("#FFinishSKU",false),
new SqlParameter("#FModelCust", "TEst"),
new SqlParameter("#FColorCust", "C1")
};
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddRange(sqlParameters);
conn.Open();
cmd.ExecuteNonQuery();
scope.Complete();
}

Related

How to get last inserted id in FluentMigrator

I'm using FluentMigrator with MySql and C#.
[Migration(3)]
public class _0003_TestMigrationTI : Migration
{
public override void Up()
{
Insert.IntoTable("cmn_file_types").Row(new { Name = "Image", Code = "IMG" });
???
}
}
How to get last inserted id after Insert.IntoTable in the same Up() method?
The ID is an INT PRIMARY AUTO_INCREMENT column in cmn_file_types table.
MigrationBase class has ConnectionsString property. Just use it creating your ADO.net query:
var builder = new SqlConnectionStringBuilder(ConnectionString);
var query = "SELECT IDENT_CURRENT('Table')"
using (var connection = new SqlConnection(builder.ConnectionString))
{
connection.Open();
var command = new SqlCommand(query, connection);
var lastId = command.ExecuteScalar();
connection.Close();
}
update:
Try this one. Worked for me. Here you'll get the same transaction for your new query, unlike that case with ADO. So "select IDENT_CURRENT" will show you uncommited current Id.
Execute.WithConnection((IDbConnection connection, IDbTransaction transaction) =>
{
var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = #"select IDENT_CURRENT('TestTable1')";
var count = command.ExecuteScalar();
});

How do I return a table from a SQL query as a List in C#?

I have a Xamarin app that connects to a SQL Server (*), and I want to insert the data from the SQL Server's tables into a local SQLite database. My approach is to execute a query like
select * from Table1
retrieve the results of the query as a List, then insert that List into my SQLite table. That said I'm very new at using SqlClient, so please share if there are better ways to do this. Thanks.
Edit: the smallest number of columns these tables have is 5. Don't know if that disqualifies Lists as the best option.
My code:
private void LoadData()
{
string cs = #"connection string here";
using (SqlConnection sconn = new SqlConnection(cs))
{
sconn.Open();
SqlDataReader reader = null;
SqlCommand aml = new SqlCommand("select * from Table1");
reader = aml.ExecuteReader();
while (reader.Read())
{
// get result of query as List somehow?
}
using (SQLiteConnection conn = new SQLiteConnection(App.DatabaseLocation))
{
conn.CreateTable<Table1>();
if (conn.Query<Table1>("select * from Table1").Count() <= 0)
{
// insert the list object
}
}
}
}
(*) The app does not use a web service as the app is intended for onsite use only and will not be distributed publicly.
A better alternative way to do it more easier is to use a ORM like dapper
With the help of dapper, all you need to do is
using (var connection = new SqlConnection(_sqlConnectionString))
{
var results = connection.Query<YourTableModel>(query).ToList();
return results;
}
You can get data from the SQL Server as DataTable or convert it to a list as you prefer.
public DataTable GetDataTable(string connectionString, string tableName)
{
SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
string query = $'SELECT * FROM [{tableName}]';
SqlCommand cmd = new SqlCommand(query, conn);
DataTable t1 = new DataTable();
using (SqlDataAdapter a = new SqlDataAdapter(cmd))
{
a.Fill(t1);
}
return t1;
}
Then use this table or list returned from the above method to insert in the SQLite table.
string cs = #"Data Source=datasource;Initial Catalog=databasename;User ID=user;Password=password";
DataTable table = GetDataTable(cs, "Table1");
using (SQLiteConnection conn = new SQLiteConnection(App.DatabaseLocation))
{
conn.CreateTable<Table1>();
if (conn.Query<Table1>("select * from Table1").Count() <= 0)
{
foreach(DataRow row in table.Rows)
{
//Access values of each row column row["columnName"]
// insert the list object
}
}
}
Refer to this one:
Inserting Data from SQL Server to Sqlite

JQGrid ASP.net C# add edit delete not updating in database

I am using this demo for JQGrid ASP.net C# http://www.trirand.net/examples/grid/editing_data/edit_add_delete/default.aspx
Data is updating and deleting in JQGrid but not in database, how can we update in database also??
In delete function you create a datatable and push data from Database. but you did not exec delete command in database. So it works but not delete from database.
protected void JQGrid1_RowDeleting(object sender, Trirand.Web.UI.WebControls.JQGridRowDeleteEventArgs e)
{
DataTable dt = GetData();
dt.PrimaryKey = new DataColumn[] { dt.Columns["CustomerID"] };
DataRow rowToDelete = dt.Rows.Find(e.RowKey);
if (rowToDelete != null)
{
dt.Rows.Remove(rowToDelete);
// store your CustomerID in variable
string CustomerId = rowToDelete[0].ToString();//Datatype base on your sql table column
SqlConnection sqlConnection = new SqlConnection();
sqlConnection.ConnectionString = ConfigurationManager.ConnectionStrings["SQL2008_661086_trirandEntities"].ConnectionString;
sqlConnection.Open();
string sqlStatement = "delete FROM Customers where CustomerID = "+ CustomerId +"";
SqlCommand cmd = new SqlCommand(sqlStatement , sqlConnection);
cmd.ExecuteNonQuery();
}
JQGrid1.DataSource = GetData();
JQGrid1.DataBind();
}
Now your code will work as you want.

Fastest way of performing Bulk Update in C# / .NET

I'm trying to figure out whats the best possible way to perform a bulk update via my mini console application in SQL server. I have written my own way of bulk update like following:
SqlCommand command = new SqlCommand();
command.Connection = new SqlConnection("Data Source=.;Initial Catalog=mydb;Integrated Security=SSPI");
command.Connection.Open();
for (int i = 0; i < items.Count; i = i + 1000)
{
var batchList = items.Skip(i).Take(1000).ToList();
for (int j = 0; j < batchList.Count(); j++)
{
command.CommandText += string.Format("update Items set QuantitySold=#s_id{0} where ItemID = #id{0};", j);
command.Parameters.AddWithValue("#s_id" + j, batchList[j].QuantitySold);
command.Parameters.AddWithValue("#id" + j, batchList[j].ItemID);
}
command.ExecuteNonQuery();
command = new SqlCommand();
command.Connection = new SqlConnection("Data Source=.;Initial Catalog=mydb;Integrated Security=SSPI");
command.Connection.Open();
}
command.Connection.Close();
But I'm not so happy with the performance of this one, updating 50000-100000 records in my DB gets quite slow when doing it like this, even tho it does them in batches of 1000....
Is there any library/solution out there that could "speed things up"?
Can someone help me out ?
The fastest way would be to bulk insert the data into temporary table using the built in SqlBulkCopy Class, and then update using join to that table
Or you can use a tool such as SqlBulkTools which does exactly this in an easy way.
var bulk = new BulkOperations();
using (TransactionScope trans = new TransactionScope())
{
using (SqlConnection conn = new SqlConnection("Data Source=.;Initial Catalog=mydb;Integrated Security=SSPI")
{
bulk.Setup()
.ForCollection(items)
.WithTable("Items")
.AddColumn(x => x.QuantitySold)
.BulkUpdate()
.MatchTargetOn(x => x.ItemID)
.Commit(conn);
}
trans.Complete();
}
You can use Kros.KORM for bulk operation.
using (var database = new Database("connectionstring ...", "ado client name ..."))
{
database
.Query<Movie>()
.AsDbSet()
.BulkUpdate(_data);
}
Or if you do not need to use ORM and have the source data reader available, you can use the SqlServerBulkInsert / SqlServerBulkUpdate or MsAccessBulkInsert / MsAccessBulkUpdate classes to perform bulk operations.
For example:
using (var bulkInsert = new SqlServerBulkInsert("connection string"))
{
bulkInsert.Insert(reader);
}
You can see comparison with pure ADO.NET commands https://github.com/Kros-sk/Kros.Libs/wiki
I don't know what items is in your code so I don't know how to get the items into a table valued parameter. I can help with that if you need it but I would need to know what that object is.
Regardless you could do something like this on the sql side. Then you simply execute this procedure with your items collection as inbound parameter.
create type Items as TABLE
(
ItemID int
, Quantity int
)
GO
create procedure UpdateItemsBulk
(
#Items Items READONLY
) as
set nocount on;
Update i
set QuantitySold = items.Quantity
from items i
join #Items items on items.ItemID = i.ItemID
GO

C# I want to bind Ingredients into recipe in listbox with a database

I want to add ingredient into recipe. I have tried many things in order to do that but it didn't work sadly.
Idea: http://prntscr.com/dw8lom
My database tables: http://prntscr.com/dw8ms7
My code I've used to add recipe:
private void VoegReceptenToe()
{
string query = "INSERT INTO Recipe VALUES (#RecipeName, 30, 'goed mixen')";
using (connection = new SqlConnection(connectionstring))
using (SqlCommand command = new SqlCommand(query, connection))
{
connection.Open();
command.Parameters.AddWithValue("#RecipeName", txtRecipeName.Text);
command.ExecuteScalar();
PopulateRecipes();
}
}
Populate recipe:
{
string query = "SELECT a.Name FROM Ingredient a " +
"INNER JOIN Recipe_Ingredient b ON a.Id = b.IngredientId " +
"WHERE b.RecipeId = #RecipeId";
// de connectionstring hoef je niet te openen en te sluiten als,
// je een using statement gebruikt, dat doet hij vanzelf.
using (connection = new SqlConnection(connectionstring))
using (SqlCommand command = new SqlCommand(query,connection))
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
command.Parameters.AddWithValue("#RecipeId", lstRecipe.SelectedValue);
DataTable ingredientTable = new DataTable();
adapter.Fill(ingredientTable);
//Dit displayt de listbox.
lstIngredient.DisplayMember = "Name";
lstIngredient.ValueMember = "id";
lstIngredient.DataSource = ingredientTable;
}
}
You are opening a new connection to the database before closing the existing one.
What happens in your code is that when execution enters method VoegReceptenToe it opens a new connection to the database to insert some data. However, before closing that connection you're calling PopulateRecipes method which opens a new connection to the database.
What you have to do is to split insert and query operations and you can do this by moving the method VoegReceptenToe after the using statement:
private void VoegReceptenToe()
{
string query = "INSERT INTO Recipe VALUES (#RecipeName, 30, 'goed mixen')";
using (connection = new SqlConnection(connectionstring))
using (SqlCommand command = new SqlCommand(query, connection))
{
connection.Open();
command.Parameters.AddWithValue("#RecipeName", txtRecipeName.Text);
command.ExecuteScalar();
}
PopulateRecipes();
}
As long as you ensure that Recipe & Ingredient names are unique in their tables (maybe using a unique index) & you have made sure the RecipeName & IngredientName exist in the tables (e.g. by only selecting from your listboxes) then the following query may do what you want :
INSERT INTO Recipe_Ingredient (RecipeID, IngredientID)
VALUES (
SELECT ID FROM Recipe Where Name = #RecipeName,
SELECT ID FROM Ingredient Where Name = #IngredientName)
This inserts an entry into the Recipe_Ingredient table, consisting of the ID of the RecipeName & the IngredientName as long as there exists a single entry for each.
You will need to add parameters for both the RecipeName & IngredientName to your command and run the ExecuteNonQuery method as the INSERT does not return a value.
Similarly to add a new RecipeName the ExecuteNonQuery method should be used to run this query :
INSERT INTO Recipe (Name, PrepTime, Instructions) VALUES (#RecipeName, 30, 'goed mixen')
and for a new Ingredient
INSERT INTO Ingredient (Name) VALUES (#Ingredient)
Also take note of RePierre's reordering of your code to ensure connections are not prematurely closed.

Categories