Signum Framework Logo
"Open framework that encourages convention over configuration, using C# code,
not XML files, to model at the right level of abstraction and achieve deadlines.
...but also has a full Linq provider, and syncs the schema for you!"
Login
RSS

Search

»



Main
Index
Map
Videos
Download
Source code
Tutorials
Forum
FAQ



Image



PoweredBy

Introduction

Signum.Windows provides a very simple model to express Search functionality over the entities of your application using Linq to Signum queries.

Also, on the client side, it provides a unified user interface for searching:

Image

Searching, as other features, is a vertical in the sense that it affects both the Server and the Client application (and the WCF Service in the middle).

Let's see how all this works:

The Server side

From the Server point of view, creating queries is as simple as writing some IQueryable using Linq to Signum and storing them into the DynamicQueryManager, identifying each one by a key (queryName).

In that sense DynamicQueryManager is just a Dictionary from queryName to the actual Linq to Signum IQueryable that represents your query, but it also has other responsibilities:

  • Translating your IQueryable to a QueryDescription that can be used by the Client application (to set-up the SearchControl available filters).
  • At runtime, appends at the end of your query the filters chosen by the end-user as a Where Linq operation.
  • Transforms the results of the query in a QueryResult object for the Client application to use.

All these features allow you to easily implement IQueryServer on your service, as defined in IQueryServer.

Writing Queries

The same model used for other scenarios (custom business logic, or loading legacy data for instance) is used for writing the queries that will be used as views of your data in a SearchWindows/SearchControl Database.Query. Just an example:

dqm[typeof(BugDN)] = from b in Database.Query<BugDN>()
                     select new
                     {
                        Entity = b.ToLazy(),
                        b.Project,
                        b.Start,
                        b.End,
                        b.Hours,
                        b.Description,
                        NumComents = b.Comments.Count,
                        Discoverer = b.Discoverer.ToLazy(),
                        Fixer = b.Fixer.ToLazy(),
                        b.Status,
                     };

There are, however, certain considerations that are specific to Search:

  • DO NOT retrieve full entities on your queries, it has important performance penalties.
  • DO retrieve Lazy versions of your entities (you can use ToLazy() in a query as explained here), since they are rendered as cool links to the entities on the client side.
  • DO use anonymous types as the return type of your queries, they are more lightweight than entities, and the search engine doesn't allow simple primitive types. As expected, the order you declare the properties is the order they will appear in the UI.

Also, in order to make it über-simple to setup your queries 'metadata', and since anonymous types don't allow custom attributes, a micro-language for anonymous type properties will do the job, let's see how it works:

Image

Currently QueryDescription and QueryResult are deffined as:

    public class QueryDescription
    {
        public List<Column> Columns { get; set; }
    }

public class QueryResult { public List<Column> Columns { get; set; } public object[][] Data{get;set;} (...) }


Both need to provide information of the Columns in the view:

    public class Column
    {   
        public string Name { get; set; }
        public Type Type { get; set; }
        
        public bool Filterable { get; set; }
        public bool Visible { get; set; }

public string DisplayName { get; set; } public const string Entity = "Entity"; public bool IsEntity { get { return this.Name == Entity; } } (...) }

  • Name: The name of the column (name of the property in the anonymous type).
  • Type: The type of the column (type of the property in the anonymous type).
  • Filterable: Indicates that the column can be used as a filter for the end user. The default is true but you can turn it off on your anonymous type definition adding "_nf_" in your property name.
  • Visible: Indicates that the column will be seen in the set of results of the query. The default is true but you can turn it off on your anonymous type definition adding "_nv_" in your property name.

  • DisplayName: The name of the column that will be shown to the user (both in the data grid and in the filters panel) it's just the property name replacing "_" -> " " | "_p_" -> ".", and removing "_nf_" and "_nv_".

  • IsEntity: Transparent property that returns true if the Name of the column is "Entity". If so, then:
    • The column is not Visible
    • The column is not Filterable
    • The column becomes the 'owner' of the whole row, receiving double clicks to navigate or to select the entity as the return value.

So a (overly complicated) query like this:

dqm[typeof(CommentDN)] = from b in Database.Query<BugDN>()
                         from c in b.Comments
                         select new
                         {
                             Entity = b.ToLazy(),
                             Date_Found = c.Date,
                             _nf_ScreenShot = c.ScreenShot,
                             _nv_Text = c.Text,
                             c.Writer,
                             B_p_Status = b.Status,
                         };

Will be transformed to columns like this and sent to the client application:

NameTypeFilterableVisibleDisplayNameIsEntity
EntityLazyFalseFalseEntityTrue
Date_FoundDateTimeTrueTrueDate FoundFalse
_nf_ScreenShotLazyFalseTrueScreenShotFalse
_nv_TextStringTrueFalseTextFalse
WriterLazyTrueTrueWriterFalse
B_p_StatusStatusTrueTrueB.StatusFalse

Query Names

In order to identify a query from both the Client and the Server, it needs a name. Instead of using System.String as the type for queryNames we use Object because it allows some conventions:

  • Use typeof(YourEntityDN) for the default query of YourEntityDN: Presumably, but not necessarily, 'Entity' property is a Lazy in this query.
  • Whenever possible, use a shared enum or shared constant between client and server as queryNames, to avoid relying on error-prone string literals that can get out of sync when you make a misspelling.
    == The Client side ==
    The current implementation on the client side comes in two flavours, a SearchControl and a SearchWindow.

SearchControl

SearchControl is a control that integrates search capabilities in your own windows and controls. Here are the basic properties:

public partial class SearchControl
{
   public object QueryName {...} 
   public FreezableCollection<FilterOptions> FilterOptions {...}   

public Type EntityType{...} public object SelectedItem {...} public object[] SelectedItems {...} public bool MultiSelection {...} public FilterMode Mode {...} public bool SearchOnLoad {...}

public bool View {...} public bool Create {...} public bool ViewOnCreate {...}

public event Func<object> Creating; public event Action<object> Viewing; public event Action DoubleClick; (...) }

  • QueryName: Name of the query that the control will use when using IQueryServer operations.
  • FilterOptions: List of initial (and definitive) custom Filters that will appear in the panel. Better explained in Navigator Find and in the example below.

  • EntityType: The clean type of the Entity column (lazyness appart), if any. Do not confuse with the queryName, even if they usually match.
  • SelectedItem: The value of the Entity (hidden) column of the selected row.
  • SelectedItems: The values of the cells in the Entity (hidden) column of the selected rows, if MultiSelections is true.
  • MultiSelection: Enables/Disables allowing multiple selection on elements in the result grid.
  • Mode: FilterMode enumeration that defines where and how the filter panel will be shown, as defined in Navigator Find.
  • SearchOnLoad: When true, results are retrieved automatically when the control is loaded.

And properties and Events we are used to:

  • View: Enables the View button (and viewing when double-clicking a row).
  • Create: Enables the Create button.
  • ViewOnCreate: Enables the Create button.

  • Creating: Thrown when the user clicks the green cross button. By default Creates the entity of type EntityType and filters that match with properties and are used for initialization. More info in Constructor.
  • Viewing: Thrown when the user clicks the blue arrow icon, or when double clicking in arrow (see below). By default executes Navigator.View over the Entity in the selected row.
  • DoubleClick: Thrown when the user does a double click in the selected row. By default it just does Viewing.

DoubleClick is used by SearchWindow for closing the window when Buttons == OkCancel

Example

Let's integrate a SearchControl in the Project user interface to show all its' bugs (even if they are not part of the entity itself):

<UserControl x:Class="Bugs.Windows.Controls.Project"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:m="clr-namespace:Signum.Windows;assembly=Signum.Windows"
    xmlns:d="clr-namespace:Bugs.Entities;assembly=Bugs.Entities"
    m:Common.TypeContext="d:ProjectDN"
    MinWidth="300">
    <StackPanel>
        <m:ValueLine m:Common.Route="Name"/>
        <m:ValueLine m:Common.Route="IsInternal" LabelText="Is Internal"/>
        <m:SearchControl QueryName="{x:Type d:BugDN}" x:Name="sc">
            <m:SearchControl.FilterOptions>
                <m:FilterOptions ColumnName="Project" Operation="EqualTo" Frozen="True"  Value="{Binding DataContext, ElementName=sc}"/>
            </m:SearchControl.FilterOptions>
        </m:SearchControl>
    </StackPanel>
</UserControl>

What we do here is create a SeachControl, and use typeof(BugDN) as queryName. Since we don't want all the entities in the query, only the one that belongs to the project, we create a FilterOptions object in the FilterOptions collection and set the right properties:

  • ColumnName: Since we don't have the QueryDescription yet, we can't provide the Column itself. Set ColumnName and the right Column will be found at runtime.
  • Operatoin: A compatible member of the FilterOperation enum.
  • Value: RealValue is used internally because a Lazy is needed. Use Value to let the runtime deal with laziness transparently.

A plain Binding (no source) can't be used because FilterOptions has no DataContext. It has, however, inheritance context, so a Binding that sets ElementName explicitly will work.

Here is the end result:

Image

SearchWindow

SearchWindow is just a container Window around a SearchControl.

As EntityWindows, SearchWindows are not meant to be called directly from your code. Use Navitagor.Find instead!. This way you save code and you are free to override SearchWindow in a later stage.

If for some reason you need to call SearchWindow directly, here is the basic interface:

public partial class SearchWindow
{
   public object QueryName
   public SearchButtons Buttons
   public FreezableCollection<FilterOptions> FilterOptions
   public object Result
   public bool MultiSelection
   public FilterMode Mode
   public bool SearchOnLoad
}

public enum SearchButtons { OkCancel, Close }

Many of them sound familiar. They are just binded to SearchControl properties:

  • QueryName
  • FilterOptions
  • MultiSelection
  • Mode
  • SearchOnLoad

There are two new ones, however:

  • Buttons: The SearchButton enumeration allows you to choose what buttons will be visible in the bottom margin of the window:
    • OkCancel: Errand mode. Often used as part of a previous task, I.E: An EntityControl pressing Find. When you double click in a row the entity is chosen and the windows is closed.
    • Close: Just a walk mode. Often is the start of some user interaction (I.E: An item in the main menu). When you double click in a row the entity is viewed.

  • Result: When using OkCancel, (Errand Mode), this property is filled with the Entity in the selected row (usually a Lazy object) or, when MultiSelection is enabled, and object[] with the Entities in the selected rows.

I won't give an usage example here because you should use Navitagor.Find instead. If you have good reasons to instantiate SearchWindow by yourself take a look at Navigator implementation for an example.
Creative Commons License Signum Framework Site by Signum Software is licensed under a Creative Commons Attribution 3.0 License.
Powered by ScrewTurn Wiki version 3.0.5.600.