Improving method performance - linq-to-sql

I wrote the following method that receives a list and updates the database based on certain criteria:
public void UpdateInventoryGoods(List<InventoryGoods> list, int id)
{
int index = 0;
var query = from inventoryGoods in context.InventoryGoods
where inventoryGoods.ParentId == id
select inventoryGoods;
List<InventoryGoods> goodsList = query.ToList();
using (var scope = new TransactionScope())
{
foreach (InventoryGoods i in list)
{
foreach (InventoryGoods e in goodsList)
{
if (index == 30)
{
index = 0;
context.SubmitChanges();
}
if (e.Gid == i.Gid && !getEventId(e.Id).HasValue && !e.ActionOn.HasValue)
{
e.Action = i.Action;
}
else if ((e.Gid == i.Gid && getEventId(e.Id).HasValue) && (e.Action != i.Action || i.ActionOn == DateTime.MinValue))
{
e.Action = i.Action;
e.ActionOn = null;
var allEvents = from invent in context.InventoryGoodsEvents
where invent.InventoryGood == e.Id
select invent;
List<InventoryGoodsEvents> inventoryGoodsEventsList = allEvents.ToList();
var events = from g in context.GoodsEvent
select g;
List<GoodsEvent> goodsEventList = events.ToList();
foreach (InventoryGoodsEvents goodsEvent in inventoryGoodsEventsList)
{
context.InventoryGoodsEvents.DeleteOnSubmit(goodsEvent);
foreach (GoodsEvent ge in goodsEventList)
{
if (ge.Id == goodsEvent.EventId)
{
ge.IsDeleted = true;
ge.DeletedOn = DateTime.Now;
ge.DeletedBy = System.Web.HttpContext.Current.User.Identity.Name;
}
}
}
}
++index;
}
}
context.SubmitChanges();
scope.Complete();
}
}
public int? getEventId(int InventoryGood)
{
var InventoryGoodsEvents = from i in context.InventoryGoodsEvents
where i.InventoryGood == InventoryGood
select i;
List<InventoryGoodsEvents> lst = InventoryGoodsEvents.ToList();
if (lst.Count() > 0)
{
return lst[0].EventId;
}
else
{
return null;
}
}
Though this method works well for about 500 or 1000 objects, it gets too slow or eventually times out when I feed it over 8000 objects or more.
So, where could I improve its performance a little?

Don't call the database in a loop.
Try moving the queries outside the loops like this:
public void UpdateInventoryGoods(List<InventoryGoods> list, int id)
{
int index = 0;
var query = from inventoryGoods in context.InventoryGoods
where inventoryGoods.ParentId == id
select inventoryGoods;
List<InventoryGoods> goodsList = query.ToList();
using (var scope = new TransactionScope())
{
var allEvents = from invent in context.InventoryGoodsEvents
where goodsList.Contains(invent.InventoryGood)
select invent;
List<InventoryGoodsEvents> inventoryGoodsEventsList = allEvents.ToList();
var events = from g in context.GoodsEvent
select g;
List<GoodsEvent> goodsEventList = events.ToList();
foreach (InventoryGoods i in list)
{
foreach (InventoryGoods e in goodsList)
{
if (index == 30)
{
index = 0;
context.SubmitChanges();
}
var eventId = getEventId(e.Id);
if (e.Gid == i.Gid && !eventId.HasValue && !e.ActionOn.HasValue)
{
e.Action = i.Action;
}
else if ((e.Gid == i.Gid && eventId.HasValue) && (e.Action != i.Action || i.ActionOn == DateTime.MinValue))
{
e.Action = i.Action;
e.ActionOn = null;
foreach (InventoryGoodsEvents goodsEvent in inventoryGoodsEventsList)
{
context.InventoryGoodsEvents.DeleteOnSubmit(goodsEvent);
foreach (GoodsEvent ge in goodsEventList)
{
if (ge.Id == goodsEvent.EventId)
{
ge.IsDeleted = true;
ge.DeletedOn = DateTime.Now;
ge.DeletedBy = System.Web.HttpContext.Current.User.Identity.Name;
}
}
}
}
++index;
}
}
context.SubmitChanges();
scope.Complete();
}
}

I'm no Linq expert, but I think you can probably improve getEventId (should be capital first letter btw) with something like
public int? GetEventId(int inventoryGood)
{
var firstInventoryGoodsEvent = context.InventoryGoodsEvents
.Where(i => i.InventoryGood == inventoryGood)
.FirstOrDefault();
// ...etc
}
The use of FirstOrDefault() means you don't process the whole list if you find a matching element.
There are probably other optimisations but it's quite difficult to follow what you're doing. As an example:
foreach (InventoryGoods i in list)
{
foreach (InventoryGoods e in goodsList)
{
}
}
i and e don't confer much meaning here. It might be obvious to you what they mean but they aren't very descriptive to someone who has never seen your code before. Similarly, list is not the best name for a List. List of what? Your variable name should describe it's purpose.
Edit:
I'm not sure about anything else. You seem to be using ToList() in a few places where as far as I can see it's not necessary. I don't know what effect that would have on performance, but someone cleverer than me could probably tell you.
You could also try hoisting a few of your values outside of loops, eg:
foreach (foo)
{
foreach (bar)
{
DeletedOn = DateTime.Now;
DeletedBy = System.Web.HttpContext.Current.User.Identity.Name;
}
}
can be re-written as
var deletedOn = DateTime.Now;
var deletedBy = System.Web.HttpContext.Current.User.Identity.Name;
foreach (foo)
{
foreach (bar)
{
DeletedOn = deletedOn;
DeletedBy = deletedBy;
}
}
Again, I'm not sure how much difference if any that would make, you'll need to test it and see.

It's not going in batches of 30, it's going in batches of 1.
There's a query with no criteria, so it loads the whole table. Is that your intention?
getEventId(e.Id) returns a consistent value. Don't call it twice (per loop).

Related

Angular: 2 bugs in saving to local storage

I have 2 bugs in methods that save products to local storage (when a user adds them to 'favorites').
The code is part of Angular service, but it can be read without this reference also.
First bug: Sometimes, when a user saves a product to favorites, not one, but multiple products get stored.
Second bug: When I store too many products, I get an error saying that the storage is full (it's full because I parse JSON wrongly, so unwanted additional backslashes are added).
This is the codepan (you can see the result in the console): https://codepen.io/mandy555/pen/dyPVGxj
storage;
storageKeys;
x;
i = 0;
saveId;
arr;
normalArr;
productInStore;
productsInStore;
idsInStore;
newIds;
newProducts;
favorites;
storageAvailable(type) {
try {
this.storage = window[type];
this.x = '__storage_test__';
this.storage.setItem('x', this.x);
this.storage.removeItem('x');
return true;
}
catch (e) {
return e instanceof DOMException && (
// everything except Firefox
e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
(this.storage && this.storage.length !== 0);
}
}
storeFavorite(product = '', productId = '') {
productId = String(productId);
this.normalArr = [];
if (this.storageAvailable('localStorage')) {
this.storage = window.localStorage;
if (!this.storage.getItem('favorites_id')) {
this.saveId = productId;
this.normalArr.push(product);
} else {
this.productsInStore = JSON.parse(this.storage.getItem('favorites_product'));
if (this.storage.getItem('favorites_id').indexOf(',') === -1) {
this.arr = [this.storage.getItem('favorites_id')];
this.normalArr = this.productsInStore;
} else {
this.arr = this.storage.getItem('favorites_id').split(',');
this.normalArr = this.productsInStore;
}
if (!this.arr.includes(productId) && productId !== '') {
this.saveId = this.storage.getItem('favorites_id') + ',' + productId;
this.normalArr.push(product);
} else {
this.saveId = this.storage.getItem('favorites_id');
}
}
this.storage.setItem('favorites_id', this.saveId);
this.storage.setItem('favorites_product', JSON.stringify(this.normalArr));
} else {
this.storage = null;
}
}
deleteFavorite(productId = '') {
productId = String(productId);
this.normalArr = [];
if (this.storageAvailable('localStorage')) {
this.storage = window.localStorage;
if (this.storage.getItem('favorites_id') && this.storage.getItem('favorites_product')) {
this.productsInStore = JSON.parse(this.storage.getItem('favorites_product'));
if (this.storage.getItem('favorites_id').indexOf(',') === -1) {
this.storage.clear();
} else {
this.idsInStore = this.storage.getItem('favorites_id').split(',');
this.newIds = this.idsInStore.filter(id => {
return id !== productId;
});
this.newproducts = this.productsInStore.filter(product => {
return String(product.id) !== productId;
});
this.newIds = this.newIds.join();
this.storage.setItem('favorites_id', JSON.stringify(this.newIds));
this.storage.setItem('favorites_product', JSON.stringify(this.newproducts));
}
}
} else {
this.storage = null;
}
}
The first problem was that I forgot to assign storage to local storage. The second solution was to save x and not JSON.stringify(x) in the local storage, since x was already a string. The corrected version is here:
https://codepen.io/mandy555/pen/dyPVGxj

AS3 many buttons with boolean function - less verbose?

I have twenty eight instances of a two-frame MovieClip (frame1 = off - frame 2 = on) to select PDFs to send. The following code works fine, but I am looking to tighten it up and make it less verbose and easier to read. I include only one reference to an instance for space and sanity sake.
function PDFClick(e:MouseEvent):void {
targetPDF = e.target.ID;
trace("targetPDF " +targetPDF);
if (targetPDF == "PDF1")
if (pdf.pcconnectionPDF1.currentFrame == 1)
{
pdf.pcconnectionPDF1.gotoAndPlay(2);
PDF1 = 1;
trace("PDF1 is "+PDF1);
}else{
pdf.pcconnectionPDF1.gotoAndPlay(1);
PDF1 = 0;
trace("PDF1 is "+PDF1);
}
Thanks! trying to learn
You'll want to generalize your calls to your ID, that way you don't need special code for each condition.
function PDFClick(e:MouseEvent):void {
var ID:String = e.target.ID;
var mc = pdf["pcconnection" + ID];
if (mc.currentframe == 1) {
mc.gotoAndPlay(2);
this[ID] = 1;
} else {
mc.gotoAndPlay(1);
this[ID] = 0;
}
}
How about this:
function PDFClick(e:MouseEvent):void {
targetPDF = e.target.ID;
trace("targetPDF " +targetPDF);
if (targetPDF == "PDF1") {
var frame:int = pdf.pconnectionPDF1.currentFrame;
pdf.pconnectionPDF1.gotoAndPlay( frame == 1 ? (PDF1 = 1)+1 : (PDF1 = 0)+1 );
}
}
I think that's about what you are looking for.

Auto manage and protect Created\Updated fields with Entity Framework 5

I want so every added\changed record will have a time stamp of creation\change.
But - so it will be easy to embed and easy to manage - automatically.
Overwrite the 'DbContext' class or embed this in the '.tt' file (Codefirst \ DBFirst)
The code assume so you have the fields 'CreatedOn'\'ModifiedOn' inside the POCO.
If you don't have them, or you have only one - the code will work fine.
Be aware! If you use a extension (as this one) so allow you to do batch updates or changes from a stored procedure - this will not work
EDIT:
I found the source of my inspiration - thanks 'Nick' here
public override int SaveChanges()
{
var context = ((IObjectContextAdapter)this).ObjectContext;
var currentTime = DateTime.Now;
var objectStateEntries = from v in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
where v.IsRelationship == false && v.Entity != null
select v;
foreach (var entry in objectStateEntries)
{
var createdOnProp = entry.Entity.GetType().GetProperty("CreatedOn");
if (createdOnProp != null)
{
if (entry.State == EntityState.Added)
{
if (createdOnProp != null)
{
createdOnProp.SetValue(entry.Entity, currentTime);
}
}
else
{
Entry(entry.Entity).Property("CreatedOn").IsModified = false;
}
}
var modifiedOnProp = entry.Entity.GetType().GetProperty("ModifiedOn");
if (modifiedOnProp != null)
{
modifiedOnProp.SetValue(entry.Entity, currentTime);
}
}
return base.SaveChanges();
}

Can't find a match

I have a nested arrays with pairs of numbers:
_open = [[8,15], [9,16]];
from which i want to find a match using ArrayUtilities.findMatchIndex but it always returns -1 when looking for an element array. For example:
ArrayUtilities.findMatchIndex(_open, [8, 15])
I'm wondering if it is possible for AS3 to compare arrays, because comparing other types (strings, numbers, etc) just work fine
Here's findMatchIndex():
public static function findMatchIndex(aArray:Array, oElement:Object, ...rest):Number {
var nStartingIndex:Number = 0;
var bPartialMatch:Boolean = false;
if(typeof rest[0] == "number") {
nStartingIndex = rest[0];
}
else if(typeof rest[1] == "number") {
nStartingIndex = rest[1];
}
if(typeof rest[0] == "boolean") {
bPartialMatch = rest[0];
}
var bMatch:Boolean = false;
for(var i:Number = nStartingIndex; i < aArray.length; i++) {
if(bPartialMatch) {
bMatch = (aArray[i].indexOf(oElement) != -1);
}
else {
bMatch = (aArray[i] == oElement);
}
if(bMatch) {
return i;
}
}
return -1;
}
Comparing other types (strings, numbers, etc) works fine, because they are so-called primitives, and are compared by values. Arrays, though, are objects, therefore they are compared by reference. Basically it means that [8,15] != [8,15].
One way around it is replacing this line...
else {
bMatch = (aArray[i] == oElement);
}
... with something like this ...
else {
bMatch = compareElements(aArray[i], oElement);
}
... where compareElements will try to check its arguments' types first, and if they're objects, will compare their values.

How to merge two arraycollection and without duplicates?

I want merge 2 array collection, where no duplicates are allowed,
var ac1:ArrayCollection = new ArrayCollection([ {s:"4",e:"8"}, {s:"9",e:"10"}, ]);
var ac2:ArrayCollection = new ArrayCollection([ {s:"2",e:"3"}, {s:"4",e:"8"}, {s:"9",e:"10"}, {s:"11",e:"12"}, ]);
how can I do it in efficient way
Thanks,
Errr, I'm not an actionscript guy but it's a classic problem - sort the two arrays in ascending order, then feed them into your third array, always selecting the lowest value from the two source arrays and escaping the write to the destination if what you would write is already at the end of the list - you'll end up with a sorted list of the unique values in the n input arrays
For more properties, use something like:
function equals(o1:Object, o2:Object):Boolean
{
if (o1 == o2) return true;
if (!o1 || !o2) return false;
for (var key:String in o1)
{
if (!(key in o2)) return false;
if (o1[key] != o2[key]) return false;
}
return true;
}
Perhaps this is a good starter.
function equals(o1:Object, o2:Object)
{
return (o1 && o2) && (o1 != o2) && (o1.s != o2.s) && (o1.e != o2.e);
}
function merge(a:Array, b:Array):Array
{
const source = [];
var isContained:Boolean = false;
for (var i:int; i< a.length;i++) {
isContained = false
for (var j:int; j< b.length;j++) {
if (equals(a[i], b[j])) {
isContained = true;
break;
}
}
if (!isContained) {
source.push(a[1]);
}
}
return source.concat(b);
}