Using Stored Procedure for only delete in Entity - c#

Here's my stored proc I want to use in my DB:
ALTER PROCEDURE dbo.usp_DeleteExample
#ExampleID int, #LoggedInUserID int, #SessionID int, #AppID smallint
as
Declare #ExampleName varchar(255)
Set #ExampleName = (Select VehicleName from Example where ExampleID = #ExampleID)
Delete from ExampleToGeofence where ExampleID = #ExampleID;
exec usp_DeleteExampleHistoryData #ExampleID;
Delete From Example where ExampleID = #ExampleID;
Insert into UserActionHistory(ActionID, UserID, SessionID, ItemID, OldValue, ApplicationID, EventUTCDate)
Values (203, #LoggedInUserID, #SessionID, #ExampleID, #ExampleName, #AppID, getutcdate());
Here's my code where I'm trying to use it:
public Example Delete(Example example)
{
db.Examples.SqlQuery("usp_DeleteExample #ExampleID",
new SqlParameter("ExampleID", example.ExampleID)
);
db.SaveChanges();
return example;
}
Yet, when I call this through my WebAPI, nothing gets deleted.
What am I doing wrong here? Please let me know if you need more information.
Thank you.
Edit:
ModelBuilder configuration I just added:
modelBuilder.Entity<Example>()
.MapToStoredProcedures(e =>
e.Delete(v => v.HasName("usp_DeleteExample")));

I believe you should be using DbContext.Database.ExecuteSqlCommand. I'm not aware of any such format DbContext.TableName.ExecuteSqlCommand.
db.Database.ExecuteSqlCommand("usp_DeleteExample #ExampleID, #LoggedInUserID, #SessionID, #AppID ",
new SqlParameter("LoggedInUserID", variableName), new SqlParameter("SessionID", variableName),new SqlParameter("AppID ", variableName));
Because you are not modifying an entity there's no need to call SaveChanges() either. Personally, I import the SP's in the designer so they are readily available in C#.
Also note that you need to add the other 3 parameters because none of the parameters are optional.

SqlQuery is used to return entities. I think you want ExecuteSQlCommand:
public void Delete(Example example)
{
db.Examples.ExecuteSqlCommand("exec usp_DeleteExample #ExampleID",
new SqlParameter("#ExampleID", example.ExampleID)
);
return;
}

It seems there's some kind of limitations for the use of stored procedures for CRUD operations.
You must map insert, update and delete stored procedures to an entity
if you want to use stored procedure for CUD operations. Mapping only
one of them is not allowed.
Source

public HttpResponseMessage Delete(int id)
{
conn.ConnectionString = #"Data Source=DESKTOP-QRACUST\SQLEXPRESS;Initial Catalog=LeadManagement;Integrated Security=True";
conn.Open();
DataTable dt = new DataTable();
using (conn)
//data get by query
//using (var cmd = new SqlCommand("DELETE FROM EmployeeDetails WHERE EmployeeId=3;", conn))
//data get by sp
using (var cmd = new SqlCommand("EmployeeDetailsDelete", conn))
using (var da = new SqlDataAdapter(cmd))
{
da.Fill(dt);
}
return Request.CreateResponse(HttpStatusCode.OK, dt);
}

Related

How to insert self referencing entities into sql?

Assume I have the following structure for a sql table:
Name:
UserTable
Fields:
ID bigint IDENTITY(1, 1)
Name nvarchar(200) NOT NULL
ParentID bigint NULL
Note:
ParentID is a self referencing foreign key to the primary key ID which is optional.
Now switching over to my c#-project, I find myself wondering how to insert this entity many times from an import.
public static void InsertTable(DataTable table)
{
var connection = CreateConnection();
string query = "INSERT INTO [dbo].[User] (Name, ParentID) " +
"OUTPUT INSERTED.ID " +
"VALUES " +
"(#Name, #ParentID)";
using (connection)
{
for (int i = 0; i < table.Rows.Count; i++)
{
DataRow row = table.Rows[i];
using (SqlCommand command = connection.CreateCommand())
{
command.CommandText = query;
InsertParameters(row, command);
long insertedID = (long)command.ExecuteScalar();
row["ID"] = insertedID;
}
}
}
}
I set the parameters like this:
private static void InsertParameters(DataRow row, SqlCommand command)
{
string name = (string)row["Name"];
command.Parameters.AddWithValue("#Name", name);
if(row["ParentID"] is DBNull)
{
command.Parameters.AddWithValue("#ParentID", DBNull.Value);
}
else
{
command.Parameters.AddWithValue("#ParentID", (long)row["ParentID"]);
}
}
I figured that I won't be able to insert these entities into this table at any order. My approach was to try to insert entities with no reference to any parent first. While this can work in a simple example like this, I struggle to find an approach for multiple references.
I worked around this by just mapping the relations in some Dictionary<T1, T2> objects and revisit the ones with references later, when the ID-property of the referenced entity is set.
My problem with this is that I can clearly map one DataRow to another, but not insert them so easy, when I can not know the ID beforehand. I'd like to know if there are some better approaches to this.
I stumbled upon this particular problem while doing an import for some customer-related data. My solution so far is okay-ish, but not satisfactory. One case where it all breaks could be a loop reference, I think.
Anyway,
How would you tackle this problem and how to improve my method so far?
I would create stored procedure which does the whole process and can get the ids as such. Then in C# code call the sproc.
This is an example from my nuget package SQLJSONReader (github project page) where the SQL server sproc returns JSON and my reader ExecuteJsonReader then converts the table result, to a string of JSON.
string sproc = "dbo.DoIt";
string result;
using (SqlConnection conn = new SqlConnection(connection))
{
conn.Open();
using (var cmd = new SqlCommand(sproc, conn) { CommandType = CommandType.StoredProcedure, CommandTimeout = 600 })
{
if (parameters != null)
cmd.Parameters.AddRange(parameters);
var reader = await cmd.ExecuteJsonReaderAsync();
result = await reader.ReadAllAsync();
}
}
So your process is similar, just use your own reader.

SQLite Insert from Datatable

Using the below I get an exception with the #table part of the query. Can you use data tables to insert into SQLite this way?
DataTable table = new DataTable();
table.Columns.Add("Path", typeof(string));
table.Columns.Add("StopName", typeof(string));
table.Columns.Add("Latitude", typeof(string));
table.Columns.Add("Longitude", typeof(string));
foreach (Result result in tempResults)
{
table.Rows.Add(result.Path, result.StopName, result.Latitude, result.Longitude);
}
SQLiteCommand command = new SQLiteCommand("INSERT OR REPLACE INTO ZZ_DBA_Stop (Path, StopName, Latitude, Longitude) SELECT Path, StopName, Latitude, Longitude FROM #table", connection) { CommandTimeout = 3600, CommandType = CommandType.Text };
command.Parameters.AddWithValue("#table", table);
await command.ExecuteNonQueryAsync();
You can't pass DataTable as a parameter.
I think the main reason that you want use DataTable as parameter is that you want to bulk insert in sqlite. This is an example
using (var transaction = connection.BeginTransaction())
using (var command = connection.CreateCommand())
{
command.CommandText =
"INSERT INTO contact(name, email) " +
"VALUES($name, $email);";
var nameParameter = command.CreateParameter();
nameParameter.ParameterName = "$name";
command.Parameters.Add(nameParameter);
var emailParameter = command.CreateParameter();
emailParameter.ParameterName = "$email";
command.Parameters.Add(emailParameter);
foreach (var contact in contacts)
{
nameParameter.Value = contact.Name ?? DBNull.Value;
emailParameter.Value = contact.Email ?? DBNull.Value;
command.ExecuteNonQuery();
}
transaction.Commit();
}
Reference: Bulk Insert in Microsoft.Data.Sqlite
Unfortunately, parameters cannot be used to express names for tables or columns. You can use them only to express values in WHERE statement or in UPDATE/INSERT/DELETE operation.
So you should insert your records one by one, or write code to support batch updates like explained in this question
However if you want to experiment with a very useful thirdy party library you can write a very simple code.
This example is done using Dapper
NuGet
Project Site
using(SQLiteConnection connection = GetOpenedConnection())
{
string cmdText = #"INSERT OR REPLACE INTO ZZ_DBA_Stop
(Path, StopName, Latitude, Longitude)
VALUES(#Path, #StopName, #Latitude, #Longitude) ";
connection.ExecuteAsync(cmdText, tempResults);
}
Dapper is a simple ORM that extends the functionality of an IDbConnection. It knows how to handle your models and store and retrieve them from the database.
In the example above you pass your whole list as the second parameter to the ExecuteAsync and Dapper will insert for you the data from the whole list. The only requirement here is that your model's properties have the same name of the fields
GetOpenedConnection is just a placeholder for a method that returns an SQLiteConnection already opened. You can replace it with the code required to create the connection and add a call to open before calling the ExecuteAsync

Dapper to insert multiple rows into two tables using stored procedure

I am using Dapper (https://github.com/StackExchange/Dapper) in asp.net core myweb api project. I have a requirement to create a master record and insert a set of rows into a child table.
Ex. OrdersMaster table
create an order id in this table
OrderDetails table
ordermasterid
order items
How can I do this using Dapper? Please share some code snippets if possible.
I would implement the insert operations as a transactional stored procedure, and then call that from your .NET application.
You may need a table-valued type to pass in a list of data, like this:
CREATE TYPE List_Of_Items AS TABLE (
ItemID INT NOT NULL,
Quantity INT NOT NULL
)
The procedure might look like this
CREATE PROC Insert_Order_With_Details (
#CustomerId INT,
#Items List_Of_Items
) AS
BEGIN
BEGIN TRANSACTION
INSERT INTO OrdersMaster (CustomerId) VALUES #CustomerId
DECLARE #OrderID INT
SET #OrderID = SCOPE_IDENTITY() --last assigned id
INSERT INTO OrderDetails (OrderId, CustomerId, ItemId, Quantity)
SELECT #OrderID, #CustomerID, ItemID, Quantity
FROM #Items
COMMIT
END
Then in C#, I would suggest creating methods for creating your TVP. It is not as simple as you might like. This requires the using Microsoft.SqlServer.Server and using Dapper.Tvp.
//This is a shell to create any kind of TVP
private static void AddTableCore<T>(
this DynamicParametersTvp dp,
string tvpTypeName,
Func<T, SqlDataRecord> valueProjection,
IEnumerable<T> values,
string parameterTableName)
{
var tvp = values
.Select(valueProjection)
.ToList();
//If you pass a TVP with 0 rows to SQL server it will error, you must pass null instead.
if (!tvp.Any()) tvp = null;
dp.Add(new TableValueParameter(parameterTableName, tvpTypeName, tvp));
}
//This will create your specific Items TVP
public static void AddItemsTable(this DynamicParametersTvp dp, IEnumerable<Item> items, string parameterTableName = "Items")
{
var columns = new[]
{
new SqlMetaData("ItemID", SqlDbType.Int)
new SqlMetaData("Quantity", SqlDbType.Int)
};
var projection = new Func<Item, SqlDataRecord>(item =>
{
var record = new SqlDataRecord(columns);
record.SetInt32(0, item.Id);
record.SetInt32(1, item.Quantity);
return record;
});
AddTableCore(dp, "Items", projection, items, parameterTableName);
}
and then where you need to query you might do:
using (var cn = new SqlConnection(myConnectionString))
{
var p = new DynampicParametersTvp(new {
CustomerId = myCustomerId
});
p.AddItemsTable(items);
cn.Execute("Insert_Order_With_Details", p, commandType: CommandType.StoredProcedure);
}
The commandType argument is super important. It defaults to plain SQL text and will error if you send the name of a proc.
If you want to put in multiple orders at once, you'll need to use table-valued parameters and the Dapper.Tvp package.
See this SO question Using Dapper.TVP TableValueParameter with other parameters as well as this documentation on TVP's from Microsoft https://learn.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine. I don't think all SQL vendors support TVPs.
Using stored procedure as mentioned in other answer is good solution. My answer implements the same without Stored Procedure.
You have to use transaction. This way, either all the changes will be committed or rolled back. Code below assumes you are using Identity as Primary Key. Please refer this question for discussion about ##IDENTITY that I have used in below code.
Though code is not complete, I have put detailed comments to explain steps.
using (var connection = new SqlCeConnection("connection_string"))
{
connection.Open();
//Begin the transaction
using (var transaction = connection.BeginTransaction())
{
//Create and fill-up master table data
var paramMaster = new DynamicParameters();
paramMaster.Add("#XXX", ...);
....
//Insert record in master table. Pass transaction parameter to Dapper.
var affectedRows = connection.Execute("insert into OrdersMaster....", paramMaster, transaction: transaction);
//Get the Id newly created for master table record.
//If this is not an Identity, use different method here
newId = Convert.ToInt64(connection.ExecuteScalar<object>("SELECT ##IDENTITY", null, transaction: transaction));
//Create and fill-up detail table data
//Use suitable loop as you want to insert multiple records.
//for(......)
foreach(OrderItem item in orderItems)
{
var paramDetails = new DynamicParameters();
paramDetails.Add("#OrderMasterId", newId);
paramDetails.Add("#YYY", ...);
....
//Insert record in detail table. Pass transaction parameter to Dapper.
var affectedRows = connection.Execute("insert into OrderDetails....", paramDetails, transaction: transaction);
}
//Commit transaction
transaction.Commit();
}
}

How to get list of values from stored procedure using Linq?

I would like to get list of values from stored procedure. How to do it ?
create PROCEDURE Get_ListOf_Holiday
AS
BEGIN
select * from holiday
END
In my Linq :
using (PlanGenEntities3 entity2 = new PlanGenEntities3())
{
var testList = entity2.Get_ListOf_Holiday();
}
But I am always getting values like -1. But in my SQL server I am getting the output like list of holiday details.
How to solve this?
First Drag and Drop your Holiday Table then drag and drop your procedure
When you drag and drop Procedure in LinQ-to-SQL then default return type is always
auto generated type.
For Proper return type u need to rightclick procedure Name and click properties
change the return type equal to class name of Holiday Table.
That much details might help you
First of all, please help me to understand the question.
->Is PlanGenEntities3 a class name? If yes, then please change it because it does not convey its meaning, it should be singular, it should not contain a number [in this case 3]
-> where have u used linq?
-> why do u want to access stored proc and not the table?.. You can get all the values of from 'holiday'..
You can use something like : var x = context.holiday.ToList();`
Possible solution:-
if you are using Entity framework,you need to use a class inherited from System.Data.Entity.DbContext and use a context variable.
Now using this context variable to access data tables.
P.S: I am also new to programming,hope i can help if you share more details
Use Dataset as return Type
string dbConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["DbName"].ConnectionString;
SqlConnection sqlConnection = new SqlConnection(dbConnectionString);
SqlCommand command = new SqlCommand("Proc Name", sqlConnection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("#Id", SqlDbType.Int).Value = Id
command.Parameters.Add("#Name", SqlDbType.VarChar).Value = nameValue;
sqlConnection.Open();
command.CommandTimeout = 30;
SqlDataAdapter da = new SqlDataAdapter(command);
DataSet result = new DataSet();
da.Fill(result);
sqlConnection.Close();
return result;
using(PlanGenEntities3 db = new PlanGenEntities3()){
IList<Entity> list = new List<Entity>();
var query = db.Get_ListOf_Holiday();
foreach (var m in query)
{
list.Add(new Entity()
{
Id = m.Id,
Name = m.Name
});
}
return list;
}

A better way to achieve INSERT without hitting the database multiple times

I have the following, I could make it work as I want to but I think i'm doing it the wrong way, could you please explain how this could be done in a more efficient way ? While also looping on Categories and doing the same as with Districts within the same Insert() Method.
Thanks in advance.
#region Methods
public int Insert(List<District> Districts, List<Category> Categories)
{
StringBuilder sqlString = new StringBuilder("INSERT INTO Stores (name, image) VALUES (#Name, #Image);");
using (SqlConnection sqlConnection = new
SqlConnection(ConfigurationManager.ConnectionStrings["OahuDB"].ConnectionString))
{
SqlCommand sqlCommand = new SqlCommand(sqlString.ToString(), sqlConnection);
sqlCommand.Parameters.AddWithValue("#Name", this.Name);
sqlCommand.Parameters.AddWithValue("#Image", this.Image);
sqlConnection.Open();
int x = (int)sqlCommand.ExecuteScalar();
sqlString.Clear();
sqlCommand.Parameters.Clear();
foreach (District item in Districts)
{
sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (#DistrictID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#DistrictID", item.ID);
sqlCommand.ExecuteNonQuery();
}
return x;
}
}
EDIT
Is is wrong to achieve the above by doing the following ?
sqlString.Clear();
sqlCommand.Parameters.Clear();
sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (#DistrictID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#StoreID", x);
foreach (District item in Districts)
{
sqlCommand.Parameters.AddWithValue("#DistrictID", item.ID);
sqlCommand.ExecuteNonQuery();
}
sqlString.Clear();
sqlCommand.Parameters.Clear();
sqlString.AppendLine("INSERT INTO categories_has_stores (category_id, store_id) VALUES (#CategoryID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#StoreID", x);
foreach (Category item in Categories)
{
sqlCommand.Parameters.AddWithValue("#CategoryID", item.ID);
sqlCommand.ExecuteNonQuery();
}
The first obvious thing is to move the invariant part of the sqlCommand out of the loop
sqlCommand.Parameters.Clear();
sqlString.Clear();
sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (#DistrictID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#DistrictID", 0); // as dummy value
sqlCommand.Parameters.AddWithValue("#StoreID", x); // invariant
foreach (District item in Districts)
{
sqlCommand.Parameters["#DistrictID"].Value = item.ID;
sqlCommand.ExecuteNonQuery();
}
But this doesn't answer your fundamental problem. How to avoid hitting the database multiple times.
You could build a query with multiple inserts like this
sqlString.Clear();
sqlString.Append("INSERT INTO districts_has_stores (district_id, store_id) VALUES (");
foreach(District item in Districts)
{
sqlString.Append(item.ID.ToString);
sqlString.Append(", ")
sqlString.Append(x.ToString());
sqlString.Append("),");
}
sqlString.Length--;
sqlCommand.CommandText = sqlString.ToString()
But string concatenation is really a bad practice and I present this solution just as an example and I don't want to suggest this kind of approach.
The last possibility are Table-Valued Parameters (Only from SqlServer 2008).
First you need to create a Sql Type for the table you will pass in
CREATE TYPE dbo.DistrictsType AS TABLE
( DistrictID int, StoreID int )
and a StoredProcedure that will insert the data from the datatable passed in
CREATE PROCEDURE usp_InsertDistricts
(#tvpNewDistricts dbo.DistrictsType READONLY)
AS
BEGIN
INSERT INTO dbo.Districts (DistrictID, StoreID)
SELECT dt.DistrictID, dt.StoreID FROM #tvpNewDistricts AS dt;
END
then, back to your code you pass the district into the storedprocedure
(Probably you need to convert your List in a DataTable)
DataTable dtDistricts = ConvertListToDataTable(Districts);
SqlCommand insertCommand = new SqlCommand("usp_InsertDistricts", sqlConnection);
SqlParameter p1 = insertCommand.Parameters.AddWithValue("#tvpNewDistricts", dtDistricts);
p1.SqlDbType = SqlDbType.Structured;
p1.TypeName = "dbo.DistrictsType";
insertCommand.ExecuteNonQuery();
Well, if you look back at the link above, you will find other ways to pass your data in a single step to the database backend.... (Scroll to the end and you will find also a method that doesn't require a stored procedure on the database)
Assuming Stores has an identity column, in SQL Server, create a table type and a table-valued parameter to take advantage of it:
CREATE TYPE dbo.DistrictsTVP AS TABLE
(
DistrictID INT -- PRIMARY KEY? I hope so.
);
GO
CREATE PROCEDURE dbo.InsertStoreAndDistricts
#Name NVARCHAR(255),
#Image <some data type???>,
#Districts dbo.DistrictsTVP READONLY
AS
BEGIN
SET NOCOUNT ON;
DECLARE #StoreID INT;
INSERT dbo.Stores(name, [image]) SELECT #Name, #Image;
SET #StoreID = SCOPE_IDENTITY();
INSERT dbo.district_has_stores(district_id, store_id)
SELECT DistrictID, #StoreID
FROM #Districts;
END
GO
Then in C#, you can pass your List in directly without any looping:
using (...)
{
SqlCommand cmd = new SqlCommand("dbo.InsertStoreAndDistricts", sqlConnection);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#Districts", Districts);
tvparam.SqlDbType    = SqlDbType.Structured;
// other params here - name and image
cmd.ExecuteNonQuery();
}
Recently in my project i used XML as a data type in my stored proc and did insert update and delete in just one shot instead of hitting the database multiple times .
Sample Stored proc
ALTER PROCEDURE [dbo].[insertStore]
#XMLDATA xml,
#name varchar(50),
#image datatype
AS
Begin
INSERT INTO Store
(name
,image
)
Select XMLDATA.item.value('#name[1]', 'varchar(10)') AS Name,
XMLDATA.item.value('#image[1]', 'yourData type') AS Image
FROM #XMLDATA.nodes('//Stores/InsertList/Store') AS XMLDATA(item)
END
Similarly you can write for update and delete .In C# u need to create the xml
public string GenerateXML(List<District> Districts)
var xml = new StringBuilder();
var insertxml = new StringBuilder();
xml.Append("<Stores>");
for (var i = 0; i < Districts.Count; i++)
{ var obj = Districts[i];
insertxml.Append("<Store");
insertxml.Append(" Name=\"" + obj.Name + "\" ");
insertxml.Append(" Image=\"" + obj.Image + "\" ");
insertxml.Append(" />");
}
xml.Append("<InsertList>");
xml.Append(insertxml.ToString());
xml.Append("</InsertList>");
SqlCommand cmd= new SqlCommand("insertStore",connectionString);
cmd.CommandType=CommandType.StoredProcedure;
SqlParameter param = new SqlParameter ();
param.ParameterName ="#XMLData";
param.value=xml;
paramter.Add(param);
cmd.ExecuteNonQuery();
Personally, I would create a stored procedure for the insert and pass in a Table-Valued param, which would allow you to do
INSERT tbl (f1, f2, ... fN)
SELECT * FROM #TVP
http://msdn.microsoft.com/en-us/library/bb510489.aspx
Unless you're using SQL 2005, then I would use an XML param in my stored proc and Serialize a collection to be inserted.
Think about your system design. Where is the data that you need to insert coming from? If it's already in the database, or another database, or some other kind of data store, you should be able to achieve a more bulk kind of transfer, simply inserting from one database to the other in a loop in stored procedure.
If the data is coming from a user, or some incompatible data store, like say an export from some third party program, then you basically have to realize that to get it into the database will involve quite of few round-trips to the database. You can use some tables, or XML or such , but those are actually closer to doing a bulk insert using other methods.
The bottom line is that SQL databases are designed to do inserts one at a time. This is 99% of the time OK because you are never asking users using the UI to type in thousands of things at one time.

Categories