Multithreading using Task.Factory and Entity Framework? - c#

I have an application that is notifying each subscriber in our SMS database of an update to our system. It uses the entity framework to select each record and then creates a new Task to send a message to that person. Theoretically, this should be a fast process. I'm doing something wrong because it is only getting one complete response every second or two.
I think that the problem has to do with the way I'm setting up the Tasks in Task.Factory.StartNew(). It is acting like it is running synchronously, but I want it to run asynchronously.
If I am completely off-base with how I am using Tasks, please let me know. I got my inspiration from this post.
Here's my code:
class Program
{
static List<MessageToSend> Messages = new List<MessageToSend>();
static Entities oDatabase = new Entities();
static SMS.API oAPI = new SMS.API();
const string sAuthToken = "*****";
const string sNotificationMessage = "*****";
static void Main(string[] args)
{
foreach (var subscriber in oDatabase.SMS_Subscribers.Where(x => x.GlobalOptOut == false))
{
MessageToSend oMessage = new MessageToSend();
oMessage.ID = subscriber.ID;
oMessage.MobileNumber = subscriber.MobileNumber;
var recentlySentMessage = oDatabase.SMS_OutgoingMessages.Where(x => x.Message == sNotificationMessage && x.MobileNumber == oMessage.MobileNumber && x.Sent > new DateTime(2014, 3, 12)).FirstOrDefault();
if (recentlySentMessage != null)
{
oMessage.Completed = true;
continue;
}
Task t = Task.Factory.StartNew(() =>
{
try{
var keywordID = oDatabase.SMS_SubscribersKeywords.Where(x => x.SubscriberID == oMessage.ID).First().KeywordID;
var keyword = oDatabase.SMS_Keywords.Where(x => x.ID == keywordID).First();
oMessage.DemographicID = keyword.DemographicID;
oMessage.Keyword = keyword.Keyword;
SendNotificationMessage(oMessage);
}
catch (Exception oEx){ //Write exception to console}
});
Thread.Sleep(15);
}
while (Messages.ToList().Any(x => !x.Completed)){ //wait till all are completed}
}
public static void SendNotificationMessage(object message)
{
MessageToSend oMessage = (MessageToSend)message;
try
{
SMS.APIResponse oResponse = oAPI.SendMessage(sAuthToken, oMessage.DemographicID, oMessage.Keyword, oMessage.MobileNumber, sNotificationMessage);
if (oResponse.Success){ //Write success to console }
else{ //Write failure to console }
}
catch (Exception oEx){ //Write Exception to console }
oMessage.Completed = true;
}
}
class MessageToSend
{
public long ID { get; set; }
public long DemographicID {get;set;}
public string MobileNumber { get; set; }
public bool Completed { get; set; }
public string Keyword { get; set; }
public MessageToSend(){ Completed = false; }
}
EDIT: The inside of the foreach block now looks like this:
MessageToSend oMessage = new MessageToSend();
oMessage.ID = subscriber.ID;
oMessage.MobileNumber = subscriber.MobileNumber;
int keywordID = 0;
SMSShortcodeMover.SMS_Keywords keyword;
var recentlySentMessage = oDatabase.SMS_OutgoingMessages.Where(x => x.Message == sNotificationMessage && x.MobileNumber == oMessage.MobileNumber && x.Sent > new DateTime(2014, 3, 12)).FirstOrDefault();
if (recentlySentMessage != null)
{
oMessage.Completed = true;
continue;
}
try
{
keywordID = (int)oDatabase.SMS_SubscribersKeywords.Where(x => x.SubscriberID == oMessage.ID).First().KeywordID;
keyword = oDatabase.SMS_Keywords.Where(x => x.ID == keywordID).First();
} catch (Exception oEx){ //write exception to console, then continue; }
Task t = Task.Factory.StartNew(() =>
{
oMessage.DemographicID = keyword.DemographicID;
oMessage.Keyword = keyword.Keyword;
SendNotificationMessage(oMessage);
});
Thread.Sleep(15);
}
EDIT 2:
I updated my code again, now I gather all of my data before I go into the send. It still is hanging somewhere, but it gets all 52,000 rows of data in about 5 seconds now. The code looks like this:
var query =
(from subscriber in oDatabase.SMS_Subscribers
where subscriber.GlobalOptOut == false
where !(from x in oDatabase.SMS_OutgoingMessages
where x.Message == sNotificationMessage
where x.MobileNumber == subscriber.MobileNumber
where x.Sent > new DateTime(2014, 3, 12)
select x).Any()
join sk in oDatabase.SMS_SubscribersKeywords
on subscriber.ID equals sk.SubscriberID
join k in oDatabase.SMS_Keywords on sk.KeywordID equals k.ID into ks
from k2 in ks.Take(1)
select new MessageToSend()
{
ID = subscriber.ID,
MobileNumber = subscriber.MobileNumber,
DemographicID = k2.DemographicID,
Keyword = k2.Keyword
}).ToList();
foreach( var q in query){
Task t = Task.Factory.StartNew(() => SendNotificationMessage(q));
Tasks.Add(t);
Thread.Sleep(80);
}
Task.WaitAll(Tasks.ToArray());

If I were you I would try to execute all of the database calls at once, before trying to sen your messages.
Try doing this:
var query =
from subscriber in oDatabase.SMS_Subscribers
where subscriber.GlobalOptOut == false
where !(from x in oDatabase.SMS_OutgoingMessages
where x.Message == sNotificationMessage
where x.MobileNumber == subscriber.MobileNumber
where x.Sent > new DateTime(2014, 3, 12)
select x
).Any()
join sk in oDatabase.SMS_SubscribersKeywords
on subscriber.ID equals sk.SubscriberID
join k in oDatabase.SMS_Keywords on sk.KeywordID equals k.ID into ks
from k2 in ks.Take(1)
select new
{
ID = subscriber.ID,
MobileNumber = subscriber.MobileNumber,
DemographicID = k2.DemographicID,
Keyword = k2.Keyword
};
var tasks =
from x in query.ToArray()
let message = new MessageToSend()
{
ID = x.ID,
MobileNumber = x.MobileNumber,
DemographicID = x.DemographicID,
Keyword = x.Keyword
}
select Task.Factory.StartNew(() => SendNotificationMessage(message));
Task.WaitAll(tasks.ToArray());
I don't have your database, so I can't test this, but something like this should work if this isn't exactly right.

It doesn't surprise me that it's taking 1-2 seconds for each iteration of the for each loop because you have 3 separate database calls that are executing synchronously. A database call is pretty slow in the grand scheme of things. One way to approach this would be to have a method that has everything in the foreach block except the Task code and then use task to call it. Just need to be careful that nothing inside the Task Method ends up blocking.
I.e.
var tasks = new List<Task>();
foreach (var subscriber in oDatabase.SMS_Subscribers.Where(x => x.GlobalOptOut == false))
{
tasks.Add(Task.Factory.StartNew(() => SendNotificationTask(subscriber));
Thread.Sleep(15);
}
//Might want to to use Task.WhenAll instead of WaitAll. Just need to debug it and see what happens.
Task.WaitAll(tasks.ToArray());
public void SendNotificationTask(SomeType subscriber)
{
MessageToSend oMessage = new MessageToSend();
oMessage.ID = subscriber.ID;
oMessage.MobileNumber = subscriber.MobileNumber;
int keywordID = 0;
SMSShortcodeMover.SMS_Keywords keyword;
////Database call 1
var recentlySentMessage = oDatabase.SMS_OutgoingMessages.Where(x => x.Message == sNotificationMessage && x.MobileNumber == oMessage.MobileNumber && x.Sent > new DateTime(2014, 3, 12)).FirstOrDefault();
if (recentlySentMessage != null)
{
oMessage.Completed = true;
}
else
{
try
{
////Database call 2
keywordID = (int)oDatabase.SMS_SubscribersKeywords.Where(x => x.SubscriberID == oMessage.ID).First().KeywordID;
////Database call 3
keyword = oDatabase.SMS_Keywords.Where(x => x.ID == keywordID).First();
} catch (Exception oEx){ //write exception to console, then continue; }
oMessage.DemographicID = keyword.DemographicID;
oMessage.Keyword = keyword.Keyword;
SendNotificationMessage(oMessage);
}
}

Related

How to convert nested for loop with if condition to .Net Linq?

I am working on a function which will modify the input payload properties. The payload contains nodes and each node contains list of features. I need to remove some specific features match condition and also modify each node window property start and end time. I have written the function using traditional nested for loop, but struggling to convert it to Linq function. Anyone has idea how to convert this nested for loop function to a Linq function?
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
var nodes = payload.Nodes;
for (var i = 0; i < nodes.Count(); ++i)
{
var node = nodes[i];
var features = node.Features;
for (var j = 0; j < features.Count(); ++j)
{
var feature = features[j];
if (feature.NodeFeatureTypeID
== FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
{
features.RemoveAt(j);
}
}
var windows = node.Windows;
for (var k = 0; k < windows.Count(); ++k)
{
var window = windows[k];
if (window.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS) continue;
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
}
Let's do it in parts. This first code of yours removes features that are in a list,
for (var j = 0; j < features.Count(); ++j)
{
var feature = features[j];
if (feature.NodeFeatureTypeID
== FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
{
features.RemoveAt(j);
}
}
We need to convert that to "keep features not in a list"
var discard = new [] {FeatureTypeEnum.FEATURE_A, FeatureTypeEnum.FEATURE_B, FeatureTypeEnum.FEATURE_C, FeatureTypeEnum.FEATURE_D };
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
This part of your code skips if function type is a certain kind, and zeroes timespans if the function type is not a certain kind:
if (window.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS) continue;
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
Which might be better as a loop that operates on just those things we are interested in (we want to modify only those Windows where the nodefuctionTypeId is not Hours):
foreach(var window in node.Windows.Where(w => w.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS){
window.StartHour = new TimeSpan.FromHous(startTime);
window.EndHour = new TimeSpan.FromHours(endTime);
}
Meaning the whole is:
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
var discard = new [] {FeatureTypeEnum.FEATURE_A, FeatureTypeEnum.FEATURE_B, FeatureTypeEnum.FEATURE_C, FeatureTypeEnum.FEATURE_D };
foreach (var node in payload.Nodes)
{
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
foreach(var window in node.Windows.Where(w => w.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS){
window.StartHour = TimeSpan.FromHous(startTime);
window.EndHour = TimeSpan.FromHours(endTime);
}
}
}
I don't think I'd convert it all to a Linq form, as it would make a mess; linq queries should not have side effects (modify the objects the query iterates over) so particularly the second part where the time spans are being zeroed would have to become an operation where everything about the window was being copied over to a new Window if you wanted to LINQ it.
If a window is literally just a time span pair then it's not so bad, or if you want to provide a constructor that takes an existing Window and a startTime and endTime:
public Window(Window copyFrom, int startTime, int endTime){
this.X = copyFrom.X;
this.Y = copyFrom.Y;
...
this.StartHour = TimeSpan.FromHours(strtTime);
this.EndHour = TimeSpan.FromHours(endTime);
}
Then maybe your method could become some linq:
foreach (var node in payload.Nodes)
{
node.Features = node.Features.Where(f => !discard.Contains(f)).ToArray();
node.Windows = node.Windows.Select(w => w.NodeFunctionTypeID == FeatureTypeEnum.MM_HOURS ? w : new Window(w, startTime, endTime).ToArray();
}
..but I don't know if I would try for a wholesale replacement of the entire nodes list, for reasons even based on the name of the method: ApplyTransformations sounds like it means "take this list of nodes and change bits of them" not "take this list of nodes and give me a new list with some or all of the nodes replaced or modified" - that sort of behavior in code could wreck something else, if the calling code is expecting a tweak and the object it sends (or objects within it) are swapped out for new ones
Using a linq query for the second part would make things messier, better to just have a for loop
Something like this should work:
var nodes = payload.Nodes;
nodes.Features = nodes.Features.Where(f => !(
f.NodeFeatureTypeID == FeatureTypeEnum.FEATURE_A
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C
|| feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D
)
);
foreach(var window in nodes.Windows)
{
if (window.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS)
{
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
Pls have look at below code snippet to avoid nested loop using linq.
public class Employee
{
public string Name { get; set; }
public List<EmployeeProject> EmployeeProject { get; set; }
}
public class EmployeeProject
{
public string ProjectName { get; set; }
public string ClientName { get; set; }
}
/// <summary>
/// Object mapping
/// </summary>
/// <returns></returns>
public static List<Employee> CreateObject()
{
var employeeList = new List<Employee>();
var employee = new Employee();
var employeeProjectList = new List<EmployeeProject>();
employee.Name = "John";
var employeeProject = new EmployeeProject();
employeeProject.ProjectName = "Chrome";
employeeProject.ClientName = "Google";
employeeProjectList.Add(employeeProject);
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "WhatsApp";
employeeProject.ClientName = "Meta";
employeeProjectList.Add(employeeProject);
employee.EmployeeProject = employeeProjectList;
employeeList.Add(employee);
employee.Name = "Alex";
employeeProjectList = new List<EmployeeProject>();
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "Chrome2";
employeeProject.ClientName = "Google2";
employeeProjectList.Add(employeeProject);
employeeProject = new EmployeeProject();
employeeProject.ProjectName = "WhatsApp2";
employeeProject.ClientName = "Meta2";
employeeProjectList.Add(employeeProject);
employee.EmployeeProject = employeeProjectList;
employeeList.Add(employee);
return employeeList;
}
/// <summary>
/// Linq function
/// </summary>
public static void LinqFunctionForNestedQuery()
{
var employeeObject = CreateObject();
var result1 = employeeObject.Select(x =>
{
x.EmployeeProject = x.EmployeeProject.Select(y =>
{
y.ProjectName.Contains("Chrome");
return y;
}).ToList();
return x;
});
}
To maintain readability of the code and to make it simpler , I have modified the code.
Step1 : Replace for with foreach if you can
Step2 : Replace foreach with linq
Important tip. Resharper helps you with code suggestions.
Solution
private void ApplyTransformation(InputPayload payload, int startTime = 8, int endTime = 15)
{
foreach (var node in payload.Nodes)
{
var features = node.Features;
foreach (var feature in features.Where(feature => feature.NodeFeatureTypeID == FeatureTypeEnum.FEATURE_A ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_B ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_C ||
feature.FeatureTypeID == FeatureTypeEnum.FEATURE_D))
{
features.Remove(feature);
}
var windows = node.Windows;
foreach (var window in windows
.Where(window => window.NodeFunctionTypeID != FeatureTypeEnum.MM_HOURS))
{
window.StartHour = new TimeSpan(startTime, 0, 0);
window.EndHour = new TimeSpan(endTime, 0, 0);
}
}
}

Entity Framework: Load data from different tables in one query

In my C# project with EF Core 5.0, I have several independents tables: Clothes, Hairs, Makeup. Some of their columns are similar, but some are not..`
I need to write a method that will load the rows from these tables. What I have now is:
public async Task<(ClothesDbModel[] clothes, MakeupDbModel[] makeups, HairDbModel[] hairs)> GetDressup(int[] clothesIds, int[] makeupIds, int[] hairIds)
{
ClothesDbModel[] clothes = new ClothesDbModel[0];
if (clothesIds.Length > 0)
{
clothes = await _dbContext.Clothes.Where(c => clothesIds.Contains(c.Id)).ToArrayAsync();
}
MakeupDbModel[] makeups = new MakeupDbModel[0];
if (makeupIds.Length > 0)
{
makeups = await _dbContext.Makeups.Where(c => makeupIds.Contains(c.Id)).ToArrayAsync();
}
HairDbModel[] hairs = new HairDbModel[0];
if (hairIds.Length > 0)
{
hairs = await _dbContext.Hairs.Where(c => hairIds.Contains(c.Id)).ToArrayAsync();
}
return (clothes, makeups, hairs);
}
However, in this case, I have 3 separate queries to the database (3 awaits). I believe that is not the best way to load the data from a performance point of view. Maybe I can load the same data using DbContext only once&
I hope it will work with EF. But I know there are problems with Concat, so it may fail.
class CombinedResult
{
public ClothesDbModel Clothes;
public MakeupDbModel Makeup;
public HairDbModel Hair;
}
...
public async Task<(ClothesDbModel[] clothes, MakeupDbModel[] makeups, HairDbModel[] hairs)> GetDressup(int[] clothesIds, int[] makeupIds, int[] hairIds)
{
var queries = new List<IQueryable<CombinedResult>>();
if (clothesIds.Length > 0)
{
queries.Add(_dbContext.Clothes.Where(c => clothesIds.Contains(c.Id)).Select(c => new CombinedResult { Clothes = c }));
}
if (makeupIds.Length > 0)
{
queries.Add(_dbContext.Makeups.Where(c => makeupIds.Contains(c.Id)).Select(c => new CombinedResult { Makeup = c }));
}
if (hairIds.Length > 0)
{
queries.Add(_dbContext.Hairs.Where(c => hairIds.Contains(c.Id)).Select(c => new CombinedResult { Hair = c }));
}
var clothes = new List<ClothesDbModel>();
var makeups = new List<MakeupDbModel>();
var hairs = new List<HairDbModel>();
if (queries.Count > 0)
{
var query = queries[0];
for (var i = 1; i < queries.Count; i++)
{
query = query.Concat(queries[i]);
}
var items = await query.ToListAsync();
foreach (var item in items)
{
if (item.Clothes != null)
clothes.Add(item.Clothes);
else if (item.Makeup != null)
makeups.Add(item.Makeup);
else if (item.Hair != null)
hairs.Add(item.Hair);
}
}
return (clothes.ToArray(), makeups.ToArray(), hairs.ToArray());
}

How to fix : Cannot convert lambda expression to type int because is not delegate?

i am trying to get user decision from the list but i get that Cannot convert lambda expression to type int because is not delegate. How can i solve this? I have had a look on the internet but since i am quite new i am not sure what is the right solution
public static List<int> UserDecisionResult { get; set; }
public void GetUserDecision()
{
List<int> userDecision = new List<int>();
if (FilterAllItems) userDecision.Add(_parentCategoryId = -1);
if (FilterBeginnerItems) userDecision.Add(_parentCategoryId = 1);
if (FilterIntermediateItems) userDecision.Add(_parentCategoryId = 2);
if (FilterUpperIntermediateItems) userDecision.Add(_parentCategoryId = 3);
if (FilterAdvancedItems) userDecision.Add(_parentCategoryId = 4);
UserDecisionResult = userDecision;
}
private static List<Article> FindAllArticlesForPurchase(List<Article> allArticles)
{
var result = UserDecisionResult;
if (_parentCategoryId != -1)
{
foreach (var categoryGroup in _allUserCategoryGroups)
{
var allGroupCategories = _allCategories.Where(m => m.CategoryGroupId == categoryGroup.Id).ToList();
if (_parentCategoryId != -1)
{
foreach (var category in allGroupCategories)
{
if (category.ParentId == _parentCategoryId && _parentCategoryId != -1)
{
var categoryArticles = _allArticlesForPurchase.Where(result.Contains(m => m.CategoryId == category.Id).ToList();
//var categoryArticles = _allArticlesForPurchase.Where(result.Contains(m => m.CategoryId == category.Id).ToList());
allArticles.AddRange(categoryArticles);
}
}
}
}
}
else
{
allArticles = _allArticlesForPurchase;
}
return allArticles;
}
Your lambda expression is wrong here due to syntax. You can try:
var categoryArticles = _allArticlesForPurchase
.Where(m => m.CategoryId == category.Id
&& result.Contains(m.CategoryId))
.ToList();
or if Contains here is some custom implementation then try :
var categoryArticles = _allArticlesForPurchase
.Where(_ => result.Contains(m => _.CategoryId == category.Id))
.ToList();

WaitForDrawerClose blocks printing

I'm writing an application that uses Pos for .Net, and I'm noticing that if you call WaitForDrawerClose, then you won't be able to print receipts until it has returned.
This is not desirable behavior. Is there another way to wait for the cash drawer to close without blocking the printer?
I've looked into the OnDrawerStateChanged Event, but that is a protected member of CashDrawerBase, and I'm not entirely sure how to access it.
Here is my SSCCE:
static void Main(string[] args)
{
var posExplorer = new PosExplorer();
var waitTask = WaitForCloseAsync(posExplorer);
System.Threading.Thread.Sleep(500);
PrintText(posExplorer);
waitTask.Wait();
}
public static Task WaitForCloseAsync(PosExplorer posExplorer)
{
var result = Task.Factory.StartNew(() =>
{
Console.WriteLine("waiting");
var cashDrawer = GetCashDrawer(posExplorer);
cashDrawer.Open();
cashDrawer.Claim(1000);
cashDrawer.DeviceEnabled = true;
cashDrawer.WaitForDrawerClose(10000, 4000, 500, 5000);
cashDrawer.Release();
cashDrawer.Close();
Console.WriteLine("waited");
});
return result;
}
public static void PrintText(PosExplorer posExplorer)
{
Console.WriteLine("printing");
var printer = GetPosPrinter(posExplorer);
printer.Open();
printer.Claim(1000);
printer.DeviceEnabled = true;
var text = "abc\x1B|1lF";
printer.PrintNormal(PrinterStation.Receipt, text);
printer.Release();
printer.Close();
Console.WriteLine("printed");
}
public static CashDrawer GetCashDrawer(PosExplorer posExplorer)
{
var deviceInfo = posExplorer.GetDevices(DeviceCompatibilities.Opos)
.Cast<DeviceInfo>()
.Where(d => d.Type == "CashDrawer")
.ToList();
var device = deviceInfo.FirstOrDefault(d => d.Compatibility == DeviceCompatibilities.Opos);
if (device == null)
{
return null;
}
else
return (CashDrawer)posExplorer.CreateInstance(device);
}
private static PosPrinter GetPosPrinter(PosExplorer posExplorer)
{
var deviceInfo = posExplorer.GetDevices(DeviceCompatibilities.Opos)
.Cast<DeviceInfo>()
.Where(d => d.Type == "PosPrinter")
.ToList();
var device = deviceInfo.FirstOrDefault(d => d.Compatibility == DeviceCompatibilities.Opos);
if (device == null)
{
return null;
}
else
{
return (PosPrinter)posExplorer.CreateInstance(device);
}
}
so, what I did was essentially this: Instead of using (WaitForClose), I just poll it like this:
for (var i = 0; i < 15; i++)
{
cashDrawer = GetCashDrawer(posExplorer);
cashDrawer.Open();
cashDrawer.Claim(1000);
cashDrawer.DeviceEnabled = true;
if (!cashDrawer.DrawerOpened)
{
Console.WriteLine("waited");
return;
}
cashDrawer.Release();
cashDrawer.Close();
System.Threading.Thread.Sleep(1500);
}
Console.WriteLine("timed out");
It's not ideal, but it doesn't lock the printer up either, so It'll have to to for now.

c# linq to sql two threads visit the same db, but one is very slow

I've created two threads in my application and the first one is the main UI thread, which will do the normal job like quering below
var order = db.Orders.Where(x=>x.systemId==id).ToList();
the other thread is checking if there is any warning situation to handle, the code is as below:
TransactionOptions transOptions = new TransactionOptions()
{ IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted };
using (new TransactionScope(TransactionScopeOption.Required, transOptions))
{
BathDBDataContext dc = new BathDBDataContext(connectionString);
var all_menus = dc.Menu.Where(x => x.addAutomatic);
var menus = all_menus.Select(x => x.name).ToList();
var orders = dc.Orders.Where(x => menus.Contains(x.menu) && !x.paid && x.deleteEmployee == null);
var add_menus = orders.Select(x => x.menu).Distinct();
var ids = orders.Select(x => x.systemId).Distinct();
foreach (var systemId in ids)
{
var seat_orders = orders.Where(x => x.systemId == systemId);
foreach (var add_menu in add_menus)
{
var add_orders = seat_orders.Where(x => x.menu == add_menu && (x.priceType == null || x.priceType != "stop counting"));
if (add_orders.Count() == 0)
continue;
var max_time = add_orders.Max(x => x.inputTime);
var max_order = add_orders.OrderByDescending(x => x.inputTime).FirstOrDefault();
//var max_order = add_orders.FirstOrDefault(x => x.inputTime == max_time);
if (max_order == null || max_order.priceType == "per hour")
continue;
var the_menu = all_menus.FirstOrDefault(x => x.name == add_menu);
string menu_time = the_menu.timeLimitHour.ToString() +
":" + the_menu.timeLimitMiniute.ToString() +
":" + the_menu.timeLimitSecond.ToString();
TimeSpan tsm = TimeSpan.Parse(menu_time);
if (DateTime.Now - max_order.inputTime < tsm)
continue;
if (the_menu.addType == "by unit")
{
Orders new_order = new Orders();
new_order.menu = max_order.menu;
new_order.text = max_order.text;
new_order.systemId = systemId;
new_order.number = 1;
new_order.money = the_menu.price;
new_order.technician = max_order.technician;
new_order.techType = max_order.techType;
new_order.inputTime = DateTime.Now;
new_order.inputEmployee = "computer";
new_order.paid = false;
dc.Orders.InsertOnSubmit(new_order);
}
else if (the_menu.addType == "by time")
{
Orders new_order = new Orders();
new_order.menu = max_order.menu;
new_order.text = max_order.text;
new_order.systemId = systemId;
new_order.number = 1;
new_order.priceType = "per hour";
new_order.money = Convert.ToDouble(the_menu.addMoney);
new_order.technician = max_order.technician;
new_order.techType = max_order.techType;
new_order.inputTime = DateTime.Now;
new_order.inputEmployee = "computer";
new_order.paid = false;
dc.Orders.InsertOnSubmit(new_order);
}
}
dc.SubmitChanges();
}
in this situation, the query in the main UI thread will be very slow or sometimes doesn't even work.
can anyone help me with that?
Correct me if I am wrong but you seem to insert values to Orders in the loop and also trying to use an expression in your main UI.
Besides 5 seconds interval might be short since your LINQ expressions get more complex so that time needed for the calculations increases.
Loading that "BathDBDataContext" might take more time as your database gets new records.
I'd suggest using Cache and increase 5 seconds refresh timer.

Categories