I have implemented incremental loading like this:
public class IncrementalDataSource : ObservableCollection<Files>, ISupportIncrementalLoading
{
private int id;
private int itemsPerPage;
private int currentPage;
public IncrementalMediaEntrySource(int ID, int ipp = 4)
{
HasMoreItems = true;
id = ID;
itemsPerPage = ipp;
currentPage = 1;
}
public bool HasMoreItems
{
get;
private set;
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
var dispatcher = Window.Current.Dispatcher;
return Task.Run<LoadMoreItemsResult>(
async () =>
{
uint resultCount = 0;
var result = await GetData(id, itemsPerPage, currentPage++);
if (result == null || result.Count() == 0)
{
HasMoreItems = false;
}
else
{
resultCount = (uint)result.Count();
await dispatcher.RunAsync(
CoreDispatcherPriority.Normal,
() =>
{
foreach (var item in result)
this.Add(item);
});
}
return new LoadMoreItemsResult() { Count = resultCount };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
But it's not loading all data. It only gets around 16 items from 100+ items. If I change itemsPerPage from 4 to 10, it gets all items. But it takes a bit too long to load by 10 items, so I would like to stay with 4 items. And of course I would like to know, what is causing this, to avoid that. Where could be the problem?
UPDATE:
A friend of mine suggested to add "fake" objects, if itemsPerPage is less than 7(minimums number with which it is working correctly), so that total objects recieved would be 7. I tried that, and it actually worked, but of course added empty objects, which is totally useless. So I added check before adding objects to ObservableCollection<Files> like this:
foreach (var item in result)
if (item.Id != null) this.Add(item);
But then it is not working again. So maybe the problem is in adding? But I cannot think of possible cause for this...
Related
I have some data in a list that shows for example the Item Number, Line Number, Quantity, and Pack Size of an order. The problem is that some lines on the order will have the same item number but different quantities, pack sizes or both. For example :
Order# Line# Item# Qty Pack Size
100 1 12345 640 (10#64)
100 1 12345 128 (1#128)
100 2 23124 48 (1#48)
100 3 53425 80 (1#80)
Shown above for item 12345 they have ordered a total of 768 pieces but for 640 of those pieces they would like to receive 10 packs containing 64 pieces each and they would like 128 of the pieces in just one pack. When the ListView is created naturally it will create 4 rows, one row for each of the rows in the list. I would like it to only show 3 rows one for each Item# and if there are duplicate items it should add the quantities and combine the pack sizes into one row. I have tried doing something like this trying to force it to do what i wanted but it obviously does not work that well. I am fairly new to Android and ListViews so I am hoping that there is a better way to accomplish this. Can anyone point me in the right direction?
public class LoadInfoViewAdapter : BaseAdapter<Items>
{
private List<Items> lItems;
private Context gContext;
private string gLoadnStop;
private string gPacks;
private decimal gtotal;
int total;
public LoadInfoViewAdapter(Context context, List<Items> loadinfo, string LoadnStop)
{
lItems = loadinfo;
gContext = context;
gLoadnStop = LoadnStop;
}
public override int Count
{
get { return lItems.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override Items this[int position]
{
get { return lItems[position]; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
if (row == null)
{
row = LayoutInflater.From(gContext).Inflate(Resource.Layout.LoadInfoItems_Row, null);
}
//If the item at the current position is the same as the item in the next position add the qty and combine the packs
if (lItems[position].ItemNo == lItems[position + 1].ItemNo)
{
string itemno = lItems[position].ItemNo;
int count = 0;
row.FindViewById<TextView>(Resource.Id.txtItemNo).Text = lItems[position].ItemNo.Trim();
row.FindViewById<TextView>(Resource.Id.txtLineNo).Text = lItems[position].LineNum.Trim();
foreach (var item in lItems.Where(r => r.ItemNo == itemno))
{
if (count == 0)
{
gPacks = lItems[position].Pack.ToString().Trim();
gtotal = lItems[position].Qty;
}
else
{
gPacks = lItems[position + 1].Pack.ToString().Trim() + ", " + gPacks;
gtotal = lItems[position + 1].Qty + gtotal;
}
count = +1;
}
row.FindViewById<TextView>(Resource.Id.txtQuantity).Text = gtotal + " (" + gPacks + ") ";
gtotal = 0;
}
//check to see if the current item is was the same as the previous item added. If so I dont want to create a row for that item.
else if (lItems[position].ItemNo == lItems[position - 1].ItemNo)
{
//Not too sure what would be the best code to go here
}
//if it has no duplicate create the row like normal
else
{
row.FindViewById<TextView>(Resource.Id.txtItemNo).Text = lItems[position].ItemNo.Trim();
row.FindViewById<TextView>(Resource.Id.txtLineNo).Text = lItems[position].LineNum.Trim();
row.FindViewById<TextView>(Resource.Id.txtQuantity).Text = lItems[position].Qty.ToString().Trim() + lItems[position].Pack.ToString().Trim();
}
return row;
}
I would like it to return something like this :
12345 1 768((10#64),(1#128))
23124 2 48(1#48)
53425 3 80(1#80)
For whatever reason it will do what i was hoping for the duplicate item numbers but it sometimes duplicates a different item in the list like:
12345 1 768((10#64),(1#128))
23124 2 48(1#48)
53425 3 80(1#80)
23124 2 48(1#48)
Anyone got any suggestions?
Your fundamental issue is that the Items indexer is stating that you have 4 items available, but your desired result is 3 items.
This isn't really a task that you should be solving in your BaseAdapter. You should be processing the list in advance and reducing to your target 3 items BEFORE passing it into your base adapter.
Also, you should try and avoid using FindViewFromId in GetView, unless the convertView is null. In order to do this, you can pack the previously identified Views into the Tag property: that's what it's for, see https://blog.xamarin.com/creating-highly-performant-smooth-scrolling-android-listviews/
If you really want to do it all in your base adapter, then convert the items in the constructor, something like this:
public class LoadInfoViewAdapter : BaseAdapter<Items>
{
private List<CondensedItems> cItems;
private Context gContext;
public LoadInfoViewAdapter(Context context, List<Items> loadinfo, string LoadnStop)
{
gContext = context;
gLoadnStop = LoadnStop;
// Get all of the unique item numbers.
var uniqueItemNos = loadInfo.Select(x => x.ItemNo).Distinct();
cItems = new List<CondensedItem>();
foreach(string uniqueItem in uniqueItemNos)
{
// Gets all of the items with the target itemNo. If you want to avoid merging them all, you need to do some work checking for sequential indexes or something similar.
var matchingItems = loadInfo.Select(x => x.ItemNo == uniqueItem).ToList();
var lineNo = matchingItems[0].LineNo;
var qty = matchingItems.Sum(x => x.Qty).ToString();
StringBuilder packsSB = new StringBuilder();
foreach(var item in matchingItems)
{
packs.Append(item.Pack + ",");
}
string packs = packsSB.ToString().Trim(',');
cItems.Add(
new CondensedItem
{
ItemNo = uniqueItem,
LineNo = lineNo,
Packs = $"{qty}({packs})"
});
}
}
public override int Count
{
get { return cItems.Count; }
}
public override long GetItemId(int position)
{
return position;
}
public override Items this[int position]
{
get { return cItems[position]; }
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
View row = convertView;
CondensedViewHolder viewHolder;
if (row == null)
{
row = LayoutInflater.From(gContext).Inflate(Resource.Layout.LoadInfoItems_Row, null);
viewHolder = new CondensedViewHolder
{
ItemView = row.FindViewById<TextView>(Resource.Id.txtItemNo),
LineView = row.FindViewById<TextView>(Resource.Id.txtLineNo),
PacksView = row.FindViewById<TextView>(Resource.Id.txtQuantity)
}
row.Tag = viewHolder;
}
else
{
viewHolder = row.Tag as CondensedViewHolder;
}
viewHolder.ItemView.Text = cItems[position].ItemNo;
viewHolder.LineView.Text = cItems[position].LineNo;
viewHolder.PacksView.Text = cItems[position].Packs;
return row;
}
private class CondensedItem
{
string ItemNo { get; set; }
string LineNo { get; set; }
string Packs { get; set; }
}
private class CondensedViewHolder : Java.Lang.Object
{
public TextView ItemView { get; set; }
public TextView LineView { get; set; }
public TextView PacksView { get; set; }
}
}
I need to make a loop that iterates through all objects in the list and extracting the value of a specific index and return an Int with the total amount.
I will try to explain as throughly as possible.
public class CostEachActivity
{
public string activityID { get; set; }
public int accountNr { get; set; }
public int amount { get; set; }
}
This is my class which the list contains objects from.
I will send down a string of activityID which I have to return the total cost of from all accounts.
this is the list which contains all the objects(the count is 5 atm)
List<CostEachActivity> dka = new List<CostEachActivity>();
EDIT: This is the working method.
int sum = 0;
List<CostEachActivity> templist = new List<CostEachActivity>();
foreach (CostEachActivity cea in dka)
{
if (cea.activityID == activityID)
templist.Add(cea);
}
foreach(var item in tempList)
{
sum += item.Amount;
}
The Problem
Your inner loop, besides not compiling, should be outside the main loop. After finding all the items you want to sum it (or in a different way to already sum it as you find the matching item.
Also, seeing that you opened a {} after the if and nested the second foreach in it - That scope happens for every item. You if statement, if returns true will execute the adding to the tempList and then, no matter what occurred in the if the second foreach will take place.
if (cea.activityID == activityID) // If this is true
templist.Add(cea); // Then this executes
{ // And this happens in any case
foreach (cea.amount = sum + amount)
}
Possible Solutions
Doing it the way you started:
int sum = 0;
List<CostEachActivity> templist = new List<CostEachActivity>();
foreach (CostEachActivity cea in dka)
{
if (cea.activityID == activityID)
templist.Add(cea);
}
foreach(var item in tempList)
{
sum += item.Amount;
}
Sum the values as you go:
int sum = 0;
foreach (CostEachActivity cea in dka)
{
if (cea.activityID == activityID)
sum += cea.Amount;
}
And IMO the best way: Using linq you can do this:
var sum = dka.Where(item => item.activityID == activityID)
.Sum(item => item.amount);
If I got this right you could do something like:
public int GetSum(string activityId)
{
return dka.Where(cea => cea.activityId == activityId).Sum(cea => cea.amount)
}
Or with the new C# 6.0 Expression-bodied function members:
public int GetSome(string activityId) =>
dka.Where(cea => cea.activityId == activityId)
.Sum(cea => cea.amount);
Use Linq
public int GetSum(string activityID)
{
return dka.Where(d => d.activityID == activityID).Sum(d => d.amount);
}
I have a couple validators that is validating an IDeliveryObject, which conceptually can be described as a file with several rows. That part is working fine.
IEnumerable<IDeliveryValidator> _validators; // Populated in ctor. Usually around 20 different validators.
private IEnumerable<IValidationResult> Validate(IDeliveryObject deliveryObject)
{
var validationErrors = new List<IValidationResult>();
int maxNumberOfErrors = 10;
foreach (IDeliveryValidator deliveryValidator in _validators)
{
IEnumerable<IValidationResult> results = deliveryValidator.Validate(deliveryObject).Take(maxNumberOfErrors);
validationErrors.AddRange(results);
if (validationErrors.Count >= maxNumberOfErrors )
{
return validationErrors.Take(maxNumberOfErrors).ToList();
}
}
return validationErrors;
}
The logic iterates through a couple of validators, which all validates the file for different things.
And a validator can look something like this:
public IEnumerable<IValidationResult> Validate(IDeliveryObject deliveryObject)
{
using (var reader = File.OpenText(deliveryObject.FilePath))
{
int expectedLength = 10; // Or some other value.
string line;
while ((line = reader.ReadLine()) != null)
{
var lineLength = line.Length;
if (lineLength != expectedLength)
{
// yield an error for each incorrect row.
yield return new DeliveryValidationResult("Wrong length...");
}
}
}
}
The ValidationResult looks like this:
public class DeliveryValidationResult : ValidationResult, IValidationResult
{
public DeliveryValidationResult(bool isSoftError, string errorMessage) : base(errorMessage)
{
IsSoftError = isSoftError;
}
public DeliveryValidationResult(string errorMessage) : base(errorMessage)
{
}
public DeliveryValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames)
{
}
public DeliveryValidationResult(ValidationResult validationResult) : base(validationResult)
{
}
public bool IsSoftError { get; set; }
}
public interface IValidationResult
{
string ErrorMessage { get; set; }
bool IsSoftError { get; set; }
}
Thanks to Take(maxNumberOfErrors) and yield each validator will only return 10 validationresults, which used to be fine. But now I need to handle "soft validation result", which is the same kind of validation result, but it should not be included in the number of results yielded. It's a kind of warning, which is defined by setting IsSoftError in IValidationResult. A validator can yield both "soft validation result" and "regular validation result".
What I want is to take x validation results + unlimited soft validation results, so that all IValidationResults with IsSoftError == true will be included in the collection, but not in the count. I know that it sounds weird, but the concept is that there's no need to keep validating the file after x errors, but the validation can return unlimited "warnings".
It's very important that the Enumeration isn't enumerated more than one time, because it's CPU-heavy. Below is the code I want to change.
private IEnumerable<IValidationResult> Validate(IDeliveryObject deliveryObject)
{
var validationErrors = new List<IValidationResult>();
int maxNumberOfErrors = 10;
foreach (IDeliveryValidator deliveryValidator in _validators)
{
// Here I want results to contain MAX 10 regular validation results, but unlimited soft validation results
IEnumerable<IValidationResult> results = deliveryValidator.Validate(deliveryObject).Take(maxNumberOfErrors);
validationErrors.AddRange(results);
if (validationErrors.Count(x => !x.IsSoftError) >= maxNumberOfErrors)
{
return validationErrors.Take(maxNumberOfErrors).ToList();
}
}
return validationErrors;
}
EDIT:
When I got 10 'hard' errors I want to stop the cycle completely. The main issue here is that the cycle doesn't stop when 10 'soft' errors occured.
In case you want to completely stop after 10 'hard' errors, you could try this:
int count = 0;
IEnumerable<IValidationResult> results = deliveryValidator.Validate(deliveryObject)
.TakeWhile(error => error.IsSoftError || count++ < maxNumberOfErrors);
this would stop when the 11th hard error is encountered.
//Go through all the items and sort them into Soft and NotSoft
//But ultimately these are in memory constructs...so this is fast.
var foo = Validate(delivery).ToLookup(x => x.IsSoftError);
var soft = foo[true];
var hard = foo[false].Take(10);
var result = Enumerable.Concat(soft, hard);
I have a huge list of strings and I want to compare a database table's records with it. What is the best solution?
You can suppose specified table's name is ATable and its structure is like:
public class ATable
{
[Key]
public long Id{get;set;}
[Key]
public long Name{get;set;}
}
I wrote the following code
using(var context = new MyDbContext())
{
context.Log = (log) => {Console.WriteLine(log)};
var result = context.ATables.Where(item => hugeList.Contains(item.Name)).ToList();
}
I checked generated logs and I saw that above code translated to SQL IN(...) statement and because of hugeness of list application crash.
I'm sure there is a good way to solve this problem, then you professionals can show me right one.
Thanks
Since EF 6 Alpha 1, EF includes an improvement that accelerates the translation of Enumerable.Contains.
If you are using an earlier version of EF or the size of your list is too big, as propose #Dan-o you can break the huge list in smaller chunks. To do this you can use the solution that #divega wrote in this post. Below I adapt that solution to your problem context:
public partial class MyDbContext
{
public IEnumerable<ATable> GetElements(IEnumerable<long> hugeList, int chunkSize = 100)
{
foreach (var chunk in hugeList.Chunk(chunkSize))
{
var q = ATables.Where(a => chunk.Contains(a.Name));
foreach (var item in q)
{
yield return item;
}
}
}
}
Extension methods for slicing enumerable sequences:
public static class EnumerableSlicing
{
private class Status
{
public bool EndOfSequence;
}
private static IEnumerable<T> TakeOnEnumerator<T>(IEnumerator<T> enumerator, int count,
Status status)
{
while (--count > 0 && (enumerator.MoveNext() || !(status.EndOfSequence = true)))
{
yield return enumerator.Current;
}
}
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> items, int chunkSize)
{
if (chunkSize < 1)
{
throw new ArgumentException("Chunks should not be smaller than 1 element");
}
var status = new Status { EndOfSequence = false };
using (var enumerator = items.GetEnumerator())
{
while (!status.EndOfSequence)
{
yield return TakeOnEnumerator(enumerator, chunkSize, status);
}
}
}
}
Then you can do something like this:
var result= context.GetElements(hugelist).ToList();
On my view I have a GridView. As the number of items can be very high I'm trying to implement ISupportIncrementalLoading as new ItemsSource.
public class IncrementalCollection : ObservableCollection<Object>, ISupportIncrementalLoading
{
private int _addedItems = 0;
private const int _PAGESIZE = 20;
private IList<BookingDTO> _bookings;
public IncrementalCollection(Guid guid)
{
LoadBookings(guid);
}
private async Task LoadBookings(Guid guid)
{
var data = await IoC.Resolve<IMBAMService>().GetOrderedBookingsForAccountAsync(guid);
_bookings = data.Data;
}
public bool HasMoreItems
{
get { return (this.Count < _bookings.Count); }
}
public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
var coreDispatcher = Window.Current.Dispatcher;
return Task.Run<LoadMoreItemsResult>(async () =>
{
await coreDispatcher.RunAsync(CoreDispatcherPriority.High,
() =>
{
foreach (var item in _bookings.Skip(_addedItems).Take((int)count))
{
_addedItems++;
this.Add(item);
}
});
return new LoadMoreItemsResult() { Count = count };
}).AsAsyncOperation<LoadMoreItemsResult>();
}
}
In the navigation function I create a new instance.
BookingList.ItemsSource = new IncrementalCollection(ViewModel.Account.Id);BookingList.ItemsSource = new IncrementalCollection(Guid);
My problem is now that LoadMoreItemsAsync is called so many times that the hole list will be displayed and not as expected after scrolling.
What do I need to change that it loads only the first 50 items and the rest when it's needed after scrolling?
I've tried to implement it like here:
http://blogs.msdn.com/b/devosaure/archive/2012/10/15/isupportincrementalloading-loading-a-subsets-of-data.aspx
It's possible that the GridView is trying to make use of infinite space instead of limiting its size to that of the visible screen. This would cause it to keep querying to load items to fill up the available space, even if those items are not visible. Read this page for more info on this, specifically where it mentions ScrollViewer: http://msdn.microsoft.com/en-us/library/windows/apps/hh994637.aspx.