Here I will post problems I and my colleagues met and solutions we found.

Sunday, January 11, 2009

Lazy loading for CSLA lists to support Server mode in DevExpress Grid. Part 3

Now, when we have LazyLoaderController here is what was done in ExtendedBindingList.



#region Lazy loading
private LazyLoadingController _lazyLoadingController;
private int _loadingWindowSize = 100;
private int _deletedIndex = -1;
private bool _useTheading = false;
private bool _isFetched = false;

/// <summary>
/// Change size of loading window (number of records to be loaded)
/// </summary>
/// <param name="loadingWindowSize"></param>
protected void ChangeLoadingWindowSize(int loadingWindowSize)
{
_loadingWindowSize = loadingWindowSize;
}
/// <summary>
/// Fetch data into current list
/// </summary>
/// <param name="creatria"></param>
protected virtual void FetchData(object creatria)
{
throw new Exception("FetchMoreData is not implemented");
}

protected void SetFetched()
{
_isFetched = true;
}

protected bool IsFetched
{
get { return _isFetched; }
}

/// <summary>
/// Load more records into current list if necessary to support
/// window around particular element
/// </summary>
/// <param name="index">defines what objects should be loaded (+/- window size/2)</param>
private void FetchNewWindow(int index)
{
if (_lazyLoadingController != null)
{
lock (_lazyLoadingController)
{
int startingIndex = index - (_loadingWindowSize / 2);
if (startingIndex < 0)
startingIndex = 0;
List<object> idList = _lazyLoadingController.GetUnloadedIdList(startingIndex, _loadingWindowSize);
if ((idList != null) && (idList.Count > 0))
{
LazyLoadingCriteriaBase criteria = new LazyLoadingCriteriaBase(LoadingCommand.FetchData);
criteria.IdList = idList;
FetchData(criteria);
}
}
}
}

private void FetchObject(object id)
{
List<object> idList = new List<object>(1);
idList.Add(id);
LazyLoadingCriteriaBase criteria = new LazyLoadingCriteriaBase(LoadingCommand.FetchData);
criteria.IdList = idList;
FetchData(criteria);
}
/// <summary>
/// Get unique id for child object.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected virtual object GetItemId(object item)
{
if (item is IIdObject)
return ((IIdObject)item).Id;
else
return item.GetHashCode();
}

protected virtual DateTime GetItemLastModified(T item)
{
if (item is IIdObject)
return ((IIdObject)item).LastModified;
else
return DateTime.MinValue;
}

/// <summary>
/// Remove Id of the object. Called internally when object removed
/// </summary>
/// <param name="id"></param>
protected void RemoveId(object id)
{
if (_lazyLoadingController != null)
lock (_lazyLoadingController)
{
_lazyLoadingController.RemoveId(id);
}
}
/// <summary>
/// Turn lazy loading on. Should be called before first fetch is done
/// </summary>
protected void SetLazyLoading(bool useTheading)
{
if (_lazyLoadingController == null)
{
_lazyLoadingController = new LazyLoadingController();
// multithreading doesn't work well yet
// _useTheading = useTheading;
}
}
/// <summary>
/// Is LazyLoading turned on
/// </summary>
public bool IsLazyLoading
{
get { return (_lazyLoadingController != null); }
}
/// <summary>
/// Some loading may be done in separate thread when lazy loading is used.
/// </summary>
protected bool UseThreading
{
get { return _useTheading; }
}

/// <summary>
/// Adds Id of the object to the list. Object is not loaded yet. Should be called
/// when ids are loaded
/// </summary>
/// <param name="id">if true, no check for existing id is done, better performance</param>
protected void AddId(object id, bool knownNew, DateTime lastModified)
{
if (_lazyLoadingController != null)
{
lock (_lazyLoadingController)
{
_lazyLoadingController.AddId(id, knownNew, lastModified);
}
}
}

/// <summary>
/// Called internally when object is loaded
/// </summary>
/// <param name="id"></param>
/// <param name="item"></param>
protected void LoadObject(object id, T item)
{
if (_lazyLoadingController != null)
lock (_lazyLoadingController)
{
_lazyLoadingController.LoadObject(id, item);
}
}

/// <summary>
/// Used internally
/// </summary>
protected void ClearIds()
{
if (_lazyLoadingController != null)
lock (_lazyLoadingController)
{
_lazyLoadingController.Clear();
}
}

/// <summary>
/// Adds object to the list of loaded objects
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
protected override void SetItem(int index, T item)
{
base.SetItem(index, item);
object id = GetItemId(item);
LoadObject(id, item);
DateTime lastModified = GetItemLastModified(item);
if (lastModified > _lastModified)
_lastModified = lastModified;
}

/// <summary>
/// Adds object to the list of loaded objects
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
protected override void InsertItem(int index, T item)
{
object id = GetItemId(item);
LoadObject(id, item);
DateTime lastModified = GetItemLastModified(item);
if (lastModified > _lastModified)
_lastModified = lastModified;
base.InsertItem(index, item);
}
/// <summary>
/// Need to override to call ILazyLoading.ListChanged with proper indexes
/// </summary>
/// <param name="e"></param>
protected override void OnListChanged(ListChangedEventArgs e)
{
base.OnListChanged(e);
if (_listChanged != null)
{
int newIndex;
int oldIndex;

if (_lazyLoadingController != null)
{
lock (_lazyLoadingController)
{
if (e.NewIndex < 0)
newIndex = e.NewIndex;
else
{
if (e.ListChangedType == ListChangedType.ItemDeleted)
{
if (_deletedIndex < 0)
throw new Exception("Index for ListchangeType.ItedDeleted is not defined for ILazyLoading.ListChanged");
newIndex = _deletedIndex;
_deletedIndex = -1;
}
else
{
object item = this[e.NewIndex];
object id = GetItemId(item);
newIndex = _lazyLoadingController.GetObjectIndex(id);
}
}
if (e.OldIndex < 0)
oldIndex = e.OldIndex;
else
{
if (e.OldIndex == e.NewIndex)
oldIndex = newIndex;
else
throw new Exception("ListChangedEventArgs.OldIndex not supported in for ILazyLoading.ListChanged");
}
}
}
else
{
newIndex = e.NewIndex;
oldIndex = e.OldIndex;
}
ListChangedEventArgs e2;
if (e.PropertyDescriptor != null)
e2 = new ListChangedEventArgs(e.ListChangedType, newIndex, e.PropertyDescriptor);
else
e2 = new ListChangedEventArgs(e.ListChangedType, newIndex, oldIndex);
_listChanged(this, e2);
}
}
/// <summary>
/// Load object by Id if not found. Useful only when LazyLoading is on
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
protected object GetObjectyById(object id)
{
if (_lazyLoadingController != null)
{
lock (_lazyLoadingController)
{
object obj = _lazyLoadingController.GetObject(id);
if (obj == null)
{
FetchObject(id);
obj = _lazyLoadingController.GetObject(id);
}
return obj;
}
}
return null;

}

#region ILazyLoading Members
int ILazyLoading.Count
{
get
{
if (_lazyLoadingController != null)
lock (_lazyLoadingController)
{
if (!IsFetched)
{
LazyLoadingCriteriaBase criteria = new LazyLoadingCriteriaBase(LoadingCommand.LoadIds);
FetchData(criteria);
}
return (_lazyLoadingController.Count);
}
else
return Count;
}
}
object ILazyLoading.this[int index]
{
get
{
if (_lazyLoadingController != null)
{
lock (_lazyLoadingController)
{
object id;
object obj;
_lazyLoadingController.GetObjectInfo(index, out obj, out id);
if (obj == null)
{
FetchNewWindow(index);
_lazyLoadingController.GetObjectInfo(index, out obj, out id);
if (obj == null)
throw new Exception("Lazy loading didn't work, object supposed to be loaded");
}
return obj;
}
}
else
return this[index];
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
object ILazyLoading.GetRowKey(int index)
{
if (_lazyLoadingController != null)
{
lock (_lazyLoadingController)
{
object obj;
object id;
_lazyLoadingController.GetObjectInfo(index, out obj, out id);
return id;
}
}
else
{
return GetItemId(this[index]);
}
}
int ILazyLoading.GetRowIndex(object key)
{
if (_lazyLoadingController != null)
lock (_lazyLoadingController)
{
return _lazyLoadingController.GetObjectIndex(key);
}
else
{
for (int i = 0; i < Count; i++)
{
if (GetItemId(this[i]) == key)
return i;
}
return -1;
}
}
int ILazyLoading.Add(object value)
{
if (_lazyLoadingController != null)
{
lock (_lazyLoadingController)
{
((IList)this).Add(value);
object id = GetItemId(value);
return _lazyLoadingController.GetObjectIndex(id);
}
}
else
return ((IList)this).Add(value);

}
bool ILazyLoading.Contains(object value)
{
if (_lazyLoadingController != null)
{
lock (_lazyLoadingController)
{
object id = GetItemId(value);
return _lazyLoadingController.GetObjectIndex(id) >= 0;
}
}
else
return ((IList)this).Contains(value);
}
int ILazyLoading.IndexOf(object value)
{
if (_lazyLoadingController != null)
{
lock (_lazyLoadingController)
{
object id = GetItemId(value);
return _lazyLoadingController.GetObjectIndex(id);
}
}
else
return ((IList)this).IndexOf(value);
}
void ILazyLoading.Insert(int index, object value)
{
if (_lazyLoadingController != null)
{

}
else
((IList)this).Insert(index, value);
}
bool ILazyLoading.IsFixedSize
{
get
{
return ((IList)this).IsFixedSize;
}
}
bool ILazyLoading.IsReadOnly
{
get
{
return ((IList)this).IsReadOnly;
}
}
void ILazyLoading.Remove(object value)
{
((IList)this).Remove(value);
}
void ILazyLoading.ApplySort(ListSortDescriptionCollection sortInfo)
{
if (_lazyLoadingController != null)
{
lock (_lazyLoadingController)
{
LazyLoadingCriteriaBase criteria = new LazyLoadingCriteriaBase(LoadingCommand.ChangeSorting);
criteria.SortInfo = sortInfo;
FetchData(criteria);
}
}
}
private ListChangedEventHandler _listChanged;
event ListChangedEventHandler ILazyLoading.ListChanged
{
add { _listChanged += value; }
remove { _listChanged -= value; }
}
#endregion
#endregion

#region LastModified
private DateTime _lastModified = DateTime.MinValue;
/// <summary>
/// Maximum from children's LastModified
/// </summary>
public DateTime LastModified
{
get
{
if (_lazyLoadingController != null)
{
if (_lazyLoadingController.LastModified > _lastModified)
return _lazyLoadingController.LastModified;
}
return _lastModified;
}
}
#endregion




/// <summary>
/// Remove the item at the
/// specified index.
/// </summary>
/// <param name="index">
/// The zero-based index of the item
/// to remove.
/// </param>
protected override void RemoveItem(int index)
{
#region Lazy loading
object item = this[index];
object id = GetItemId(item);
if (_lazyLoadingController != null)
_deletedIndex = _lazyLoadingController.GetObjectIndex(id);
else
_deletedIndex = -1;
#endregion

OnRemovingItem(this[index]);
base.RemoveItem(index);

#region Lazy loading
RemoveId(id);
#endregion
}

No comments: