NOTE: Starting from version 16.1, a simpler solution can be implemented based on the How to: Display a Non-Persistent Object's List View from the Navigation article, using a non-persistent object to represent the query result set.
Scenario
Sometimes it is important to show a readonly ListView with custom data obtained from the database, rather than through standard XPO mechanisms, by means of loading entire persistent objects.
While this custom data may include certain properties of your XPO classes, in a general case, it may come from anywhere, including but not limited to, a raw SQL query, stored procedure, database view or a very complex LINQ query returning only a subset of persistent class properties or even custom data fields. While the first scenarios can be solved by mapping a persistent class to a database view, the second group of scenarios is optimal in implementing a custom form/control (check How to show custom forms and controls in XAF (Example) for more details) or a custom CollectionSource.
Here, we will demonstrate how the latter option can be realized to display data from a custom LINQ query using the LINQ to XPO approach.
In particular, we want to show a list of employees with maximum orders:
Steps to implement
1. Include the XAF.LinqToXpo module project in your solution and make sure it builds successfully.
This module is supposed to generate custom ListView nodes in the Application Model with the _Linq suffix so that this ListView uses data returned by a predefined method (see the XPQueryMethod attribute for the ListView node) using LINQ.
To provide this custom ListView with data, a custom CollectionSourceBase class descendant is implemented (see the XAF.LinqToXpo\LinqCollectionSource.xx file). It is enabled for a custom ListView via the XafApplication.CreateCustomCollectionSource event (see the XAF.LinqToXpo\Module.xx file).
2. Invoke the Module Designer for YourSolutionName.Module project and drag and drop the XafLinqToXpoModule component to the Required Modules list (this component should automatically appear in the Visual Studio Toolbox after executing the previous step).
3. Implement the public static methods that accept DevExpress.Xpo.Session as a parameter and return System.Linq.IQueryable as a result in your persistent classes, where required.
Consider the following signature for more clarity: public static System.Linq.IQueryable MethodName(DevExpress.Xpo.Session session);
Within the method body, use the LINQ to XPO approach to return a required data set based on XPQuery<T>. If you want to return a custom property as part of your data set which does not exist within your XPO data model, then include it into the selected part of the query with a custom name (e.g., Employee_Linq, Orders_Sum_Linq, etc.).
Mark these methods with the XAF.LinqToXpo.QueryProjectionsAttribute to specify that comma-separated names of data properties will be included as a query result.
Refer to the NorthwindDemo.Module\PersistentObjects.xx for an example.
4. Invoke the Model Editor for YourSolutionName.Module project and add custom calculated fields with dummy expressions for each specified custom property which is not part of your default XPO data model. Refer to the NorthwindDemo.Module\Model.DesignedDiffs.xafml file for an example.
IMPORTANT NOTES
1. This example uses the Northwind database for testing. You can download the database creation scripts for testing from here.
2. Since a Linq-based ListView may contain custom data sets and not entire persistent objects in a general case, certain standard Controllers are disabled here (e.g., you cannot open a detail form for this data record or create a new object). Refer to the XAF.LinqToXpo\DisableStandardActionsLinqListViewController.xx file for more details.
3. If you want your default ListView to include custom calculated values, then it is possible to use the built-in Custom Fields feature for that purpose.
Question Comments
Added By: Artem G. at: 9/25/2015 4:02:41 AM
Hello Dennis. Sorry, but your code doesn't work in my simple test project.
1. First problem is that I throws an exception like "Unable to cast object of type 'ModelListView' to type 'XAF.LinqToXpo.IModelListViewLinq'."
It happens not on run, but when I open XAF Model Editor in VS. It doesn't like the line:
listViewInfo = (IModelListView)node.AddNode<IModelListView>(id);
in class ModelListViewLinqNodesGeneratorUpdater.
If I comment it, Editor works fine.
2. While I have created the entities in business object folder of Model like Customer, Employee, Orders, they have appeared in views (as any other business object), but If I try to set my custom created view with _Linq suffix to nodes, it doesn't want to work, doesn't load objects (Customer). Maybe it's a the default behaviour works, not your code? Otherwise it even doesnt enter the most part of code in ModelListViewLinqColumnsNodesGeneratorUpdater because it doesn't find any XPQueryMethod method (XPQueryMethod == null)
3. Somehow only view with name Customer_ListView_CustomersLInq_Linq appears in Model Editor. Others don't. Maybe because of problem from (1). I set it to my node, but it didn't load data (problem 2)
4. I'm not sure that it's a real important to get data from linq, but it's much more important for all community to show one good example with stored procedures and link this post no more. All link from forum references this article. But I doesn't clarify how can I use SP and where. Could you pls provide an example project with SP. Or at least a working project with this code
Regards, Artem
Added By: Artem G. at: 9/25/2015 4:03:35 AMI used last version of code. My XAF version is 13.2.10 (can't and woudn't upgrade, becase of project convert problems)
Added By: Artem G. at: 9/25/2015 4:28:47 AMAnd also such behaviour like "you cannot open a detail form for this data record " is very-very bad. Are there any solutions?
I just exmplane a simple use-case, where it's required:
1. You need a user audit statistics to be show like "department", "action", "total users", "total visits"
2. All these "totals" have to be calculated on DB level with stored procedure. So I need all this workaround to be used.
3. On details click I want to see the full statistics about department: exact list of users and their actions.
So, how can I show details ?
Added By: Dennis (DevExpress Support) at: 9/28/2015 7:07:06 AM@Artem:
A1-3: This can happen only if the 2nd step from the instructions above was skipped. Please verify it in your own sample.
A4: This LINQ-based example was originally created based on many community requests. While we will unlikely provide a new example for the old XAF versions, in the latest XAF version we have a new feature and also the eXpressApp Framework > Task-Based Help > How to: Display a Non-Persistent Object's List View from the Navigation and How to: Display Non-Persistent Objects in a Report documentation articles describing how to show non-persistent data coming from anywhere in XAF forms. It can be a stored procedures results or anything you want.
A5: While the capability to display a corresponding DetailView by double-clicking on a ListView record is not implemented in this particular example, it is not difficult to implement this functionality yourself by handling the eXpressApp Framework > DevExpress.ExpressApp.SystemModule > ListViewProcessCurrentObjectController > CustomProcessSelectedItem event.
I hope you find this information helpful. If you have any further difficulties, submit a separate ticket and attach your test project where you tried to implement the suggested changes.
Added By: Leo Hui at: 9/29/2015 12:11:55 AM
in Module.cs...
The name 'InitializeComponent()' does not exist in the current context...
how do I solve that?
@Leo: It is likely you have not converted your solution using the Project Converter tool. The following StackOverFlow threads describe other possible causes of this compilation error: http://stackoverflow.com/search?q=InitializeComponent%28%29%27+does+not+exist+in+the+current+context.
If this does not help, please submit a separate ticket and attach the project source where you are experiencing this error.