Update 2000 records with one query - asp.net

I have a Database table:
Item
ID (uniqueidentifier)
Index (int)
I have a list of 2000 key-value pairs items where the key is ID and value is Index, which i need to update it. How can i update all the 2000 items from database using one single sql query?
Right now i have something like this:
// this dictionary has 2000 values
Dictionary<Guid, int> values = new Dictionary<Guid,int>();
foreach(KeyValuePair<Guid, int> item in values)
{
_db.Database.ExecuteSqlCommand("UPDATE [Item] SET [Index] = #p0 WHERE [Id] = #p1", item.Value, item.Key);
}
However, i am making too many requests to the SQL Server, and i want to improve this.

Use table value parameters to send those values to SQL Server and update Items table in one shot:
CREATE TYPE KeyValueType AS TABLE
(
[Key] GUID,
[Value] INT
);
CREATE PROCEDURE dbo.usp_UpdateItems
#pairs KeyValueType READONLY
AS
BEGIN
UPDATE I
SET [Index] = P.Value
FROM
[Item] I
INNER JOIN #pairs P ON P.Id = I.Id
END;
GO

If you really need to update in that manner and have no other alternative - the main way around it could be this rather "ugly" technique (and therefore rarely used, but still works pretty well);
Make all 2000 statements in one string, and execute that one string. That makes one call to the database with the 2000 updates in it.
So basically something like this (code not made to actually run, it's an example so t
Dictionary<Guid, int> values = new Dictionary<Guid, int>();
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (KeyValuePair<Guid, int> item in values)
{
sb.Append(String.Format("UPDATE [Item] SET [Index] = {0} WHERE [Id] = '{1}';", item.Value, item.Key));
}
_db.Database.ExecuteSqlCommand(sb.ToString);

Related

sql lite update query list parameter issue

I have a simple sql lite query using room like this.
#Query("UPDATE MYTABLE SET CAT_ID = :idCata WHERE MYTABLE_ID IN (:listIds)")
int updateAll(long idCata, String listIds);
Where CAT_ID is defined as a nullable foreign key and MYTABLE_ID is primary key for MYTABLE.
All is working if i use one value in listIds parameter.
If i put something like "1,5" this does not update the rows in the database. If i use "1" or "5" as single value in listIds, all is ok.
I have nothing in logcat or something like issue in logs.
Can someone explain me why ?
EDIT : I posted answer
I believe that the issue is that 1,5 will be enclosed in single quotes and will be treated as a string. So only rows that have 1,5 as the value would be updated.
When you use IN the values should in an array Room will then build the correct IN clause. So you want :-
#Query("UPDATE MYTABLE SET CAT_ID = :idCata WHERE MYTABLE_ID IN (:listIds)")
int updateAll(long idCata, String[] /*<<<<<<<<<<*/ listIds);
Obviously you need to split listIds into the array.
or
int updateAll(long idCata,listIds.split(','));
which does the split
Here is the solution, note i used long array because the query is updating foreign key values which cannot be typed as string.
I think it is more elegant and safer. So the final code is :
String[] ids = listIds.split(",");
long[] idsForQuery = new long[ids.length];
try{
for(int i = 0; i < ids.length; i++){
idsForQuery[i] = Long.parseLong(ids[i]);
}
mDao.updateAll(idCata, idsForQuery);
}
catch(NumberFormatException e){
// something wrong with ids type !!
}
And in the dao, the sql lite query.
#Query("UPDATE MYTABLE SET CAT_ID = :idCata WHERE MYTABLE_ID IN (:listIds)")
int updateAll(long idCata, long[] listIds);
Thanks to MikeT for the help.

Reducing round-trips to database

I'm using ASP.NET Web Forms and trying to load data from SQL server. Here's the pseudo-code on how I do it:
connect1 = connect("database")
categories = connect.query("select * from category")
loop categories as category
print category
connect2 = connect("database")
subCategories = connect2.query("select * from subCategory where category = #0", category)
loop subCategories as subCategory
print subCategory
connect3 = connect("database")
items = connect3.query("select * from item where subCategory = #0", subCategory)
loop items as item
print item
end loop 'items
connect3.close
end loop 'subcategories
connect2.close
end loop 'categories
connect1.close
As you can see, there are lots of round-trips going on in my script, this is fine when I only have few records but when dealing with hundreds or more, this takes forever to display the data.
What can I do to reduce the number of round-trips? I thought of getting all the data at once from the database then categorize them in the application side but is that possible?
Why don't you get all data you need by one query with joins and then filter in on a client-side; Or other way you can do it (it there's not too much data) is getting data as xml, deserialize it to ienumerable an iterate through.
As i see it you do
categories = connect.query("select * from category");
so all you need is:
whole_data = connect.query("select * from category c inner join subCategory sc on c.id = sc.id inner join item i on i.id = si.id") /*or some kind of*/
/*let me think that whole_data is a list of objects, not a dataset*/
categories = whole_data.Distinct(d => d.category);
subCategories = whole_data.Distinct(d => d.subCategories);
/*and then do your loops*/
c# code for manual mapping might be like that:
using (var connection = new SqlConnection(connString))
{
connection.Open();
var command = connection.CreateCommand();
command.CommandText = "select * from ...";
var reader = command.ExecuteReader();
while (reader.Read())
{
var a = reader.GetInt32(0);
var b = reader.GetString(1);
/*so basically you read all fields that you get from DB and build object here, then you add it to List or any other kind of collection*/
}
}
Depending upon the latency to your database, making the connections, even if pooled, can take a long time. To avoid that over head, make all your connections outside of the loops. That is, don't nest the connections. Instead, structure it like this:
Connect1 = connect("database")
Connect2 = connect("database")
Connect3 = connect("database")
sql 1 nest
sql 2 nest
sql 3 nest
end nest
end nest
end nest
close connections.
If you have 10 entries per loop, and a connection takes 10 mSec, you will spend 10 x 10 x 10 = 1000 mS just doing connections. Take them outside the nest, then you spend only 30 mS making connections. Close your datareader at the completion of each nest so the connection can be reused.
Of course, for the example you showed, doing a single query is the best solution.
But, if your query is selective and you need to perform some business logic that cannot be combined in the query, then always move your connections outside of the loop.

Linq. Anonymous type error when joining to multiple tables

Im trying to return an IQueryable based on my model.
But I need to join to the same lookup table twice. Then return the query variable to the gridview.
public IQueryable<Benchmark> GetBenchMarks([QueryString("hydrant")] string hydrant,
[QueryString("revdate")] string revdate, [QueryString("street")] string street,
[QueryString("quadrant")] string quadrant, [QueryString("desc")] string desc) {
IQueryable<Benchmark> query = from p in _db.Benchmarks
join s in _db.Streets on p.Street1Number equals s.Id
join s2 in _db.Streets on p.Street2Number equals s2.Id
select new {
Street1Name = s.StreetName,
p.OrderNumber,
p.HydrantNumber,
Street2Name = s2.StreetName,
p.RevisionDate,
p.Quadrant,
p.Description,
p.Street1Number
};
}
So there is a red squiggle line on the 2nd join to s2. And the following error.
Error 5 Cannot implicitly convert type
'System.Linq.IQueryable<AnonymousType#1>' to
'System.Linq.IQueryable<Benchmarks.Model.Benchmark>'. An explicit
conversion exists (are you missing a
cast?) C:\Projects\Benchmarks\Benchmarks\Benchmarks_Home.aspx.cs 63 25 Benchmarks
Since you end your query with select new {...}, you are creating an anonymous object for each result. Instead, use select p, and each result will be a Benchmark.
However, it looks like returning a Benchmark is not what you want. In this case, you would want to change query to be of type IQueryable or IQueryable<dynamic> (and probably change the return type of the GetBenchMarks function as well, unless it does return IQueryable<Benchmark>!).
A second (potentially better) alternative would be to create a class to represent this anonymous type, and use that.
The result of your query is IEnumerable of anonymous objects, thus it cannot be converted to Benchmark.
If you want to set some additional properties (Street1Name - that are evidently not mapped on DB) from joined relations you can do:
IQueryable<Benchmark> query = from p in _db.Benchmarks
join s in _db.Streets on p.Street1Number equals s.Id
join s2 in _db.Streets on p.Street2Number equals s2.Id
select new {
....
};
var ex = query.ToList();
var result = new List<Benchmark>();
foreach(bn in ex){
result.Add(new Benchmark{ OrderNumber = bn.OrderNumber .... });
}
// return result.AsQueryable();
// but now it losts the point to return it as queryable, because the query was already executed so I would simply reurn that list
return result;
Another option is to make new class representing the object from the query and return it from the method like:
... select new LoadedBenchmark { Street1Name = s.StreetName ....}

Passing an object collection as a parameter into SQL Server stored procedure

I have a general question on whether something can be done - and whether it will be the most efficient way of doing it !
To summarise: can I pass an object collection as a parameter to a stored procedure?
Let's say that I have a SQL Server table called Users [UserID, Forename, Surname]
and another table called Hobbies [HobbyID, UserID, HobbyName, HobbyTypeID]
This set up is to record multiple hobbies against a user.
In my application, I want to update the user record.
Normally - I would update the user table and then in code, loop through each hobby and update the hobbies table record by record.
If I'm updating the user forename and 2 of their hobbies, this would require 3 calls to the database.
(1 call to a stored procedure to update the forename/surname, and 2 calls to a stored procedure to update the 2 hobby records)
My question is:
Can I make just 1 call to the database by passing all the parameters to just 1 stored procedure.
eg.
intUserID = 1
strForename = "Edward"
strSurname = "ScissorHands"
dim objHobbyCollection as New List(Of Hobby)
'Assume that I have 2 hobby objects, each with their hobbyID, UserID, HobbyName & HobbyTypeID
Dim params As SqlParameter()
params = New SqlParameter() {
New SqlParameter("#UserID", intUserID),
New SqlParameter("#Forename", strForename),
New SqlParameter("#Surname", strSurname),
New SqlParameter("#Hobbies", objHobbyCollection)
}
Can I do this ? (and which way would be more efficient?)
What would the Stored Procedure look like ?
ALTER PROCEDURE [dbo].[User_Update]
#UserID INT
,#Forename NVARCHAR(50) = NULL
,#Surname NVARCHAR(50) = NULL
,#Hobbies ??????????????
Assuming SQL Server 2008+, you can do this using a table-valued parameter. First in SQL Server create a table type:
CREATE TYPE dbo.HobbiesTVP AS TABLE
(
HobbyID INT PRIMARY KEY,
HobbyName NVARCHAR(50),
HobbyTypeID INT
);
Then your stored procedure would say:
#Hobbies dbo.HobbiesTVP READONLY
In C# (sorry I don't know vb.net equivalent) it would be as follows (but if you just have one UserID, this doesn't need to be part of the collection, does it?):
// as Steve pointed out, you may need to have your hobbies in a DataTable.
DataTable HobbyDataTable = new DataTable();
HobbyDataTable.Columns.Add(new DataColumn("HobbyID"));
HobbyDataTable.Columns.Add(new DataColumn("HobbyName"));
HobbyDataTable.Columns.Add(new DataColumn("HobbyTypeID"));
// loop through objHobbyCollection and add the values to the DataTable,
// or just populate this DataTable in the first place
using (connObject)
{
SqlCommand cmd = new SqlCommand("dbo.User_Update", connObject);
cmd.CommandType = CommandType.StoredProcedure;
// other params, e.g. #UserID
SqlParameter tvparam = cmd.Parameters.AddWithValue("#Hobbies", HobbyDataTable);
tvparam.SqlDbType = SqlDbType.Structured;
// ...presumably ExecuteNonQuery()
}

Use linq to get parent objects based on one of the property in a self referencing table

I have a table called Quiz that have these fields
id As Int
created As DateTime
header As Sring
body As String
fk_parent As int
url As String
All the items without parent key would be the question, and ones that have the parent key would be the answer. I am having the problem to get all the latest active questions (based both on questions created time and and answer created time).
I am struggling to write a Linq query that can do the above task.
Here's a start:
IQueryable<int> keys =
from x in dc.Quiz
let masterID = ParentId.HasValue ? ParentId.Value : Id
group x by masterID into g
order g by g.Max(x => x.created) descending
select g.Key
List<Quiz> = dc.Quiz.Where(x => keys.Take(20).Contains(x.Id)).ToList();
This assumes answers aren't parents of answers... If they can - you have an arbitrary depth tree walk on your hands, which is a wrong shaped nail for Linq and for SQL.
You could try joining the table on itself in LINQ and creating a new object that holds the Questions and answers:
var QandA = from q in tbl
join a in tbl on q.id equals a.fk_parent
select new {
QHeader = q.header,
QBody = q.body,
QUrl = q.url,
AHeader = a.header,
ABody = a.body,
AUrl = a.url
};
I think this is how your table is setup, but I might have the join wrong.

Resources