This is a migrated thread and some comments may be shown as answers.

[Solved] Integration with Devforce - working but not optimal

13 Answers 310 Views
GridView
This is a migrated thread and some comments may be shown as answers.
This question is locked. New answers and comments are not allowed.
Andrew
Top achievements
Rank 1
Andrew asked on 18 Oct 2010, 04:50 AM
I have managed to get GridView to utilize devforce for server side paging and filtering.  So far I have managed to do it in code, not in the viewmodel, and not by by binding the filterdescriptors and sortdescriptors which of course would be preferable.  I am hoping someone has done or building on my work can do a better job and would be willing to share their code. 

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;
        }
gv is the gridview and dp is the datapager.  The only way I could figure out to get them to refresh was to reset the datacontext.  Please let me know if there is a better way.

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

Sort by
0
Vlad
Telerik team
answered on 18 Oct 2010, 07:58 AM
Hi Andrew,

 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
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Vlad
Telerik team
answered on 18 Oct 2010, 09:55 AM
Hi Andrew,

Just a quick follow up.  I've attached small example application to illustrate you this. 

Regards,
Vlad
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Andrew
Top achievements
Rank 1
answered on 18 Oct 2010, 06:08 PM
That is way simpler, and its very interesting that its so easy to integrate.  Could you do two things for me please.  That would have saved me tons of work.

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.


0
Andrew
Top achievements
Rank 1
answered on 18 Oct 2010, 08:33 PM
I have implemented it without the repository, placing your code (modified for my data) in the viewmodel.  I am wondering why it requests an item count every time you change the page, no change to filters etc...?  Seems unecessary.

0
Andrew
Top achievements
Rank 1
answered on 19 Oct 2010, 01:41 AM

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);
            };
        }
It is called in two places, first when the viewmodel is invoked, second whenver a change occurs (which most of the time is just turning a page)
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.
0
Vlad
Telerik team
answered on 19 Oct 2010, 08:25 AM
Hello Andrew,

 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
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Andrew
Top achievements
Rank 1
answered on 19 Oct 2010, 11:05 AM
That is exactly what I need for the larger databases.  My plan is to load smaller databases, particularely those that are used constantly with includes, when the application loads, and then to manage the larger databases on demand.

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.
0
Andrew
Top achievements
Rank 1
answered on 19 Oct 2010, 11:16 AM
This example looks very similar to the other one, but it seems to be missing the group logic you put on the other one.  I think you should always maintain compatibility with your groupings in your examples.  Can you update this or will groupings break it?

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?

 
0
Vlad
Telerik team
answered on 19 Oct 2010, 11:41 AM
Hi,

 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
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Andrew
Top achievements
Rank 1
answered on 19 Oct 2010, 11:58 AM
Once again it looks like the count will be updated too often.  I would add this to your update count function, or could the trigger event be a needs refresh event rather than a page change?

if ((((Telerik.Windows.Data.VirtualQueryableCollectionView)(sender))).NeedsRefresh)
{
...
}
Assuming that is also available like it is for the QueryableCollectionView.

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;
}

0
Andrew
Top achievements
Rank 1
answered on 20 Oct 2010, 02:36 AM
i have downloaded the beta, and got my first viewmodel working with it, products.  As soon as I added .Include("Inventories") it failed and with an error I cannot fathom.

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 viewmodel
using 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);
            };
        }



0
Andrew
Top achievements
Rank 1
answered on 20 Oct 2010, 03:06 AM

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

0
Andrew
Top achievements
Rank 1
answered on 20 Oct 2010, 03:19 AM
I finally came to my senses, deleted the Inventory file from store.edmx, added it back, and voila now Inv is Int16, go figure.  Considering its a calculated field, hard to understand why its type would change.

Ouch.
Tags
GridView
Asked by
Andrew
Top achievements
Rank 1
Answers by
Vlad
Telerik team
Andrew
Top achievements
Rank 1
Share this question
or