Ninja entity filtering in LightSpeed

Sometimes entities come with a ‘natural’ filter — a filter that should be applied by default to all queries, because the database contains stuff that users shouldn’t normally see. The classic example, which is built into the LightSpeed framework, is soft deletion: entities can be kept in the database but marked as logically deleted, and ‘deleted’ entities shouldn’t be returned from queries.

But there are other common use cases for this which aren’t build into LightSpeed, including liveness, currency and security filters. For example:

  • In a membership system, you might want queries to consider only active members (liveness).
  • In a system where data has a time range during which it is valid, you might want queries to consider only data that are valid at a particular time (currency).
  • In a case management system, users should be able to see only cases that they are working on (security).

In LightSpeed 4.0 RTM, you had to be sure to apply these filters to every query, either using your awesome developer brain power (which wouldn’t help when a slack-jawed maintenance programmer took over the code) or by encapsulating queries within a fortress-like repository (which was boring). Even then there were limits to this, for example if an entity which was current or permitted had children some of whom might not be current or permitted.

The current LightSpeed nightly builds offer another approach: interception-based query filters, which allow you to add filter criteria declaratively at the entity level.

Declaring how an entity type should be filtered

To declare that an entity type should be automatically filtered, just apply the new QueryFilterAttribute. QueryFilterAttribute takes a type, which must implement the IQueryFilter interface:

[QueryFilter(typeof(IsActiveFilter))]
partial class Member { ... }
 
public class IsActiveFilter : IQueryFilter { ... }

Filtering by interface

But in models where implicit filters make sense, it’s likely that many entities in the model will need similar filters. For example, if several entity types have time ranges during which they’re valid, you’ll probably want to apply the same currency filter to all of them. To support this, LightSpeed allows you to specify QueryFilterAttribute on an interface, and applies the filter to all types that implement that interface. Here’s an example:

[QueryFilter(typeof(IsCurrentFilter))]
public interface IValidFromTo {
  DateTime ValidFrom { get; }
  DateTime ValidTo { get; }
}
 
// Both these entity types inherit IsCurrentFilter from the interface
partial class Product : IValidFromTo { ... }
partial class Sku : IValidFromTo { ... }

With this idiom the interface becomes a powerful declarative tool for specifying query behaviour, and the query filter implementation can assume the properties in the interface are available for querying on.

Implementing a query filter

Now we know how to specify an implicit filter on an entity, either directly or indirectly through an interface, how do we turn that into an actual query? Here’s the IQueryFilter interface that the query filter type must implement:

public interface IQueryFilter
{
  QueryExpression Filter { get; }
}

Let’s implement the IsActiveFilter mentioned in the Member example above. This is just a matter of returning a LightSpeed QueryExpression object that consults the appropriate entity property. A nice simple case is if the database contains an IsActive flag:

public class IsActiveFilter : IQueryFilter
{
  public QueryExpression Filter {
    get { return Entity.Attribute("IsActive") == true; }
  }
}

If you didn’t explicitly model IsActive, but wanted to implicitly archive all members who hadn’t logged in for, say, 180 days, your filter would look a bit more complex, but it would still be a familiar query expression object:

public class IsActiveFilter : IQueryFilter
{
  public QueryExpression Filter {
    get {
      DateTime cutoff = DateTime.UtcNow.AddDays(-180);
      return Entity.Attribute("LastLogin") < cutoff;
    }
  }
}

Injecting values into implicit filters

By default, when LightSpeed runs a query on an entity with an implicit filter, it instantiates the IQueryFilter object using its default constructor. This is fine for the IsActive case where there’s nothing to parameterise, but it may be more problematic for the security scenario, where the query filter will depend on the logged-in user. In most cases, this will be available through some global mechanism — in a Web application, for example, the filter could use the HttpContext.Current.User — but sometimes it will be useful to inject data into a filter or otherwise control its behaviour on a per-instance basis.

To do this, you need to use an Interceptor and override the CreateQueryFilter method. This allows you full control over the creation and initialisation of the IQueryFilter object. In practice however we don’t expect this to be widely used so I won’t cover it in detail here — suffice it to say that like most other Interceptor methods we pass in a default callback that you can use to get the default behaviour, but you can choose to bypass this callback and create the IQueryFilter yourself, or post-process the IQueryFilter returned by the callback, or whatever as you see fit.

Turning off filtering

Occasionally you will want to turn off an implicit filter, for example in an admin interface where you want to be able to work with inactive users or have permission to view all cases. To do this, just return null from your IQueryFilter.Filter implementation. You’ll need to implement some way of signalling to your IQueryFilter when it should return null — currently you can do this using a global flag or an interceptor, though we welcome feedback on other use cases.

Try it out!

Implicit entity filtering is available in the current nightly builds of LightSpeed. You can get the free Express Edition from the Downloads page, and the full Professional Edition from the store. Happy coding!

Tagged as LightSpeed

2 Responses to “Ninja entity filtering in LightSpeed”

  • Why not make the filter strongly typed? IQueryFilter could be generic and have a method that takes and returns an IQueryable the the strongly typed query is built on.

  • Because IQueryFilter applies to to ‘implicit’ queries such as eager loads as well as explicit queries, and those implicit queries are built using query objects rather than LINQ. (There’s no benefit to coming all the way back up to the LINQ level to compose those implicit queries, because they’re purely internal.) It’s possible we could build an adapter for building strongly typed query filters using LINQ, though; thanks for the suggestion!

  • Leave a Reply

Archives

Join our mailer

You should join our newsletter! Sent monthly:

Back to Top