This works for the data types I have tested (string, boolean, and int32), any others would need to be added. I have a problem with boolean, since the GridView only offers the choices already showing on the screen, and not both True and False, which is required for server side filtering/paging. I am hopeful that will be fixed. In addition to executing the filters against the server, I also capture the sorts as well.
private void gv_Filtered(object sender, Telerik.Windows.Controls.GridView.GridViewFilteredEventArgs e) { CompositePredicateDescription p = null; PredicateDescription _p = null; EntityQueryFilterCollection filters = new EntityQueryFilterCollection(); var query = (EntityQuery<Manufacturer>)_entityManager.Manufacturers; foreach (Telerik.Windows.Controls.GridView.ColumnFilterDescriptor d in gv.FilterDescriptors) { PredicateDescription p1 = pb(d.DistinctFilter.Member.ToString(), d.FieldFilter.Filter1, (d.Column).DataType); if (d.FieldFilter.Filter2.Value.ToString() != "") { PredicateDescription p2 = pb(d.DistinctFilter.Member.ToString(), d.FieldFilter.Filter2, (d.Column).DataType); CompositePredicateDescription pc ; if (((Telerik.Windows.Data.CompositeFilterDescriptor)(d.FieldFilter)).LogicalOperator == Telerik.Windows.Data.FilterCompositionLogicalOperator.And) { pc = p2.And(p1); } else { pc = p2.Or(p1.And(p1)); } if (p != null) p = p.And(pc); else if (_p != null) p = pc.And(_p); else p = pc; } else { if (p != null) p = p.And(p1); else if (_p != null) p = p1.And(_p); else _p = p1; } } if (p == null) if (_p != null) p = _p.And(_p); if (p != null) query = (EntityQuery<Manufacturer>)PredicateBuilder.FilterQuery(_entityManager.Manufacturers, p); collection = new EntityQueryPagedCollectionView(query, 20, 20, true, false); if (gv.SortDescriptors.Count>0) { int i=0; while (i<gv.SortDescriptors.Count) { collection.SortDescriptions.Add(new SortDescription(gv.SortDescriptors[i].Member, gv.SortDescriptors[i].SortDirection)); i++; } } collection.Refresh(); gv.DataContext = dp.DataContext = collection; // MessageBox.Show("filtered"); } private PredicateDescription pb(string p, Telerik.Windows.Data.FilterDescriptor filterDescriptor, Type type) { PredicateDescription returnValue; if (type == typeof(string)) returnValue = PredicateBuilder.Make(typeof(Manufacturer), p, (IdeaBlade.Linq.FilterOperator)filterDescriptor.Operator, filterDescriptor.Value); else if (type == typeof(Boolean)) returnValue = PredicateBuilder.Make(typeof(Manufacturer), p, (IdeaBlade.Linq.FilterOperator)filterDescriptor.Operator, (Boolean)filterDescriptor.Value); else returnValue = PredicateBuilder.Make(typeof(Manufacturer), p, (IdeaBlade.Linq.FilterOperator)filterDescriptor.Operator, Convert.ToInt32(filterDescriptor.Value)); return returnValue; } My goal is to move all the intelligence to helpers if general and the viewmodel if specific, and out of the code behind. I also would love to use binding as much as possible.
Also the redundancy of pc = p2.Or(p1.And(p1)); was necessary. There is a bug in the devforce Or which requires the second argument to be composite. Very odd, but simple enough to work around.
13 Answers, 1 is accepted
Why not use custom QueryableCollectionView similar to my blog post instead grid events? You will have all info for everything including, paging, sorting, filtering, grouping, etc.
Regards,Vlad
the Telerik team
Just a quick follow up. I've attached small example application to illustrate you this.
Regards,Vlad
the Telerik team
1) How would you modify your code to be organized into MVVM and repository? I am using MVVM light and I still am not proficient enough to see how to move from 1 layer to 2, or is the repository not a good idea?
2) How would you pickup detail data based on the currently selected item to populate another gridview (no paging this time). I cannot figure out how to do this with a repository. Where I am stuck, is how to pass in the parameter.
I fixed the problem with constantly requesting the count. This is the modification.
The start of Refresh() is now
void Refresh(Boolean needsRefresh) { IdeaBlade.EntityModel.EntityScalarQueryOperation<int> countQuery; if (needsRefresh) { countQuery = (from o in (EntityQuery<Customer>)Context.Customers.Where(_Data.FilterDescriptors) select o).AsScalarAsync().Count(); countQuery.Completed += (sender, args) => { _Data.SetTotalItemsCount(args.Result); }; } In the first case use Refresh(true);
In the second case use Refresh((((Telerik.Windows.Data.QueryableCollectionView)(sender))).NeedsRefresh);
After all you keep track of the need for a refresh, very handy.
With our upcoming Q3 2010 Beta you will be able also to load your data on demand (only visible records will be loaded) using our new VirtualQueryableCollectionView. I've attached small project to illustrate you this - maybe it will be interesting for you.
Kind regards,Vlad
the Telerik team
I thought we had pretty much accomplished that with your previous code snippet. It obtained a count and the first N records, and then just retrieved new records when required. Devforce cached previous retrievals, so it minimized datatbase access. The only annoying thing was that if I opened a second view on the same datagrid, devforce ignored the old cache and started a new one, retrieving fresh data from the database. Interestingly, when I retrieve an entire database, and don't use your paged data source, it does not do that, it just works from cache.
Since i am in test and evaluation mode anyway, can I download the betacode? I might as well program to the new VirtualQueryableCollectionView.
It also appears to be missing an update to itemCount when filters are changed. Is that happening in the background somewhere, if not it will not handle filters properly?
As far as I remember well grouping was off of both examples. You are right about the count in my second project. Here is how to refactor the code to fix this:
VirtualQueryableCollectionView _Data; public VirtualQueryableCollectionView Data { get { if (_Data == null) { _Data = new VirtualQueryableCollectionView() { LoadSize = 10 }; _Data.FilterDescriptors.CollectionChanged += (s, e) => { UpdateCount(); }; UpdateCount(); _Data.ItemsLoading += (s, e) => { LoadData(e.StartIndex, e.ItemCount); }; LoadData(0, _Data.LoadSize); } return _Data; } } private void UpdateCount() { var countQuery = ((EntityQuery<Order_Detail>)Context.Order_Details.Where(_Data.FilterDescriptors)).AsScalarAsync().Count(); countQuery.Completed += (s, e) => { _Data.VirtualItemCount = e.Result; }; }Our beta will be available for download later Today. Best wishes,
Vlad
the Telerik team
if ((((Telerik.Windows.Data.VirtualQueryableCollectionView)(sender))).NeedsRefresh) { ... } And yes you did manage groups on the previous example. Would you need to do the same for this example? If so please adjust it.
protected override IQueryable CreateView() { return IsGrouped ? QueryableSourceCollection.GroupBy(GroupDescriptors) : QueryableSourceCollection; } The error seems like a devforce bug, but I am so lost I thought I shoudl send it to both of you.
+ InnerException {EntityServerException: The 'Inv' property on 'Inventory' could not be set to a 'Int16' value. You must set this property to a non-null value of type 'Int32'. ---> System.InvalidOperationException: The 'Inv' property on 'Inventory' could not be set to a 'Int16' value. You must set this property to a non-null value of type 'Int32'.
at IdeaBlade.EntityModel.Server.EntityServerQueryInterceptor.HandleException(Exception e, PersistenceFailure failureType)
at IdeaBlade.EntityModel.Server.EntityServerQueryInterceptor.OnExecuteQuery()
at IdeaBlade.EntityModel.Server.EntityServerQueryInterceptor.Execute(IEntityQuery entityQuery, SessionBundle sessionBundle, IEntityServer entityServer)
at IdeaBlade.EntityModel.Server.EntityServer.Fetch(SessionBundle sessionBundle, IEntityQuerySurrogate surrogate)
at SyncInvokeFetch(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
at IdeaBlade.EntityModel.EntityManager.HandleEntityServerException(Exception ex, Boolean tryToHandle, PersistenceOperation operation)
at IdeaBlade.EntityModel.EntityManager.HandleEntityServerException(BaseOperation op, Boolean tryToHandle, PersistenceOperation persistenceOp)
at IdeaBlade.EntityModel.EntityManager.<ExecuteQueryAsyncCore>b__61[T](EntityQueryOperation`1 op)
at IdeaBlade.EntityModel.AsyncProcessor`1.<Execute>b__5(Object x)} System.Exception {IdeaBlade.EntityModel.EntityServerException}
The reason why I am so confused is that anywhere I look including the mapping Inv is Int32. I have no idea where this Int16 is even coming from
This is the statement where it fails. - on Data.Load
void LoadData(int startIndex, int itemCount)
{
var query = (EntityQuery<Product>)Context.Products.OrderBy(o => o.ProdID).Include("Inventories")
.Where(Data.FilterDescriptors)
.Sort(Data.SortDescriptors)
.Skip(startIndex).Take(itemCount)
;
query.ExecuteAsync().Completed += (s, e) =>
{
Data.Load(startIndex, e.Results);
};
}
Here is the whole viewmodelusing GalaSoft.MvvmLight; using Ashlin.Model; using IdeaBlade.EntityModel; using Telerik.Windows.Data; namespace Ashlin.ViewModel { public class ProductsViewModel : ViewModelBase { storeEntities _Context; public storeEntities Context { get { if (_Context == null) { _Context = new storeEntities(); } return _Context; } } VirtualQueryableCollectionView _Data; public VirtualQueryableCollectionView Data { get { if (_Data == null) { _Data = new VirtualQueryableCollectionView() { LoadSize = 15 }; _Data.FilterDescriptors.CollectionChanged += (s, e) => { UpdateCount(); }; UpdateCount(); _Data.ItemsLoading += (s, e) => { LoadData(e.StartIndex, e.ItemCount); }; LoadData(0, _Data.LoadSize); } return _Data; } } private void UpdateCount() { var countQuery = ((EntityQuery<Product>)Context.Products.Where(_Data.FilterDescriptors)).AsScalarAsync().Count(); countQuery.Completed += (s, e) => { _Data.VirtualItemCount = e.Result; }; } void LoadData(int startIndex, int itemCount) { var query = (EntityQuery<Product>)Context.Products.OrderBy(o => o.ProdID).Include("Inventories") .Where(Data.FilterDescriptors) .Sort(Data.SortDescriptors) .Skip(startIndex).Take(itemCount) ; query.ExecuteAsync().Completed += (s, e) => { Data.Load(startIndex, e.Results); }; } Turns out the bug is not related to the include, but rather this specific field.
I tested the Inventory file by itself using an identical scenario to the one with the product database, to see if it blew up on its own and it did.
From store.IB.Designer.cs in case its of use
#region Inv property
/// <summary>Gets or sets the Inv. </summary>
[Bindable(true, BindingDirection.TwoWay)]
[Editable(true)]
[Display(Name="Inv", AutoGenerateField=true)]
[DataMember]
public System.Nullable<int> Inv {
get { return PropertyMetadata.Inv.GetValue(this); }
set { PropertyMetadata.Inv.SetValue(this, value); }
}
#endregion Inv property
And once again when I look at the map of the field it clearly shows Int32
Ouch.
