Deleting large volumes of data from database using .NetCore - c#

I am trying to delete over 5000 records from the database using .netCore 2.1. I have the following method which works fine however it takes way too long.
public async Task<bool> deleteAdhocDetails(int[] id)
{
var status = false;
for (var x = 0; x < id.Length; x++)
{
var existingReward = await _context.AdhocRewardInfo
.Where(d => d.RowID == id[x])
.FirstOrDefaultAsync<AdhocRewardInfo>();
if ((existingReward != null) && (existingReward.HaloRewardCode != null))
{
try
{
//removing existingReward
_context.AdhocRewardInfo.Remove(existingReward);
await _context.SaveChangesAsync();
status = true;
}
catch (Exception e)
{
throw e;
}
}
}
return status;
}
I am currently using EFCore.BulkExtensions for inserting records and it works nicely. I tried using BulkDelete but it didn't seem to make a difference. I also tried to use Z.EntityFramework.Extensions.EFCore but couldn't get that to work too. I should also mention that I am kinda new to this. If someone could please point me in the right direction I would appreciate it. Thanks
In the adhoc.service.ts:
deleteAdhocRecipients(id: number[]): Promise<boolean> {
return this.http.put<boolean>(this.baseUrl + 'deleteAdhocDetails', id)
.toPromise();
}
and in the .ts file:
this.general
.load(this.aService.deleteAdhocCampaign(this.adhocForm.get('create.id').value))
.then(
y => {
if (deleteArr.length > 0) {
this.general.load(this.aService.deleteAdhocRecipients(deleteArr))
.then(
f => this.router.navigate(['/adhoc-campaign/lookup'])
);
} else {
this.router.navigate(['/adhoc-campaign/lookup']);
}
}
);

I optimized your c# code
public async Task<bool> deleteAdhocDetails(int[] id){
try{
var deletedRecords= await _context.AdhocRewardInfo.Where(d =>
id.Contains(d.RowID))
foreach (var item in deletedRecords)
{
_context.AdhocRewardInfo.Remove(item );
}
await _context.SaveChangesAsync();
return true;
}catch (Exception e){
return false;
}
}

You can remove select of object and then do only one saveChanges
public async Task<bool> deleteAdhocDetails((int id, object haloRewardCode)[] id)
{
try
{
for (var x = 0; x < id.Length; x++)
{
var existingReward = new AdhocRewardInfo() { id = id[x].id, HaloRewardCode = id[x].haloRewardCode };
if ((existingReward != null) && (existingReward.HaloRewardCode != null))
{
//removing existingReward
_context.AdhocRewardInfo.Remove(existingReward);
}
}
}
catch (Exception e)
{
throw;
}
await _context.SaveChangesAsync();
return true;
}

I would suggest to use linq2db.EntityFrameworkCore (disclaimer: I'm one of the creators).
And Delete will be fast as possible:
var deletedCount = await _context.AdhocRewardInfo
.Where(d => id.Contains(d.RowID))
.DeleteAsync();

I think the fastest way will be using sql query string
public async Task<bool> deleteAdhocDetails(int[] id)
{
var ids= string.Join(",",id);
var rows= await _context.Database.ExecuteSqlRawAsync(
"DELETE FROM AdhocRewardInfo WHERE Id IN ("+ ids + ")");
return rows>0;
}

Related

Running a thread in the background when controller return to UI with full results - async call of a function from Controller method

Hi I have Controller method as below
[HttpPost]
public JsonResult Post(string vehiclesString, string Entity, int EntityId, ApplicationUser CurrentUser)
{
//https://stackify.com/understanding-asp-net-performance-for-reading-incoming-data/
List<Vehicle> vehicles = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Vehicle>>(vehiclesString);
InputFieldController c = new InputFieldController();
var errors = new List<string>();
try
{
//let's get model for each of the input field
InputFieldController icController = new InputFieldController();
List<InputField> fields = icController.GetNamesValues("VehicleField", -1, "Vehicle", 0);
foreach (Vehicle vehicle in vehicles)
{
//convert array of strings into array of input fields
if (fields.Count != vehicle.ValueStrings.Count)
{
throw new Exception("Vehicle columns mismatch. Expected "
+ fields.Count + " fields, but received " + vehicle.ValueStrings.Count);
}
for (int i = 0; i < fields.Count; i++)
{
InputField field = fields[i];
string cell = vehicle.ValueStrings[i];
if ((cell != null || cell != String.Empty) && (field.Type == "radio" || field.Type == "dropdown"))
{
var f = field.InputDropdowns.Where(x => x.Name == cell).FirstOrDefault();
if (f != null)
{
field.InputValue.InputDropdownId = f.InputDropdownId;
}
else
field.InputValue.InputDropdownId = null;
}
else
{
field.InputValue.Value = cell;
}
vehicle.Values.Add(field);
}
vehicle.Blob = Newtonsoft.Json.JsonConvert.SerializeObject(vehicle.Values);
Vehicle v = new Vehicle();
if (vehicle.VehicleId == 0)
{
v = this.DomainLogicUnitOfWork.VehicleManager.Create(vehicle, Entity, EntityId);
}
}
JsonResult data = Json(new
{
success = true,
});
List<Vehicle> vehiclesList = this.DomainLogicUnitOfWork.VehicleManager.List(Entity, EntityId);
if (vehiclesList != null)
foreach (Vehicle v in vehiclesList)
{
if ((v != null) && (v.Blob != null))
v.Values = Newtonsoft.Json.JsonConvert.DeserializeObject<List<InputField>>(v.Blob);
}
//Task task = Task.Run(async () => await this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(Entity, EntityId));
/*
* Here I have to call the this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(string Entity, int EntityId) asynchronously
* but return the data without waiting for the CreateOrUpdate to complete
*/
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
await this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(vehiclesList, Entity, EntityId);
});
return data;
}
catch (Exception ex)
{
LogHandler.LogError(9000, "Error updating input fields", ex);
errors.Add("Error 9000:" + ex.Message);
return Json(new
{
error = ex.Message
});
}
}
And I have CreateOrUpdate method defined as below in VehicleInfoManager class
public async Task CreateOrUpdate(string Entity, int EntityId)
{
//do some stuff
var task = Task.Run(() => Test(Entity, EntityId));
//do other stuff
await task;
//some more stuff
}
And Test method is as follows
private void Test(string Entity, int EntityId)
{
List<VehicleInfo> addList; List<VehicleInfo> updateList;
try
{
this.GetAddAndUpdateList(Entity, EntityId, out addList, out updateList);
if ((addList != null) && (addList.Count > 0))
using (var cont = this.UnitOfWork.Context)
{
foreach (var a in addList)
{
cont.VehicleInfos.Add(a);
}
cont.SaveChanges();
}
if ((updateList != null) && (updateList.Count > 0))
using (var cont = this.UnitOfWork.Context)
{
foreach (var a in updateList)
{
var aa = cont.VehicleInfos?.Where(x => x.VehicleInfoId == a.VehicleInfoId)?.FirstOrDefault();
aa.Address_City = a.Address_City;
aa.Address_Country = a.Address_Country;
aa.Address_StateCode = a.Address_StateCode;
aa.Address_Street1 = a.Address_Street1;
aa.Address_Street2 = a.Address_Street2;
aa.Address_Zip = a.Address_Zip;
aa.ChassisYear = a.ChassisYear;
aa.EngineFamilyName = a.EngineFamilyName;
aa.Entity = a.Entity;
aa.EntityId = a.EntityId;
aa.InputFieldEntity = a.InputFieldEntity;
aa.InputFieldEntityId = a.InputFieldEntityId;
aa.InputFieldGroup = a.InputFieldGroup;
aa.LicensePlate = a.LicensePlate;
aa.Manufacturer = a.Manufacturer;
aa.ModelYear = a.ModelYear;
aa.PurchasedDate = a.PurchasedDate;
aa.RegHoldClearBy = a.RegHoldClearBy;
aa.RegHoldClearDate = a.RegHoldClearDate;
aa.RegHoldComment = a.RegHoldComment;
aa.RegHoldSet = a.RegHoldSet;
aa.RegHoldSetBy = a.RegHoldSetBy;
aa.RegHoldSetDate = a.RegHoldSetDate;
aa.TrailerPlate = a.TrailerPlate;
aa.UpdatedBy = a.UpdatedBy;
aa.UpdatedDate = a.UpdatedDate;
aa.VehicleId = a.VehicleId;
aa.VehicleOperator = a.VehicleOperator;
aa.VehicleOwner = a.VehicleOwner;
aa.VIN = a.VIN;
}
cont.SaveChanges();
}
}
catch (Exception ex)
{
ARB.Logging.LogHandler.LogError(9001, "CreateOrUpdate(string Entity, int EntityId) in class VehicleInfoManager", ex);
throw ex;
}
}
What I want is, I want two things here
the Post method to call or start the CreateOrUpdate method as background call but instead of waiting until the CreateOrUpdate method finishes, it should return the result data to UI and continue the big task CreateOrUpdate in the background.
Is there anyway to start the background method CreateOrUpdate after sometime like (10 mins etc) post method returns to UI, if it can't be done, its OK we don't have to worry but just asking if there is anyway to trigger this from within the same application
When I implemented it in the above way, even after using System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem I am still getting the null http context at the following location
user = System.Web.HttpContext.Current.User.Identity.Name; url = System.Web.HttpContext.Current.Request.Url.ToString();
System.Web.HttpContext.Current is coming out as null.
and the application is breaking,
I chaned my async call to the following to use HostingEnvironment.QueueBackgroundWorkItem, still the same htt current context is coming out as null, any help please
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
await this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(Entity, EntityId);
});
Thank you
System.Web.HttpContext.Current is maintained by the ASP.NET's SynchronizationContext.
When you start a new Task, the code will be executing on another thread pool thread without a SynchronizationContext and the value of System.Web.HttpContext.Current is not safe to use, whatever its value.
When the execution of the action method (Post) ends, the request will end and the HttpContext instance will be invalid, even if you mange to get a reference to it.
Also, there is no guarantee that that code you posted to the thread pool will run to complete, since it's out of ASP.NET control and ASP.NET won't be aware of it if IIS decides, for some reason, to recycle the application pool or the web application.
If you to post background work, use HostingEnvironment.QueueBackgroundWorkItem. Beware if its constraints.

I'm having an error when trying to delete an object from the MySQL database

When I give a GET I would like a certain object to be deleted from my database, but the program breaks when I do it, it follows a code sample:
public async Task<ActionResult<string>> GetProdutos([FromQuery] string city)
{
var search = from p in _context.Produtos
where p.Nome == city
select p;
if (search != null)
{
foreach (var p in search)
{
if (DateTime.Now.AddMinutes(-20) < p.Id)
{
return $"Clima em {p.Nome}:\nTemperatura atual: {p.Temp}\nTemperatura Máxima: {p.TempMax}\nTemperatura Mínima: {p.TempMin}";
}
else
{
_context.Produtos.Remove(p);
await _context.SaveChangesAsync();
}
}
}
The program breaks when I try to execute the line:
_context.Produtos.Remove(p);
This is a summary of the error:
System.InvalidOperationException: This MySqlConnection is already in use.
I would like to know how to solve this problem...
I'm studying programming so any clarification will be very welcome...
try this
if (search != null)
{
var productList= new List<Produto>();
foreach (var p in search)
{
if (DateTime.Now.AddMinutes(-20) < p.Id)
{
return $"Clima em {p.Nome}:\nTemperatura atual: {p.Temp}\nTemperatura Máxima: {p.TempMax}\nTemperatura Mínima: {p.TempMin}";
}
else productList.Add(p);
}
if(productList.Count >0)
{
_context.Produtos.RemoveRange(productList);
await _context.SaveChangesAsync();
}
}
And IMHO
if (DateTime.Now.AddMinutes(-20) < p.Id)
{
return $"Clima em {p.Nome}:\nTemperatura atual: {p.Temp}\nTemperatura Máxima: {p.TempMax}\nTemperatura Mínima: {p.TempMin}";
}
must be a joke.
in this case your code could be more simple
var dateTime= DateTime.Now.AddMinutes(-20);
var productList= await ( from p in _context.Produtos
where (p.Nome == city && dateTime >= ... )
select p).ToListAsync();
_context.Produtos.RemoveRange(productList);
await _context.SaveChangesAsync();

async call with c# only works with step by step debugging

So I'm currently building a Web Api with .NET, and using async calls with entity framework.
On my PUT endpoint for a controller, I'm trying to get whether the user already belongs to another level, or if he's in the DB at all, here's the controller code:
[HttpPut]
public async Task<IHttpActionResult> PutCommittee(CommitteeViewModel committee)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (!User.IsInRole("Dean") && !User.IsInRole("Chair"))
return Unauthorized();
var user = await db.Users.Where(u => u.Cn == User.Identity.Name).FirstOrDefaultAsync();
if (user == null) { return BadRequest("Your user does not exist"); }
if (User.IsInRole("Dean"))
{
var college = await db.Colleges.Where(c => c.Dean.Cn == user.Cn).FirstOrDefaultAsync();
if (college == null) { return BadRequest("You're not a Dean of any college"); }
committee.Name = _utils.getCollegeCommitteeName(college);
}
else
{
var department = await db.Departments.Where(d => d.Chair.Cn == user.Cn).FirstOrDefaultAsync();
if (department == null) { return BadRequest("You're not a Chair of any college"); }
committee.Name = _utils.getDepartmentCommitteeName(department);
}
var model = await db.Commitees.Where(c => c.Name == committee.Name).FirstOrDefaultAsync();
if (model == null)
return BadRequest("You have no committee");
var tuple = await getUsers(committee);
model.Users = tuple.Item1;
if (model.Users == null)
return BadRequest(tuple.Item2);
db.Entry(model).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw;
}
return StatusCode(HttpStatusCode.NoContent);
}
And Here's the function that checks for the users:
private async Task<Tuple<List<User>, string>> getUsers(CommitteeViewModel committee)
{
string error = "";
List<User> users = new List<User>();
var tuple = new Tuple<List<User>, string>(users, error);
var role = await db.Roles.Where(r => r.Name == "Committee").FirstOrDefaultAsync();
foreach (UserViewModel u in committee.Users)
{
var user = await db.Users.Where(us => us.Cn == u.Cn).FirstOrDefaultAsync();
if (user != null)
{
if (user.Role.Name == "Chair" || user.Role.Name == "Dean")
{
error = "User " + user.Name + " is a " + user.Role.Name + " and cannot be member of a review committee";
return tuple;
}
users.Add(user);
}
else
{
user = _loginProvider.generateUser(u.Cn, role);
db.Users.Add(user);
await db.SaveChangesAsync();
users.Add(user);
}
}
return tuple;
}
I'm using a tuple since async method don't support OUT parameters, in case there's an error.
So the problem is that when I delete a user in my front-end (and then send a put request with the updated array), and I debug step by step, it does delete it, but when I don't, if I put a breakpoint at the try block, the variable model.Users contains the previous array (the original from the model), and this only happens when I delete a user from an array, and the weird thing is that it also happened when I wrote the code synchronously
Thank you for your help.
Just remember... Async methods running back server, so if you have proccess that need run first you need to replace async method for a simple one. Trying it and I´m sure you can solve it.

Confused on Updating Table using DataGrid

-Image on top Codes Below....
private void Save_FGARec()
{
try
{
for(int x= 0; x < FGAdataGrid.Rows.Count; x++)
{
sysSFCDBDataContext SFC = new sysSFCDBDataContext();
Sales_FGAllocated FGA = SFC.Sales_FGAllocateds.FirstOrDefault(r => r.RowID == Convert.ToInt64(FGAdataGrid.Rows[x].Cells[0].Value));
if (FGAdataGrid.Rows[x].Cells[0].Value != null)
{
FGA.TotalLoaded = Convert.ToInt64(FGAdataGrid.Rows[x].Cells[6].Value);
SFC.SubmitChanges();
}
else
{
SFC.Connection.Close();
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
}
-- Is my Code on Update Right? I'm confuse coz my table doesn't update what i store on this column totalloaded which counts as cell[8]... did i missed something here?
try below, not sure this will solve the issue, but may be you are not dispose/ close the data context correctly with current code. you can use using block like below
using (sysSFCDBDataContext SFC = new sysSFCDBDataContext())
{
Sales_FGAllocated FGA = SFC.Sales_FGAllocateds.FirstOrDefault(r => r.RowID == Convert.ToInt64(FGAdataGrid.Rows[x].Cells[0].Value));
if (FGAdataGrid.Rows[x].Cells[0].Value != null)
{
FGA.TotalLoaded = Convert.ToInt64(FGAdataGrid.Rows[x].Cells[6].Value);
SFC.SubmitChanges();
}
}

WaitAll with Task not working

I Created several Task in the way below. But it seems WaitAll is not working. It is sending response without wait. Anything goes wrong here?
private void GetItemsPrice(IEnumerable<Item> items, int customerNumber)
{
try
{
var tasks = new List<Task>();
for (var i = 0; i < items.Count(); i += 50)
{
var newTask = DoGetItemsPrice(items.Skip(i).Take(50), customerNumber);
tasks.Add(newTask);
}
Task.WaitAll(tasks.ToArray());
}
catch (Exception ex)
{
ErrorLog.WriteLog(GetType().Name, "GetItemsPrice", string.Format("customerNumber={0}", customerNumber), ex.Message);
}
}
private static Task DoGetItemsPrice(IEnumerable<Item> items, int customerNumber)
{
return Task.Factory.StartNew(() =>
{
var sxApiObj = new SxApiService();
var request = new OEPricingMultipleRequest();
request.customerNumber = customerNumber;
request.arrayProduct =
items.Select(
itemCode =>
new OEPricingMultipleinputProduct
{
productCode = itemCode.ItmNum,
quantity = itemCode.Quantity,
warehouse = ConfigurationVariables.DefaultWareHouse
}).ToArray();
var response = sxApiObj.OEPricingMultiple(ConfigurationVariables.SfAppServer,
ConfigurationVariables.SfUserId,
ConfigurationVariables.SfPassword,
request);
if (response.arrayPrice != null)
{
foreach (var priceData in response.arrayPrice)
{
var productCode = priceData.productCode;
var item = items.FirstOrDefault(itm => itm.ItmNum == productCode);
if (item == null) continue;
item.ItmListPrice1 = priceData.price.ToString("c", ConfigurationVariables.UsCulture);
item.ItmListPrice2 = priceData.discountAmount.ToString("c", ConfigurationVariables.UsCulture);
item.ItmListPrice3 = priceData.extendedAmount.ToString("c", ConfigurationVariables.UsCulture);
item.Quantity = priceData.netAvailable;
}
}
});
}
There is nothing wrong with my question. WaitAll works fine and the code also correct.

Categories

Resources