I'm running this query in LINQ:
var unalloc = db.slot_sp_getUnallocatedJobs("Repair",
RadComboBox1.SelectedValue, 20);
It runs when I first open the page, but when I go back to it and try to run the same query with a different value, "Con", being passed through, the linq to sql designer.cs tells me that I've got a timeout error.
Any ideas?
Edit: This is what's in the designer:
[Function(Name="dbo.slot_sp_getUnallocatedJobs")]
Public ISingleResult<slot_sp_getUnallocatedJobsResult> slot_sp_getUnallocatedJobs([Parameter(Name="JobType", DbType="VarChar(20)")] string jobType, [Parameter(Name="Contract", DbType="VarChar(10)")] string contract, [Parameter(Name="Num", DbType="Int")] System.Nullable<int> num)
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), jobType, contract, num);
return ((ISingleResult<slot_sp_getUnallocatedJobsResult>)(result.ReturnValue));
}
}
This is the error:
SQLException was unhandled by user code
Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Related
Using EF Core 2.2.6 and Pomelo.EntityFrameworkCore.MySql 2.2.6 (with MySqlConnector 0.59.2)). I have a model for UserData:
public class UserData
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public ulong ID { get; private set; }
[Required]
public Dictionary<string, InventoryItem> Inventory { get; set; }
public UserData()
{
Data = new Dictionary<string, string>();
}
}
I have a REST method that can be called that will add items to the user inventory:
using (var transaction = context.Database.BeginTransaction())
{
UserData data = await context.UserData.FindAsync(userId);
// there is code here to detect duplicate entries/etc, but I've removed it for brevity
foreach (var item in items) data.Inventory.Add(item.ItemId, item);
context.UserData.Update(data);
await context.SaveChangesAsync();
transaction.Commit();
}
If two or more calls to this method are made with the same user id then I get concurrent accesses (despite the transaction). This causes the data to sometimes be incorrect. For example, if the inventory is empty and then two calls are made to add items simultaneously (item A and item B), sometimes the database will only contain either A or B, and not both. From logging it appears that it is possible for EF to read from the database while the other read/write is still occurring, causing the code to have the incorrect state of the inventory for when it tries to write back to the db. So I tried marking the isolation level as serializable.
using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
Now I sometimes see an exception:
MySql.Data.MySqlClient.MySqlException (0x80004005): Deadlock found when trying to get lock; try restarting transaction
I don't understand how this code could deadlock... Anyways, I tried to proceed by wrapping this whole thing in a try/catch, and retry:
public static async Task<ResponseError> AddUserItem(Controller controller, MyContext context, ulong userId, List<InventoryItem> items, int retry = 5)
{
ResponseError result = null;
try
{
using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
UserData data = await context.UserData.FindAsync(userId);
// there is code here to detect duplicate entries/etc, but I've removed it for brevity
foreach (var item in items) data.Inventory.Add(item.ItemId, item);
context.UserData.Update(data);
await context.SaveChangesAsync();
transaction.Commit();
}
}
catch (Exception e)
{
if (retry > 0)
{
await Task.Delay(SafeRandomGenerator(10, 500));
return await AddUserItem(controller, context, userId, items, retry--);
}
else
{
// store exception and return error
}
}
return result;
}
And now I am back to the data being sometimes correct, sometimes not. So I think the deadlock is another problem, but this is the only method accessing this data. So, I'm at a loss. Is there a simple way to read from the database (locking the row in the process) and then writing back (releasing the lock on write) using EF Core? I've looked at using concurrency tokens, but this seems overkill for what appears (on the surface to me) to be a trivial task.
I added logging for mysql connector as well as asp.net server and can see the following failure:
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
=> RequestId:0HLUD39EILP3R:00000001 RequestPath:/client/AddUserItem => Server.Controllers.ClientController.AddUserItem (ServerSoftware)
Failed executing DbCommand (78ms) [Parameters=[#p1='?' (DbType = UInt64), #p0='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
UPDATE `UserData` SET `Inventory` = #p0
WHERE `ID` = #p1;
SELECT ROW_COUNT();
A total hack is to just delay the arrival of the queries by a bit. This works because the client is most likely to generate these calls on load. Normally back-to-back calls aren't expected, so spreading them out in time by delaying on arrival works. However, I'd rather find a correct approach, since this just makes it less likely to be an issue:
ResponseError result = null;
await Task.Delay(SafeRandomGenerator(100, 500));
using (var transaction = context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
// etc
This isn't a good answer, because it isn't what I wanted to do, but I'll post it here as it did solve my problem. My problem was that I was trying to read the database row, modify it in asp.net, and then write it back, all within a single transaction and while avoiding deadlocks. The backing field is JSON type, and MySQL provides some JSON functions to help modify that JSON directly in the database. This required me to write SQL statements directly instead of using EF, but it did work.
The first trick was to ensure I could create the row if it didn't exist, without requiring a transaction and lock.
INSERT INTO UserData VALUES ({0},'{{}}','{{}}') ON DUPLICATE KEY UPDATE ID = {0};
I used JSON_REMOVE to delete keys from the JSON field:
UPDATE UserData as S set S.Inventory = JSON_REMOVE(S.Inventory,{1}) WHERE S.ID = {0};
and JSON_SET to add/modify entries:
UPDATE UserData as S set S.Inventory = JSON_SET(S.Inventory,{1},CAST({2} as JSON)) WHERE S.ID = {0};
Note, if you're using EF Core and want to call this using FromSql then you need to return the entity as part of your SQL statement. So you'll need to add something like this to each SQL statement:
SELECT * from UserData where ID = {0} LIMIT 1;
Here is a full working example as an extension method:
public static async Task<UserData> FindOrCreateAsync(this IQueryable<UserData> table, ulong userId)
{
string sql = "INSERT INTO UserData VALUES ({0},'{{}}','{{}}') ON DUPLICATE KEY UPDATE ID = {0}; SELECT * FROM UserData WHERE ID={0} LIMIT 1;";
return await table.FromSql(sql, userId).SingleOrDefaultAsync();
}
public static async Task<UserData> JsonRemoveInventory(this DbSet<UserData> table, ulong userId, string key)
{
if (!key.StartsWith("$.")) key = $"$.\"{key}\"";
string sql = "UPDATE UserData as S set S.Inventory = JSON_REMOVE(S.Inventory,{1}) WHERE S.ID = {0}; SELECT * from UserData where ID = {0} LIMIT 1;";
return await table.AsNoTracking().FromSql(sql, userId, key).SingleOrDefaultAsync();
}
Usage:
var data = await context.UserData.FindOrCreateAsync(userId);
await context.UserData.JsonRemoveInventory(userId, itemId);
I have a game where I use (Spring, Hibernate and MySQL 5.7) in the back-end. At the end of each game, I execute a native query to bulk update balance of the winners. Most games the update executes successfully for all winners, but for a few games I face the problem of updating the balance for most of them except for one or two (random). The update doesn't throw an error and as said Most of the winners' balance is updated successfully. Here is the method in my DAO (I summed it up) with the native query:
#Override
public Integer addAmountToPlayerBalance(List<Long> playerIds, Double amount, Long gameId) {
try
{
StringBuilder queryNative = new StringBuilder();
queryNative.append("update game_player_user set balance = ifnull(balance,0) + :amount where id in (:playerIds)");
Session session = teleEM.unwrap(Session.class);
org.hibernate.Query query = session.createSQLQuery(queryNative.toString());
query.setParameter("amount", amount);
query.setParameterList("playerIds", playerIds);
int numOfUpdatedRecords = query.executeUpdate();
return numOfUpdatedRecords;
}
catch (NoResultException e)
{
return null;
}
}
Notes:
1) I added the code for returning the number of updated records a week ago but it hasn't occurred on production since then. but as I said, it happens occasionally.
2) The list of player Ids includes all of the winners even in the games where the issue appears.
3) The amount is double but rounded to 2 decimal places.
I am using spring NamedParameterJdbcTemplate with my sql.I am executing 2 queries through jbdc template it is taking 1.1 and 4 s respectively. but the same query if I am running in my sql it is taking 0.5 and 1 s respectively I don't understand what could be my bottleneck. my application and db resides on same server so there can be no network overhead, I have my connection pooled. I can say it is working because query with less amount of data taking 50 ms through application. please let me know what could be my bottleneck
below is my code of namedjdbcTemplate
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("organizationIds", list);
parameters.addValue("fromDate", fromDate);
parameters.addValue("toDate", toDate);
long startTime = System.currentTimeMillis();
List<MappingUI> list =namedParameterJdbcTemplate.query(QueryList.CALCULATE_SCORE,parameters,new RowMapper<MappingUI>() {
#Override
public MappingUI mapRow(ResultSet rs, int rowNum) throws SQLException {
MappingUI mapping= new MappingUI();
mapping.setCompetitor(rs.getInt("ID"));
mapping.setTotalRepufactScore(rs.getFloat("AVG_SCORE"));
return mapping;
}
});
below is my query
SELECT AVG(SCORE.SCORE) AS AVG_SCORE,ANALYSIS.ID AS ID FROM SCORE SCORE,QC_ANALYSIS ANALYSIS WHERE SCORE.TAG_ID = ANALYSIS.ID AND ANALYSIS.ORGANIZATION_ID IN (:organizationIds) AND DATE(ANALYSIS.DATES) BETWEEN DATE(:fromDate) AND DATE(:toDate) GROUP BY ANALYSIS.ORGANIZATION_ID
I have a function that takes a few parameters and is generating a unique varchar id to be returned to the application. When I call the function from my java application it executes (the data is inserted), but when I try to retrieve the outparam it throws SQL exception
Parameter index out of range (1 > number of parameters, which is 0).
java code:
String sql = "{?=call mytable.myFunction (?,?,?,?,?)}";
CallableStatement tmt = sqlConn.prepareCall(sql);
tmt.registerOutParameter(1, Types.VARCHAR);
tmt.setString(2, firstName);
tmt.setString(3, lastName);
tmt.setDate(4, new java.sql.Date(dob.getTime()));
tmt.setString(5, null);
tmt.setString(6, null);
tmt.execute();
String accountNumber = tmt.getString(1);
I'm using Navicat to work with MySQL, and the procedure executes in Navicat and shows the correct return value. However, when I call tmt.getString(1) from java app, it throws the above exception.
Please, help.
Thank you.
I'm using LINQ to SQL to call sprocs at my company. Normally it works great but on some queries, if nothing is found it will throw a SqlException "No Records Found".
How should I handle this case?
Here is an example call I would make:
/// <summary>
/// Gets the pending messages.
/// </summary>
/// <param name="historySearchCriteria">The history search criteria.</param>
/// <returns><c>List</c> of pending messages.</returns>
public List<PendingMessage> GetPendingMessages(HistorySearchCriteria historySearchCriteria)
{
using (MessageDataContext db = new MessageDataContext(DatabaseProperties.MessageConnectionString))
{
List<PendingMessage> pendingMessages = new List<PendingMessage>();
pendingMessages.AddRange(db.usp_search_message_pending(historySearchCriteria.AccountId,
historySearchCriteria.TrackingNumber,
historySearchCriteria.StartDateTime,
historySearchCriteria.EndDateTime)
.Select(p => new PendingMessage()
{
Account = p.account,
ActionType = (OrderActionType) Enum.Parse(typeof(OrderActionType), p.action_type.ToString()),
AttemptsRemaining = p.attempts_remaining,
Message = p.message
}));
return pendingMessages;
}
}
What is the best way to handle the fact that I simply want to return an empty list if no records are found.
You could simply catch that Exception, and return new List<PendingMessage>; within the handler.
You could use DefaultIfEmpty.
Somthing like:
pendingMessages.AddRange(
db.usp_search_message_pending
(
historySearchCriteria.AccountId,
historySearchCriteria.TrackingNumber,
historySearchCriteria.StartDateTime,
historySearchCriteria.EndDateTime
)
.DefaultIfEmpty()
.Select( /* select clause here */)
);
Where does the text "No Records Found" come from?
Execute the stored procedure from Management Studio using the same parameters that result in the exception.
Does SSMS report an error?
If not, separate the C# line into 3 steps:
Invoking the stored procedure
Check for != null and invoke Select()
Check for != null and call AddRange()