Download LightSpeed User Guide

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Extensible Storage Engine wikipedia , lookup

Open Database Connectivity wikipedia , lookup

Microsoft SQL Server wikipedia , lookup

Database wikipedia , lookup

Microsoft Jet Database Engine wikipedia , lookup

PL/SQL wikipedia , lookup

Entity–attribute–value model wikipedia , lookup

Clusterpoint wikipedia , lookup

Relational model wikipedia , lookup

Versant Object Database wikipedia , lookup

Database model wikipedia , lookup

Transcript
Mindscape LightSpeed User Guide
1
Contents
Introduction........................................................................... 15
What is LightSpeed?............................................................................................ 16
New to LightSpeed? ............................................................................................ 17
Old Hand? ........................................................................................................... 18
Key Features........................................................................................................ 19
About This Book .................................................................................................. 21
Creating Domain Models ....................................................... 22
Objects and Databases........................................................................................ 23
LightSpeed and Object-Relational Mapping ............................................................... 23
Creating Models with the Visual Designer .......................................................... 25
Creating Entities in the Designer ................................................................................ 26
Using Entity Classes .................................................................................................... 30
Creating Database Tables from Entities ...................................................................... 30
Creating Entities from Database Tables ...................................................................... 33
Creating Associations Between Entities ...................................................................... 35
Many-to-Many Associations ....................................................................................... 37
XML Documentation ................................................................................................... 39
Creating Models in Code ..................................................................................... 40
Creating Entity Classes ................................................................................................ 40
Creating Associations Between Entities ...................................................................... 42
Many-to-Many Associations ....................................................................................... 45
Special Considerations for Visual Basic ....................................................................... 47
Creating Code Models from Database Tables ............................................................. 48
Validation ............................................................................................................ 49
Specifying Validation Criteria ...................................................................................... 49
Advanced Validation Options ..................................................................................... 50
Automatic Validations................................................................................................. 52
Declarative Validation in Code .................................................................................... 52
Overriding OnValidate ................................................................................................ 53
Validation Considerations ........................................................................................... 54
Localising Validation Messages ................................................................................... 54
Working with Entities.......................................................................................... 56
Mindscape LightSpeed User Guide
2
The Unit of Work......................................................................................................... 56
The LightSpeedContext Object ................................................................................... 57
LightSpeed Configuration Basics ......................................................................... 58
LightSpeed Configuration Section ............................................................................... 58
Loading a LightSpeedContext from Configuration ...................................................... 58
Specifying the Data Provider....................................................................................... 59
Specifying the Connection String ................................................................................ 60
Other Configuration Options ...................................................................................... 60
Querying the Database Using LINQ .................................................................... 62
Creating a Strong-Typed Unit of Work........................................................................ 62
Writing LINQ Queries Using C# Syntax........................................................................ 63
Writing LINQ Queries Using the Standard Query Operators ...................................... 63
Writing LINQ Queries Against Hand-Coded Entities ................................................... 64
Common LINQ Techniques ......................................................................................... 65
LINQ Expressions......................................................................................................... 67
Querying the Database Using Query Objects ..................................................... 68
Creating a Unit of Work .............................................................................................. 68
Query Expressions....................................................................................................... 68
Sorting and Paging ...................................................................................................... 69
Query Objects ............................................................................................................. 70
Single Entity Queries ................................................................................................... 70
Count Queries ............................................................................................................. 71
Choosing Between LINQ and Query Objects .............................................................. 71
Creating, Modifying and Deleting Entities .......................................................... 72
How Changes are Saved .............................................................................................. 72
Adding a New Entity.................................................................................................... 72
Updating an Existing Entity ......................................................................................... 73
Deleting an Existing Entity .......................................................................................... 74
Saving the Unit of Work .............................................................................................. 74
Transactions ........................................................................................................ 76
Using TransactionScope .............................................................................................. 76
Using ADO.NET Transactions ...................................................................................... 76
Automatic Transactions .............................................................................................. 77
Controlling the Database Mapping......................................... 78
Understanding the Default Mapping .................................................................. 79
Entity Classes Map to Tables ...................................................................................... 79
Entity Id Maps to the Id Column ................................................................................. 79
Fields Map to Columns ............................................................................................... 79
Mindscape LightSpeed User Guide
3
Associations Map to Foreign Key Columns ................................................................. 80
Id Values Come From KeyTable .................................................................................. 80
Default Mapping Example........................................................................................... 80
Overriding the Default Mapping ......................................................................... 82
Mapping Individual Tables and Columns .................................................................... 82
Mapping Individual Tables and Columns in the Designer ........................................... 82
Mapping Individual Tables and Columns in Code ....................................................... 83
Defining Your Own Mapping Convention ................................................................... 83
Reserved Words .......................................................................................................... 85
Overriding Persistence Behaviour....................................................................... 86
Excluding a Field From Persistence ............................................................................. 86
Excluding a Field From Being Saved ............................................................................ 86
Examples ..................................................................................................................... 87
Identity Generation............................................................................................. 88
Identity Methods in LightSpeed .................................................................................. 88
Setting the Identity Method Globally ......................................................................... 88
Overriding the Identity Method on a Per-Entity Basis ................................................ 89
KeyTable Identity Generation ..................................................................................... 89
Sequence and MultiSequence Identity Generation .................................................... 90
Guid and GuidComb Identity Generation ................................................................... 91
IdentityColumn Identity Generation ........................................................................... 92
Identity Generation Options ....................................................................................... 92
How Block Allocation Methods Work ......................................................................... 92
Working with Database Views ............................................................................ 94
Loading Entities Through a View................................................................................. 94
Creating an Entity Class to Map a View ...................................................................... 95
Invoking Stored Procedures ................................................................................ 97
Invoking a Stored Procedure Using LINQ .................................................................... 97
Invoking a Stored Procedure Using Query Objects ..................................................... 98
Database Considerations for Stored Procedures ........................................................ 99
Additional Support for Stored Procedures ................................................................. 99
Building Applications with LightSpeed ................................. 100
Configuration .................................................................................................... 101
How to Configure LightSpeed ................................................................................... 101
Loading the Configuration ........................................................................................ 102
Setting Up a LightSpeedContext in Code .................................................................. 102
Context Per Application, Not Context Per Request .................................................. 102
Terminology .............................................................................................................. 103
Mindscape LightSpeed User Guide
4
Configuration Settings .............................................................................................. 104
Localizing LightSpeed Messages ....................................................................... 105
Localizing Property Names........................................................................................ 105
Customising How LightSpeed Connects to the Database ................................. 107
Implementing a Custom Connection Strategy .......................................................... 107
Using a Custom Connection Strategy........................................................................ 109
Building Web Applications ................................................... 111
Building ASP.NET Web Forms Applications ...................................................... 112
Unit of Work Scoping ................................................................................................ 112
Validation .................................................................................................................. 113
Data Binding using EntityDataBinder ........................................................................ 115
Samples ..................................................................................................................... 117
Building ASP.NET MVC Applications ................................................................. 118
Unit of Work Scoping ................................................................................................ 118
Model Binding for LightSpeed Entities...................................................................... 119
Validation .................................................................................................................. 121
Samples ..................................................................................................................... 123
Running LightSpeed in Medium Trust............................................................... 124
ASP.NET Dynamic Data ..................................................................................... 125
Handling Associations ............................................................................................... 126
Validation .................................................................................................................. 126
Building WPF and Windows Forms Applications .................. 128
Unit Of Work Scoping........................................................................................ 129
Build with Design Patterns ........................................................................................ 129
Working with Dialogs ................................................................................................ 129
Concurrency .............................................................................................................. 130
Using Refresh Buttons .............................................................................................. 131
Long Running Units of Work ..................................................................................... 131
Entity Support for Rich Client Frameworks....................................................... 133
ClickOnce Deployment ...................................................................................... 134
Building Silverlight Applications ........................................... 135
Using WCF RIA Services with LightSpeed .......................................................... 136
Domain Service Basics............................................................................................... 137
Mindscape LightSpeed User Guide
5
Surfacing the Domain Entities................................................................................... 137
Generating and Using the Silverlight Data Source .................................................... 138
Building Distributed Applications ......................................... 140
Distributed Entity Programming ....................................................................... 141
The Distributed Unit of Work.................................................................................... 141
The DistributedUnitOfWorkService .......................................................................... 142
Manually Hosting the Service ................................................................................... 143
Supported Bindings ................................................................................................... 145
Configuring Your Client for a DistributedUnitOfWork .............................................. 145
Executing Operations at the Client ........................................................................... 147
Data Contracts .......................................................................................................... 147
Building WCF Services using Entities ........................................................................ 148
Samples ..................................................................................................................... 148
Building WCF Services using Data Transfer Objects ......................................... 150
Providing Custom Mapping for IUnitOfWork.Import ............................................... 152
Compatibility with LightSpeed 3 Generated DTOs ................................................... 153
Samples ..................................................................................................................... 154
Testing and Debugging ......................................................... 155
Unit Testing ....................................................................................................... 156
Using a Real Database ............................................................................................... 156
Using Fakes ............................................................................................................... 158
Using Mocks .............................................................................................................. 159
Logging .............................................................................................................. 161
Built-In Loggers ......................................................................................................... 161
Enabling Logging ....................................................................................................... 161
Disabling Logging ...................................................................................................... 162
Building a Custom Logger ......................................................................................... 162
Profiling ............................................................................................................. 164
Query Patterns .......................................................................................................... 164
Timing Queries .......................................................................................................... 164
Using a Custom Logger for Profiling.......................................................................... 164
Domain Modelling Techniques ............................................. 167
Inheritance ........................................................................................................ 168
Discriminators ........................................................................................................... 168
Single Table Inheritance ............................................................................................ 169
Class Table Inheritance ............................................................................................. 170
Mindscape LightSpeed User Guide
6
Implementing Common Services in a Base Class ...................................................... 171
Which Should I Choose? ........................................................................................... 171
Value Objects .................................................................................................... 173
Defining a Value Object Type in the Designer .......................................................... 173
Creating a Value Object Member in the Designer .................................................... 173
Value Objects in Database-First Development ......................................................... 174
Defining a Value Object Type in Code ....................................................................... 174
Creating a Value Object Member in Code ................................................................ 175
Setting Value Object Properties ................................................................................ 175
Value Object Database Mappings ............................................................................. 176
Reference Data and Lookups ............................................................................ 179
Working with Models in the Visual Designer ........................ 180
LightSpeed Model Explorer ............................................................................... 181
Workflows for Rapid Application Development ............................................... 182
Driving the Database from the Domain Model ......................................................... 182
Modelling Using Your Database Tools ...................................................................... 182
Which Should I Choose? ........................................................................................... 183
Reorganising Model Elements in a Database First Workflow ................................... 184
Configuring Database Synchronisation ..................................................................... 185
Enums and Other User-Defined Types.............................................................. 186
Adding an Enum Type to the Designer ..................................................................... 186
Adding a Database-Defined Type to the Designer .................................................... 186
Adding a User-Defined Type with Custom Mapping ................................................. 187
Using a User-Defined Type ....................................................................................... 188
Refactoring in the Designer .............................................................................. 189
Custom Views.................................................................................................... 190
Filtering ..................................................................................................................... 190
QuickViews................................................................................................................ 191
Linked Models ................................................................................................... 193
Code Generation ....................................................................................................... 193
Creating Associations Across Model File Boundaries ............................................... 193
Creating and Maintaining Entity Links ...................................................................... 194
Changing the Designer Defaults........................................................................ 195
Customising the Generated Code ..................................................................... 196
Using Your Own Code Generation Templates .......................................................... 196
Extending the Designer Metamodel ......................................................................... 198
Mindscape LightSpeed User Guide
7
Using T4 Templates with the LightSpeed Designer................................................... 200
Many-to-Many Associations ............................................................................. 203
Using an Auto Through Entity ................................................................................... 203
Using an Explicit Through Entity ............................................................................... 204
How Do Auto and Explicit Through Entities Differ? .................................................. 205
Designer Shortcuts and Tips ............................................................................. 206
Speeding Up Property Entry ..................................................................................... 206
Custom Attributes ..................................................................................................... 206
Grabbing an Image of Your Model ............................................................................ 207
Using Reminder Notes .............................................................................................. 207
Use Get Started for Configuration File Entries .......................................................... 207
Rearranging Properties ............................................................................................. 208
Assigning Keyboard Shortcuts to LightSpeed Commands ........................................ 208
Using a Custom Base Class for Your Entities ............................................................. 208
Changing Property or Entity Names When Other Code Already Uses Them ............ 209
Writing Custom Property Getter or Setter Code ...................................................... 209
Setting Validation Options Which Aren’t In the Properties Window ........................ 209
Show Data Command ............................................................................................... 210
Advanced Querying Techniques ........................................... 211
Full Text Search ................................................................................................. 212
Indexing Data ............................................................................................................ 212
Building the Search Index ......................................................................................... 213
Performing Searches ................................................................................................. 214
Advanced Operator Support ..................................................................................... 215
Further Search Reading............................................................................................. 215
Invoking SQL Functions ..................................................................................... 216
Mapping SQL Functions in LINQ................................................................................ 216
Mapping to a Custom Function................................................................................. 218
Mapping Argument Order ........................................................................................ 218
Mapping Member Functions .................................................................................... 218
Invoking SQL Functions Using Query Objects ........................................................... 219
Compiled Queries.............................................................................................. 220
Compiling a LINQ based query .................................................................................. 220
Compiling a query object based query ..................................................................... 220
Executing a compiled query ...................................................................................... 221
Parameterising compiled queries ............................................................................. 221
Exploring the Query Object............................................................................... 223
Basic Querying Operations ....................................................................................... 223
Mindscape LightSpeed User Guide
8
Controlling Entity Load ............................................................................................. 223
Projections ................................................................................................................ 223
Views ......................................................................................................................... 225
Controlling Aliasing ................................................................................................... 226
Joins .......................................................................................................................... 226
Grouping ................................................................................................................... 229
Subexpressions ......................................................................................................... 231
Unions and Intersections .......................................................................................... 232
Hints .......................................................................................................................... 232
Using direct SQL statements ..................................................................................... 232
Other Querying Properties ....................................................................................... 232
Subexpressions ................................................................................................. 233
Working with Metadata ....................................................... 235
The LightSpeed Metamodel .............................................................................. 236
Referencing the Metadata Assembly ........................................................................ 236
Getting Class Information ................................................................................. 237
Getting Class Settings ............................................................................................... 237
Getting Field and Association Information ............................................................... 238
Fields and Properties ................................................................................................ 238
Getting Validation Information ................................................................................. 239
Getting and Setting Fields through Metadata .................................................. 240
Getting Field Values .................................................................................................. 240
Getting Association Values ....................................................................................... 241
Setting Field Values ................................................................................................... 241
Performance and Tuning ...................................................... 242
Controlling How Entities Load........................................................................... 243
Eager Loading............................................................................................................ 244
Fine Grained Control Using Named Aggregates ....................................................... 245
Controlling How Entity Data Loads ................................................................... 248
Understanding Named Aggregates ................................................................... 251
Visualising Aggregates .............................................................................................. 251
Bulk Updates and Deletes ................................................................................. 252
Bulk Updates ............................................................................................................. 252
Bulk Deletes .............................................................................................................. 253
Considerations for Bulk Operations .......................................................................... 254
Mindscape LightSpeed User Guide
9
Batching ............................................................................................................ 255
Customising the Batch Size ....................................................................................... 255
Identity Columns and Batching ................................................................................. 256
Databases and Batching ............................................................................................ 256
Caching .............................................................................................................. 257
Enabling Second Level Caching ................................................................................. 257
Configuring the Second Level Cache ......................................................................... 258
Understanding the Second Level Cache.................................................................... 259
Collection Caching..................................................................................................... 260
Caching Considerations............................................................................................. 260
Working with the Cache Manually ............................................................................ 261
Database Hints .................................................................................................. 262
Index Hints ................................................................................................................ 262
Table Hints ................................................................................................................ 263
Database Hints Using Query Objects ........................................................................ 263
Measuring Performance ................................................................................... 264
Intercepting Queries ......................................................................................... 265
Intercepting ADO.NET interactions ........................................................................... 265
Intercepting LightSpeed queries ............................................................................... 266
Declaring how an entity type should be filtered....................................................... 266
Filtering by interface ................................................................................................. 267
Implementing a query filter ...................................................................................... 267
Injecting values into implicit filters ........................................................................... 269
Turning off filtering ................................................................................................... 269
Implementing Storage Policies with LightSpeed ................... 270
Entity Tracking .................................................................................................. 271
Storing Creation and Update Times .......................................................................... 271
Storing Creating and Updating Users ........................................................................ 271
Soft Deletion ..................................................................................................... 273
Storing Which User Deleted an Entity ...................................................................... 273
Loading Soft Deleted Entities .................................................................................... 274
Timestamps for Entity Tracking and Soft Deletion ........................................... 275
Built-In Timestamp Strategies ................................................................................... 275
Using a Custom Timestamp Strategy ........................................................................ 275
Database-side Timestamping .................................................................................... 277
User Ids for Entity Tracking and Soft Deletion .................................................. 278
Mindscape LightSpeed User Guide
10
Built-In User Identification Strategies ....................................................................... 278
Using a Custom Identification Strategy .................................................................... 278
Concurrent Editing ............................................................................................ 280
Guidance for Optimistic Concurrency Checking ....................................................... 280
Implementing Policies in Hand-Coded Entities ................................................. 282
Column Names for Policy Fields................................................................................ 282
Change Tracking ................................................................................................ 283
Saving Only Changed Fields ...................................................................................... 283
Working with Legacy Databases........................................... 284
Invoking Stored Procedures .............................................................................. 285
CRUD Stored Procedures .................................................................................. 286
CRUD Procedure Conventions .................................................................................. 287
CRUD Procedure Limitations ..................................................................................... 287
Using Natural Keys ............................................................................................ 289
Implementing the GeneratedId Method .................................................................. 289
Natural Keys and Column Mappings ......................................................................... 290
When the Natural Key is Assigned ............................................................................ 291
Natural Keys and the IdentityColumn Identity Method ........................................... 291
Using Composite Keys ....................................................................................... 292
Composite Key Types ................................................................................................ 292
Composite Keys in the Designer ............................................................................... 292
Composite Keys in Hand-Coded Entities ................................................................... 293
Assigning Composite Keys......................................................................................... 294
Composite Foreign Keys............................................................................................ 294
Foreign Keys That Are Part of a Composite Key ........................................................ 296
Composite Foreign Keys That Overlap the Primary Key ........................................... 296
Many-to-Many Associations Represented As Composite Keys ................................ 296
Mapping Database Types to Domain Types...................................................... 298
Custom Wrappers ..................................................................................................... 298
Field Converters ........................................................................................................ 299
Field Converters for Hand-Coded Entities ................................................................ 302
Field Converters and Querying ................................................................................. 303
Querying Considerations for Field Converter Design................................................ 303
Working with Database Providers ........................................ 305
DB2 .................................................................................................................... 306
Mindscape LightSpeed User Guide
11
LINQ Support............................................................................................................. 306
Tools Support ............................................................................................................ 306
MySQL ............................................................................................................... 307
Designer Support ...................................................................................................... 307
Creating Tables from the Designer ........................................................................... 307
Oracle ................................................................................................................ 308
Using Oracle Stored Procedures with LightSpeed .................................................... 308
ODP.NET Versioning Considerations ......................................................................... 308
Designer Support ...................................................................................................... 309
PostgreSQL ........................................................................................................ 310
Designer Support ...................................................................................................... 310
SimpleDB ........................................................................................................... 311
Connection String Format ......................................................................................... 311
Limitations ................................................................................................................ 311
Eventual Consistency and Consistent Reads ............................................................. 312
Data Storage ............................................................................................................. 312
Tools Support ............................................................................................................ 313
LightSpeed 5 Changes ............................................................................................... 313
SQLite ................................................................................................................ 314
SQL Server ......................................................................................................... 315
SQL Server 2008 ........................................................................................................ 315
SQL Server 2012 ........................................................................................................ 315
SQL Server 2000 Limitations ..................................................................................... 316
SQL Server 2000 Designer Support ........................................................................... 316
SQL Server Compact.......................................................................................... 317
Versions .................................................................................................................... 317
Paging and Ordering ................................................................................................. 317
LINQ Support............................................................................................................. 317
Tools Support ............................................................................................................ 317
VistaDB 4 ........................................................................................................... 318
Paging and Ordering ................................................................................................. 318
LINQ Support............................................................................................................. 318
Tools Support ............................................................................................................ 318
Low Level Database Access ............................................................................... 319
Creating ADO.NET Objects with LightSpeed ............................................................. 319
Using ADO.NET Objects with LightSpeed .................................................................. 319
Mindscape LightSpeed User Guide
12
Windows Azure Table Service ........................................................................... 320
Database Migrations ............................................................ 321
Creating Migrations .......................................................................................... 322
Writing Migration Code ............................................................................................ 322
Creating Migrations from a Model ........................................................................... 323
Create Migrations from Scratch ................................................................................ 324
Data Types ................................................................................................................ 324
Identity Generation................................................................................................... 324
Injecting Custom User Code...................................................................................... 324
Running Migrations ........................................................................................... 325
Running Migrations from Visual Studio .................................................................... 325
Running Migrations from the Command Line........................................................... 326
Running Migrations from Your Application .............................................................. 326
Creating SQL Scripts from Migrations ............................................................... 328
Choosing the Generation Settings ............................................................................ 328
Database Support ............................................................................................. 330
Appendices .......................................................................... 331
Configuration Reference ................................................................................... 332
Tips, Tricks and Troubleshooting ...................................................................... 335
Use a Short-Running Unit of Work............................................................................ 335
Use a Single LightSpeedContext................................................................................ 335
Use Configuration Files Where Possible ................................................................... 335
Partial Classes ........................................................................................................... 335
Use Eager Loading and Named Aggregates to Tune Loading Performance .............. 336
Avoid Needless Change Tracking .............................................................................. 336
Measure the Performance Impact of Changing UpdateBatchSize ........................... 336
Consider Changing IdentityBlockSize ........................................................................ 337
Keep Sequences in Sync with IdentityBlockSize ....................................................... 337
ObjectDisposedException in SystemTransactionCompletedEvent ........................... 337
Command Line Tools Reference ....................................................................... 338
lsgen – Create Entity Classes from Database ............................................................ 338
lsmigrate – Apply Migrations .................................................................................... 339
Database Providers in Command Line Tools ............................................................. 339
Languages in Command Line Tools ........................................................................... 340
LINQ Support Limitations .................................................................................. 341
Unsupported LINQ Operators ................................................................................... 341
Mindscape LightSpeed User Guide
13
Comparisons to an Associated Entity ....................................................................... 342
CLR Methods in a LINQ Query................................................................................... 342
Joining, Grouping and Combining ............................................................................. 343
Set operations ........................................................................................................... 343
Database Specific Limitations ................................................................................... 343
Further Reading ................................................................................................ 344
Index ................................................................................... 345
Mindscape LightSpeed User Guide
14
Introduction
Mindscape LightSpeed User Guide
15
What is LightSpeed?
At its worst business logic can be very complex. Rules and logic describe many different
cases and slants of behavior, and it’s this complexity that objects were designed to work
with. A Domain Model creates a web of interconnected objects, where each object
represents some meaningful individual, whether as large as a corporation or as small as
a single line on an order form.
Martin Fowler
LightSpeed is a framework that helps you to rapidly build persistent domain models for
.NET applications. LightSpeed helps you to define classes and their relationships, and
to conveniently load and save entities using a database, avoiding the need to write
large amounts of boilerplate data access code. Using LightSpeed, you work at the level
of business objects, not at the level of database rows.
Mindscape LightSpeed User Guide
16
New to LightSpeed?
Your LightSpeed installation includes the book Getting Started with LightSpeed, which
walks you through the process of creating a domain model and using it in a simple
application. You can view this using the Mindscape > LightSpeed > Guides > Getting
Started link on the Start menu.
The installation also includes a number of samples which will help you understand how
to use specific features of LightSpeed and how to structure applications. You can
launch the samples using the Mindscape > LightSpeed > Samples link on the Start
menu.
We have also posted a number of screencasts on the Mindscape Web site, which
demonstrate the basics of LightSpeed and drill into a number of key features.
Mindscape LightSpeed User Guide
17
Old Hand?
If you’re already experienced with LightSpeed, you can find out what’s new and
different in this version of LightSpeed from the Mindscape > LightSpeed > Guides >
Upgrade Guide link on the Start menu.
Mindscape LightSpeed User Guide
18
Key Features
LightSpeed’s design philosophy is centered on the following guiding principles:





Convention over configuration.
Support idiomatic .NET domain models: validation, data binding, change
notification etc.
Highly usable API and low barrier to entry.
Encapsulate and encourage best practice patterns: session per request, Unit of
Work etc.
Small, lightweight and fast.
There isn’t room here to provide a full list of LightSpeed features, but among the key
features are:






Domain modelling. LightSpeed supports domain-driven design concepts such
as entities and value objects, the Unit of Work pattern and aggregates.
Visual model design. You can create models in a visual designer, making it
easy to see the relationships between entities and reducing the need for hand
coding. Models can be created from an existing database or from scratch using
a toolbox.
Rapid design iteration. The designer allows quick, non-destructive
synchronisation of the database and the model, meaning there are no speedbumps as you evolve your model.
LINQ. You can use the popular LINQ syntax and methods to perform
LightSpeed queries, gaining the benefits of code completion, compiler type
checking and so on. LightSpeed also provides a query object API for dynamic
queries and finer control.
Validation. You can specify validation rules at the entity or property level.
LightSpeed automatically checks validity before allowing an entity to be saved.
Each entity exposes an Errors collection which supports data binding for easy
presentation.
Eager and lazy loading. You can load an entity’s dependencies in the same
database query as the entity, avoiding the infamous N+1 problem. You can
also define your own eager load graphs for different situations.
Mindscape LightSpeed User Guide
19






UI framework support. LightSpeed implements several standard UI integration
interfaces for you, including IEditableObject, INotifyPropertyChanged and
IDataErrorInfo, making it easy to use entities in data-driven user interfaces.
Convention-based mapping. You can just create your classes without having
to spend extra effort mapping them to a database schema.
Safe, efficient data access. LightSpeed always uses parameters in database
statements, avoiding the risk of SQL injection vulnerabilities. Statements are
optimised and batched for efficiency.
Multiple databases. You can use LightSpeed with Microsoft SQL Server,
Oracle, MySQL, PostgreSQL, IBM DB2, SQLite, SQL Server Compact and VistaDB.
It also works with the Amazon SimpleDB and Microsoft SQL Azure cloud
databases.
Database migrations. For manageable and controllable upgrading and
downgrading of the database schema.
Distributed application support. You can ship an object graph over the wire,
work with it on a client and return the changes efficiently to the server without
needing to leave the LightSpeed API.
Mindscape LightSpeed User Guide
20
About This Book
This book contains conceptual documentation and guidance information for
LightSpeed. You should read it in conjunction with these other books:


Getting Started with LightSpeed, which walks you through the process of
creating a simple LightSpeed application
LightSpeed API Reference, which provides detailed documentation for all
LightSpeed classes and members
You can access these books from the Mindscape > LightSpeed folder on the Start menu.
Mindscape LightSpeed User Guide
21
Creating Domain Models
The domain model is a conceptual model of a ‘domain of interest’ or problem domain.
The domain model represents all the kinds of entities that are relevant to the system or
application you’re working on, and the relationships between them. This chapter
describes how to build and evolve domain models using LightSpeed.
Mindscape LightSpeed User Guide
22
Objects and Databases
When you analyse a business domain, you are creating a conceptual model of that
domain. You identify the entities in that domain, the state and behaviour of those
entities, and their relationships. However, at some point, that conceptual model has to
be translated into a concrete software implementation.
In fact, in almost all practical business applications, it has to be translated into (at least)
two concrete software implementations: one implementation in terms of programming
entities (objects), and one in terms of a relational database. This is where things start
getting tedious and potentially complex, because the object and relational worlds use
quite different representations.
At best, the code to query the database, load objects and save them again is laborious
and repetitive. More often, there are additional complications, such as multiple types
of objects with associations between them – for example, a Customer has a set of
Orders – or inheritance relationships – for example, we have identified AudioClip and
VideoClip as sharing a considerable amount of state and behaviour, and would like to
model this using inheritance. The extremely different representations of associations
and inheritance in the object and relational worlds makes it complicated as well as
laborious to write code that translates between the two representations.
This is where object-relational mapping comes in. An object-relational mapper, or
ORM, takes care of the mechanical details of translating between the worlds of
programmatic objects and relational data. The ORM figures out how to load and save
objects, using either explicit instructions such as an XML configuration file, or its own
heuristics, or a combination of the two. This lets you, the programmer, focus on writing
your business logic and application functionality against the domain model in its object
representation, without having to worry about the details of the relational
representation.
LightSpeed and Object-Relational Mapping
LightSpeed as an object-relational mapper leans strongly towards using its own
heuristics to figure out how to load and save data: that is, it works out how objects and
properties map to tables and columns without having to be told. This is known as
Mindscape LightSpeed User Guide
23
convention over configuration. The immediate practical benefit of this is that we don’t
need to write rules telling the ORM how to load and save objects. The impact is that it
requires us to keep our object design and our database design reasonably in sync.
However, even this has a higher-level benefit: it guides us towards a consistent data
design that reflects the business domain.
What does this mean in practical terms? It means that when we identify a domain
entity, we always have the same basic tasks to wire it up in LightSpeed:




Create a class representing the domain entity.
Declare properties representing the attributes of the new entity.
Create associations to other entities.
Create the underlying database schema.
Once we’ve defined our entity in this way, we can use LightSpeed to retrieve or persist
instances of this entity to and from the database. We can also go on to configure
behaviour (e.g. adding validation) and performance (e.g. caching or lazy-loading) as
required, and to extend the domain model by adding custom methods.
LightSpeed provides a visual designer for creating domain models and defining domain
entities. The designer makes it easy to produce both the object and relational
representations from a single master model, providing an extremely convenient
modelling workflow. You can also define entities purely in code.
For a step-by-step introduction to creating domain models and using them in
LightSpeed, see the Getting Started guide, which you can find on the Start menu.
Mindscape LightSpeed User Guide
24
Creating Models with the Visual Designer
The LightSpeed visual designer integrates into Visual Studio 2008, Visual Studio 2010
and Visual Studio 2012.
To create a model with the visual designer, add a LightSpeed Model item to your Visual
Studio project. The easiest way to do this is to right-click the project in Solution
Explorer and choose Add > New Item, then choose LightSpeed Model in the Add Item
dialog. You can find it in the Data tab as well as the main list.
The designer is initially blank (except for some links for users who want help getting
started). You can either create entities in the designer using the Toolbox, or create
entities from your existing database tables.
Mindscape LightSpeed User Guide
25
Creating Entities in the Designer
To create an entity in the designer, open the Visual Studio Toolbox and drag the Entity
from the Toolbox onto the design surface.
Initially, the new entity will be called Entity1. To change this, just type the new name
while the entity is selected.
The Visual Studio Properties window shows more options for configuring the entity:
Mindscape LightSpeed User Guide
26
For example, you can also edit the entity name through the Name box in the Properties
window.
Many of the settings in the Properties window are things you’ll only need to think
about as you start building up your model. For example, you’ll use the Persistence
options if you need to customise the way the entity is stored in the database. This
book covers the various options under the relevant chapters. You can also get an idea
of what each option does by looking at the description area at the bottom of the
Properties window.
One option that’s important for all models is the Identity Type option. Every entity in
LightSpeed has an Id, a unique identifier that allows LightSpeed to tell it apart from
other entities of the same type. The default identity type is Int32 – the .NET Int32 type,
equivalent to the C# int type. If you expect to create a huge number of entities, you’ll
probably want to change this to Int64 (C# long). Some users prefer GUID Ids to
numeric Ids, so you can also choose the Guid identity type. (The String identity type is
usually used only for natural keys in legacy databases: see Working with Legacy
Databases if you think you need string keys.)
Mindscape LightSpeed User Guide
27
To add properties to the entity, right-click it and choose Add New Entity Property.
A shortcut for this is to hit the Insert key when the entity (or an existing property of the
entity) is selected. This will add a new entity after the entity that is currently selected.
You can edit the name of the new property by typing:
Mindscape LightSpeed User Guide
28
The default data type for newly created properties is String. Use the Properties grid to
change this if necessary:
You can also enter the type in front of the property name, just like declaring a C# field:
When you save your model, LightSpeed generates .NET classes for each of the entities
you have defined. These classes are stored in a generated code file with the same
name as the model file and a .cs or .vb file extension. You can open this file in Visual
Studio to view the entity class code, but you cannot edit the file – if you do, your
changes will be overwritten next time LightSpeed regenerates the code. Always make
changes through the designer, never in the generated code file.
Mindscape LightSpeed User Guide
29
The generated entity classes are partial classes. This means you can extend them
through your own partial class files, for example to add domain methods. Again,
always do this through a partial class file, never by editing the generated code.
Using Entity Classes
As mentioned above, each entity in the designer is a .NET class. All entity classes
inherit from the LightSpeed Entity<TId> class, which in turn inherits from the Entity
base class. The Basic Operations chapter describes how to load, create, modify and
delete entities, and the Getting Started book (linked on the Start menu) and screencast
(linked from the designer surface and the Get Started command) provide walkthroughs.
The properties you specify on the designer become properties of the class. You can get
or set these properties using normal C# or Visual Basic syntax:
Customer customer = /* get a customer – see Basic Operations chapter */;
Console.WriteLine(customer.FirstName);
customer.MemberNo = 12345;
Creating Database Tables from Entities
As discussed above, a domain model is realised in at least two ways: an object model
and a relational database schema. The object model is automatically generated when
you save the visual model file. You can also have LightSpeed generate the database
schema for you.
To do this, you must first tell LightSpeed what kind of database you are using, and
provide the connection string to that database. To do this, click on the model
background and enter these settings into the Properties window.
Mindscape LightSpeed User Guide
30
(You only need to do this once – LightSpeed remembers the settings for future
updates.)
Now, if you right-click on the model background, you will see an Update Database
option:
Click on this and LightSpeed will compare your model to the database and display a list
of changes that need to be made to the database. (If the database doesn’t already
exist, LightSpeed may be able to create it for you. This depends on the database
provider. For providers where LightSpeed can’t create databases, you’ll need to use a
suitable database administration tool to create a blank database.)
Mindscape LightSpeed User Guide
31
You can use Update Database to keep your database schema in sync with your model
as your model evolves. Update Database does not regenerate tables each time, but
instead applies only the changes it detects between the model and the database
schema. Hence, it does not modify or delete existing data (except when it detects that,
for example, a column needs to be deleted because it is no longer in the model) so it is
safe to use with databases containing test data. It is not, however, a production tool!
If you want to capture the changes that Update Database detects, so that you can run
them against production environments in a controlled way, see the Database
Migrations chapter.
You can exclude an action proposed by Update Database by clearing the relevant
checkbox. However, because the difference between the model and database remains,
LightSpeed will continue to suggest the change until you reconcile the difference.
Update Database allows you to very rapidly iterate your model. Because it is
non-destructive it is easy to tweak the model incrementally until you are happy with it,
try out experimental changes, and so on. For more information about this, see
Workflows for Rapid Application Development in the chapter Working with Models in
the Visual Designer.
Mindscape LightSpeed User Guide
32
Creating Entities from Database Tables
The previous sections assumed that you were creating a new model from scratch, and
could begin from the domain model design. It may be that your database already
exists, and you want to create a model that corresponds to that existing database
schema.
To do this, open the Visual Studio Server Explorer, and expand the database you want
to model. (If the database isn’t already in Server Explorer, right-click Data Connections
and choose Add Connection to add it.) Select the tables you want to include in your
model, and drag them onto the design surface.
When you drag tables from a database, you don’t need to specify the entity name and
identity type, or the property names and data types, because these are already set up
in the database through the tables and columns. LightSpeed works them out from the
database schema.
You can make changes to an entity which has been dragged from Server Explorer just
as if you had created it using the Toolbox. For example, you can add new properties by
choosing Add New Entity Property or using the Insert key. Of course, this means your
entity class is now out of sync with your database, but you can use Update Database to
fix that. Update Database works out what changes need to be made and applies them
to the database for you.
Mindscape LightSpeed User Guide
33
(In this case, you don’t need to have entered a database provider or connection string
the way you did when you started from scratch. LightSpeed works them out from the
Server Explorer connection.)
If you want to keep the database as the master source for the model, you can use the
Update From Source command instead of Update Database.
Update From Source updates the model to be in sync with the database schema. For
example, if you have added a column to a table, Update From Source will add a
corresponding property to the entity. Note however that Update From Source only
Mindscape LightSpeed User Guide
34
looks at existing entities – this is because a database may contain a huge number of
tables not related to the task at hand, and you don’t want these cluttering up the
model.
Like Update Database, Update From Source is non-destructive. For example, if you’ve
applied validations or renamed a property (provided you have kept the database
column mapping), Update From Source will not overwrite your changes. Of course, if
you have changed something that takes the model out of sync with the database, such
as changing a property data type, then Update From Source will propose to change it
back.
As with Update Database, you can exclude an action proposed by Update From Source
by clearing the relevant checkbox, but the action will continue to appear until you
reconcile the difference.
Creating Associations Between Entities
LightSpeed supports three kinds of associations: one-to-many, one-to-one and
many-to-many.
To create a one-to-many association, select the One To Many Association connector in
the toolbox, and drag an arrow from the ‘one’ end to the ‘many’ end. For example, if a
Customer can have multiple Orders, drag the arrow from Customer to Order.
Mindscape LightSpeed User Guide
35
This creates a property at each end of the association. The ‘one’ end has a collection
property, representing the collection of associated ‘child’ entities. The ‘many’ end has
a backreference property, representing the ‘parent’ entity. In the example above,
Customer has an Orders collection property, and Order has a Customer backreference
property. LightSpeed guesses names for these properties based on the entity names.
You can edit these names by clicking on them in the diagram, or by selecting the arrow
and editing the Collection Name and Backreference Name options in the Properties
window.
In code, you work with these properties in the same way as with other collection and
object properties:
// Using a collection property
customer.Orders.Add(newOrder);
customer.Orders.Remove(cancelledOrder);
// Using an entity association
order.Customer = requestingCustomer;
A one-to-many association also results in a foreign key property. You can’t customise
the name or type of this property, because the name is always the backreference name
followed by “Id” – for example, CustomerId – and the type is always the identity type of
the parent entity. You may sometimes use the foreign key property in your code,
especially in serialisation scenarios, and you can customise its mapping to a database
column if required (see Controlling the Database Mapping).
Mindscape LightSpeed User Guide
36
A one-to-one association is added in much the same way as a one-to-many association,
except that it has a source and a target instead of a collection and a backreference.
When you use Update Database to create or update database tables, LightSpeed
creates a foreign key column in the appropriate table to represent each association.
If you are creating entities from database tables, then LightSpeed creates associations
for you based on foreign keys in the database. Consequently, when you drag a table
onto the designer, any columns that are foreign keys do not appear as properties –
instead, they appear implicitly as the foreign keys of the inferred associations.
Many-to-Many Associations
Many-to-many associations work in a slightly different way to one-to-many or
one-to-one associations, because they have to be stored in a different way at the
relational level. Instead of a simple foreign key, a many-to-many association requires a
whole table of foreign keys. This table is variously known as a join table, relationship
table or through table. Each entry in the through table represents a pair of associated
entities; and an entity can participate in multiple pairs.
A many-to-many association is modelled in LightSpeed using a through association, so
named because it goes ‘through’ an intermediate entity. The intermediate entity
corresponds to the through table and is known as the through entity.
To create a through association, select the Through Association connector in the
toolbox, and drag an arrow between the entities you want to associate. You must then
select the arrow and specify the through entity. The easiest way to do this is to enter a
name in the Auto Through Entity box: LightSpeed will create a minimal through entity
for you. (For more information about this and how to get finer control over the
through entity, see the chapter Working with Models in the Visual Designer.)
Mindscape LightSpeed User Guide
37
A through association results in two collection properties, one at each end. LightSpeed
guesses names for these based on the names of the entities. When you use a through
association from code, you’ll usually uses these two collections, in just the same way as
normal collections – you can iterate over them, add items to them, remove items from
them, and so on.
Using a through association
// iterating over the through association
foreach (Tag tag in contribution.Tags)
Console.WriteLine(contribution.Title + " is tagged " + tag.Value)
// modifying the through association collection
contribution.Tags.Add(penguinTag);
contribution.Tags.Remove(dromedaryTag);
// modifying the other end
penguinTag.Contributions.Add(waddlingVideo);
The through association also results in a through entity class and one-to-many
associations from the ‘main’ entity classes to the through entity class (which in turn
manifest in code as collection, backreference and foreign key properties). These are
visible in code, but most applications don’t need to use them. You’ll typically only work
with through entities for diagnostics, or if you want to associate further data with each
pairing in the many-to-many (for example, a ‘tagged by’ field).
Mindscape LightSpeed User Guide
38
When you use Update Database to create or update database tables, LightSpeed
creates any required through tables, with suitable foreign keys.
XML Documentation
Your model can include documentation for entities, properties, one-to-many
associations and stored procedures. Documentation will be emitted as XML
documentation comments which are displayed in Intellisense or can be built into a Help
file using a tool such as Sandcastle.
To view or edit documentation, right click on the designer and choose Documentation.
The LightSpeed Documentation window is displayed. Depending on the selected entity,
this will display different fields—typically Summary, Remarks and Additional. Enter your
documentation into these fields. The Documentation window tracks your selection in
the same way as the Properties window.
For most fields, LightSpeed will generate the required documentation tags (e.g.
<summary>) for you. However, for fields marked (XML), you must include the container
tags yourself. This allows you to generate elements such as <exception> which are not
directly represented in LightSpeed.
LightSpeed supports documentation for most common model elements, but not for
some less frequently used elements. Please visit the support forum (linked from the
Start menu) if you need to document a model element which does not currently
support documentation.
Mindscape LightSpeed User Guide
39
Creating Models in Code
The visual designer is an extremely convenient way to create domain models, and most
LightSpeed developers use it for almost all modelling requirements. You can also build
a LightSpeed model in code. Even if you use the designer for most tasks you may
sometimes want to drop down to the code level, for example to add some logic to a
property setter.
In fact, at run time a LightSpeed model is just code. Even if you only use the visual
designer, the actual runtime model is the generated code from the designer.
The easiest way to understand LightSpeed coding conventions is to sketch out a model
in the designer, then examine the generated code. You’ll see that it follows a very
regular pattern, and you can copy that pattern in hand-written code.
Creating Entity Classes
To create an entity class, define a .NET class which inherits from Entity<TId>. The TId
type parameter is the identity type of the class. For example, to define a Customer
class whose Id is an integer, write:
public class Customer : Entity<int>
{
}
Entity classes must have a public default constructor. If you don’t specify a constructor,
the C# compiler supplies a public default constructor, but if you specify a non-default
constructor, you’ll need to provide a default constructor as well. (See below for Visual
Basic considerations.)
The persistent state of an entity is defined by its fields. It’s very important to
understand that LightSpeed is interested in fields, not properties! The designer blurs
this distinction, because in most entities, every field is wrapped by a property and
every property wraps a persistent field. But when you hand-write code it’s essential to
understand it. For example, it means you mustn’t use C# automatic properties,
Mindscape LightSpeed User Guide
40
because the backing field for automatic properties is compiler-generated and will have
the wrong name:
public class Customer : Entity<int>
{
public string Surname { get; set; }
}
// Error – backing field will NOT be named Surname
Instead, you must explicitly create a field with the right name. (LightSpeed also permits
the underscore prefix.)
A persistent field in LightSpeed
public class Customer : Entity<int>
{
private string _surname; // LightSpeed ignores underscore prefix
}
You can then create a wrapper property so that application code can access the
persistent value. The property getter can just return the field value, but the property
setter must call the Entity.Set method. The Set method is important because it is how
LightSpeed knows that the field has changed. This is essential for knowing that at
entity needs to be saved, and to support application interfaces such as IEditableObject
and INotifyPropertyChanged.
A wrapper property for a persistent field
public class Customer : Entity<int>
{
private string _surname;
public string Surname
{
get { return _surname; }
set { Set(ref _surname, value); }
}
}
Mindscape LightSpeed User Guide
41
You don’t have to provide a wrapper property and LightSpeed won’t care if you don’t.
LightSpeed only cares about the field, and about the Set method being used to modify
it.
Because LightSpeed cares only about fields, not properties, LightSpeed attributes – for
example, validation attributes, or attributes that control the database mapping – must
go on fields rather than properties. (The compiler will warn you if you make a mistake.)
Creating Associations Between Entities
In the designer, a one-to-many association is represented as a single arrow, which
results at the code level in a collection, a backreference and a foreign key property. In
code, you need to create all of these fields – and, normally, wrapper properties –
explicitly. To represent a collection, use a field of type EntityCollection<T>; to
represent an entity reference, use a field of type EntityHolder<T>. (Foreign keys are
just normal scalar fields, typically of type int or Guid.)
The collection and holder fields should be marked readonly, and initialised to new
collection and holder instances.
Association fields always come in pairs. If the Customer class defines a collection of
Orders, then the Order class must define a reference to Customer. These pairs are
reverse associations. LightSpeed will throw an exception at runtime if it can’t find the
reverse for an association. Furthermore, each EntityHolder<T> must be matched with
a foreign key field, whose name is the same as the EntityHolder<T> field followed by Id.
For example, if the Order class has a holder named _customer, it must have a scalar field
named _customerId.
Mindscape LightSpeed User Guide
42
A one-to-many association therefore looks like this in code:
Representing a one-to-many association
public class Customer : Entity<int>
{
private readonly EntityCollection<Order> _orders = new EntityCollection<Order>();
}
public class Order : Entity<int>
{
private readonly EntityHolder<Customer> _customer = new EntityHolder<Customer>();
private int _customerId;
}
When application code accesses an association property, you need to ensure that the
association is loaded. To do this, call the Get method. This loads the association – the
collection or the associated entity – if required. If the association is already loaded, Get
doesn’t do anything. You’ll usually call Get from a property getter.
Application code can update a collection using the Add and Remove methods. To
update an entity reference, you must call the Set method. Set updates the contents of
the EntityHolder<T> and updates other information such as the foreign key field.
Mindscape LightSpeed User Guide
43
The conventional property wrappers for a one-to-many association therefore look like
this in code:
Property wrappers for a one-to-many association
public class Customer : Entity<int>
{
private readonly EntityCollection<Order> _orders = new EntityCollection<Order>();
public EntityCollection<Order> Orders
{
get { return Get(_orders); }
}
}
public class Order : Entity<int>
{
private readonly EntityHolder<Customer> _customer = new EntityHolder<Customer>();
private int _customerId;
public Customer Customer
{
get { return Get(_customer); }
set { Set(_customer, value); }
}
public int CustomerId
{
get { return _customerId; }
set { Set(ref _customerId, value); }
}
}
Remember, as with simple fields, LightSpeed cares only about fields, not properties, so
you’re not forced to follow this pattern or even to expose association properties at all.
One-to-one associations are implemented in the same way as one-to-many
associations, except that you use an EntityHolder<T> at both ends. The usage of the
EntityHolder<T> is the same, and there must be a foreign key field at one end.
Normally, LightSpeed can match associations up with their reverse associations
because there is only one association between any two classes. If you have multiple
associations between the same pair of classes, you must use
ReverseAssociationAttribute to pair them up.
Mindscape LightSpeed User Guide
44
public class Customer : Entity<int>
{
[ReverseAssociation("Customer")]
private readonly EntityCollection<Order> _orders = new EntityCollection<Order>();
}
public class Order : Entity<int>
{
[ReverseAssociation("Orders")]
private readonly EntityHolder<Customer> _customer = new EntityHolder<Customer>();
private int _customerId;
}
Many-to-Many Associations
As mentioned above, a many-to-many association in LightSpeed is represented by a
through association. A through association is implemented in terms of a one-to-many
association to a through entity and a many-to-one association from the through entity
to the target entity. For example, suppose you want to model a many-to-many
association between Contribution and Tag entities. You would need a through entity,
which we will call ContributionTag, and one-to-many associations from Contribution to
ContributionTag and Tag to ContributionTag.
Most through entities contain nothing except the associations to the entities being
linked, which are represented as the EntityHolder<T> ends of one-to-many
associations. As usual the foreign key fields are required as well. So the
ContributionTag entity would look like this:
Mindscape LightSpeed User Guide
45
Representing a through entity in code
public sealed class ContributionTag : Entity<int>
{
private int _contributionId;
private int _tagId;
private readonly EntityHolder<Contribution> _contribution =
new EntityHolder<Contribution>();
private readonly EntityHolder<Tag> _tag = new EntityHolder<Tag>();
// Wrapper properties omitted for brevity. Wrapper properties are optional
// and could be left out if you do not expect to work directly with through
// entities.
}
Conversely, Contribution and Tag each have a reverse association which is one-tomany: each Contribution can have any number of ContributionTags and each Tag can
also have any number of ContributionTags. Here’s the relevant fragment of
Contribution:
The underlying one-to-many association for a through association
public class Contribution : Entity<int>
{
private readonly EntityCollection<ContributionTag> _contributionTags =
new EntityCollection<ContributionTag>();
// Wrapper property omitted
}
You would define a similar association from Tag to ContributionTag.
Finally, you can now implement the through association. This is a field of type
ThroughAssociation<TThrough, TTarget>. The through association needs to be
initialised with the EntityCollection representing the one-to-many association from the
source entity (Contribution) to the through entity (ContributionTag). To load the
through association, call the Get method. As with other associations, this is usually
done in the property getter.
Mindscape LightSpeed User Guide
46
Implementing a through association
public class Contribution : Entity<int>
{
private ThroughAssociation<ContributionTag, Tag> _tags;
public ThroughAssociation<ContributionTag, Tag> Tags
{
get
{
if (_tags == null)
{
_tags = new ThroughAssociation<ContributionTag, Tag>(_contributionTags);
}
return Get(_tags);
}
}
}
Again, the Tag entity will contain similar code for its Contributions through association.
The through entity is a true entity in the model, so you can use it to hang data and
behaviour relating to the association itself. For example, to track who applied a
particular tag to a particular contribution, you could put an AddedBy field on the
ContributionTag entity.
Special Considerations for Visual Basic
A nice feature of .NET is the great interop between components written in different
languages. LightSpeed is no exception and can be used just as easily from Visual Basic
as from C#.
However, when hand-coding entities in Visual Basic, there is one minor difference,
which is due to Visual Basic classes initialising in a slightly different order from C#
classes. The difference is that you must manually call the LightSpeed Initialize method
from the entity constructor, as follows:
Mindscape LightSpeed User Guide
47
Writing an entity constructor using Visual Basic
Public Class Status
Inherits Entity(Of Integer)
Public Sub New()
MyBase.New(False)
Initialize()
End Sub
End Class
You only need to do this if you are hand-coding entities. If you use the designer then
it is taken care of for you.
Another small wrinkle is that Get and Set, the LightSpeed Entity methods, are reserved
words in Visual Basic, and must therefore be escaped with square brackets:
Calling the Get and Set methods from Visual Basic
Public Property StatusName() As String
Get
Return [Get](_statusName)
' note square brackets around Get
End Get
Set(ByVal value As String)
[Set](_statusName, value)
' note square brackets around Set
End Set
End Property
Again, if you use the designer, it will take care of this for you.
Creating Code Models from Database Tables
If you have an existing database schema, and you would prefer to develop your entities
in code rather than using the designer, you can use the lsgen command-line tool to
create C# or Visual Basic classes from your database. See the Appendices for
instructions on using lsgen.
Mindscape LightSpeed User Guide
48
Validation
LightSpeed provides a rich, extensible object-level validation framework. An entity may
be validated by calling the Entity.Validate method or querying the Entity.IsValid
property. Validation errors are exposed at the entity-level through the bindable
Entity.Errors collection. Custom validation may be performed by overriding the
Entity.OnValidate method. Objects are always validated before they are saved to the
database and a ValidationException will be raised if an attempt is made to save an
invalid object.
Specifying Validation Criteria
To specify validation criteria for a property, select the property, go to the Properties
window and enter the required validations.
Mindscape LightSpeed User Guide
49
The following validation options are available:
Validation
Description
Validate Email
Ensures that the property contains a valid email address. In
LightSpeed 5, this now supports emails with + and ‘ characters.
Validate Format
For strings, ensures that the string conforms to the supplied
regular expression
Validate Length
For strings, validates the string length. Write <= n if the string
must be no longer than n characters, >= n if the string must be at
least n characters, and n - m if the string must be between n and m
characters.
Validate Presence
Ensures that a value has been provided for the property. For
numeric types, this means the value is non-zero; for strings, it
means the value is not null or the empty string.
Validate Unique
Ensures that the property value is unique – ideal for email
addresses, user names, etc. See Validation Considerations below.
Validate URI
Ensures that the property contains a valid URI
Validate Value
For numeric values, validates the property value. You can specify a
range using the format min - max, or a comparison using the =, !=,
<, >, <= and >= operators, which have the same meanings as in C#
(e.g. <= n to ensure that the value is less than or equal to n).
Advanced Validation Options
If you need more fine control over validation than you can enter in the Properties
window, you can use the LightSpeed Model Explorer to manage individual validation
attributes. LightSpeed Model Explorer provides the following additional capabilities:



Additional options such as the Allow Empty String option on
PresenceValidation or the URI Kind or Is Required options on URI Validation.
Custom error messages (select the validation and edit its Custom Error in the
Properties window).
Simplified editing user interface for Length and Range validations (separate
entries in Properties window for maximum and minimum).
Mindscape LightSpeed User Guide
50


Simplified editing for Comparison validations (drop-down list of operators).
Support for custom validations. (Enter the validation attribute as you would like
it emitted in the model code, excluding the surrounding brackets. For example,
MyValidation(123). Note that, depending on the contents of the attribute
declaration, this may make your model language-specific.)
To open the LightSpeed Model Explorer, choose View > Other Windows > LightSpeed
Model. Then expand the tree view to show the property whose validation you want to
customise, and open its Validations folder.
You can then modify validations through the Properties grid, and add them by rightclicking the property and choosing Add New validation_type Validation. In particular,
to add a custom validation, choose Add New Custom Validation.
Mindscape LightSpeed User Guide
51
Note that some validation options in the Properties window may map to different
underlying validation objects in the tree view. For example, a Validate Value
expression may be implemented as a Range Validation or a Comparison Validation.
Automatic Validations
LightSpeed infers several validations automatically based on the underlying model.


Association presence validation – ensures that any required associations are
present. LightSpeed infers this based on whether the association foreign key
field is nullable.
DateTime range validation – ensures that any DateTime fields fall within a
range acceptable for the database at hand.
These validations are built-in and therefore result in built-in messages. To override the
message, you must apply a declarative validation in code (see below). Use the
ValidateAttribute with a rule type of PresenceAssociationValidationRule or
ProviderDateRangeValidationRule as appropriate, and specify your custom message on
the ValidateAttribute.
Declarative Validation in Code
For hand-coded entities or fields, you can specify validation by applying attributes to
the field. (Remember LightSpeed attributes go on fields, not properties.)
Simple declarative validation
[ValidateLength(0, 50)]
[ValidatePresence]
private string _firstName;
The following attributes are available:
Attribute
Description
ValidateAttribute
Used to specify a custom validation rule
Mindscape LightSpeed User Guide
52
Attribute
Description
ValidateComparisonAttribute
Compares the target field to another value, e.g. less
than 100
ValidateEmailAddressAttribute
Ensures that the property contains a valid email
address
ValidateFormatAttribute
For strings, ensures that the field conforms to the
supplied regular expression
ValidateLengthAttribute
For strings, ensures that the string length falls
between the specified bounds
ValidatePresenceAttribute
Ensures that a value has been provided for the
property. For numeric types, this means the value is
non-zero; for strings, it means the value is not null or
the empty string.
ValidateRangeAttribute
For numeric values, validates that the value is within
the specified range
ValidateUniqueAttribute
Ensures that the property value is unique – ideal for
email addresses, user names, etc. See Validation
Considerations below.
ValidateUriAttribute
Ensures that the property contains a valid URI
Note that the designer merges some of these attributes (for example, the Validate
Value option can map to either ValidateComparisonAttribute or
ValidateRangeAttribute depending on the particular validation).
Overriding OnValidate
To perform more complicated validation, such as whole entity validation logic, override
the Entity.OnValidate method. Report errors by adding them to the Errors collection.
Mindscape LightSpeed User Guide
53
Overriding OnValidate
protected override void OnValidate()
{
if ((Contributor == null) && (ApprovedBy == null))
{
Errors.AddError("Must have one or the other");
}
}
Validation Considerations
When using the Validate Unique validation (or the ValidateUniqueAttribute), you
should be aware that:



It incurs a COUNT(*) query against the database each time the validation runs.
The designer will create a unique constraint on the column which should make
this fast, but you should still be aware that it can cause a lot of queries during,
for example, a bulk import.
The check is performed against the database, not against other in-memory
entities. For example, if you create two new entities with the same value, and
save them in the same unit of work, you can bypass the uniqueness validation.
Again, a unique constraint at the database level will catch this, but it will result
in a database exception rather than a validation exception.
In LightSpeed 5, unique validation is scoped to the level which declares the
field, not the level of the entity which is being validated at that time.
Localising Validation Messages
LightSpeed validation messages are intended to be human-readable. If you need to
display messages in a language other than English, or just to replace the messages with
your own messages, you can do so by creating a satellite resource DLL and/or a display
naming strategy. For more information, see Localization in the Building Applications
with LightSpeed chapter.
Mindscape LightSpeed User Guide
54
Basic Operations
The core operations of LightSpeed are the familiar CRUD (Create,
Read, Update, Delete) database operations. The CRUD model lies behind the majority
of database-backed applications and Web sites. This chapter shows you how to
implement CRUD using LightSpeed.
Mindscape LightSpeed User Guide
55
Working with Entities
All persistent data is represented in LightSpeed as entities. An entity represents a
business object with identity. It can be as big as a company or as small as a single order
line. Of course, an entity can have associations to other entities, allowing an aggregate
to represent a rich business domain. An entity can be loaded, modified and saved, and
retains its identity throughout its lifecycle.
The Unit of Work
When you work with entities, you normally do so as part of a unit of work. The unit of
work pattern is described as:
Maintains a list of objects affected by a business transaction and coordinates the
writing out of changes and the resolution of concurrency problems.
In LightSpeed, a unit of work represents a business transaction in progress. It tracks
the entities involved in that business transaction, takes care of loading entities from the
database if required, and provides a way to save any changes made in the course of the
business transaction. Roughly speaking, a unit of work is a collection of entities and
their pending database operations.
A unit of work is represented in LightSpeed as the IUnitOfWork interface. IUnitOfWork
provides operations to load, add and remove entities, and to save pending changes.
The basic process of working with entities in LightSpeed is therefore very simple:




Begin a unit of work.
Perform whatever combination of loads, adds, updates and removals are
required for the business function you’re implementing.
Save any pending changes in the unit of work.
End the unit of work.
During this process, the unit of work object automatically:

Tracks loaded entities in an identity map.
Mindscape LightSpeed User Guide
56



Tracks entity state changes, so that it knows which entities if any need to be
saved.
Updates change tracking and versioning information if required.
Connects to the database as and when required.
The LightSpeedContext Object
A LightSpeedContext object represents the configuration and connection settings for a
particular database instance. A LightSpeedContext is like a souped-up connection
string: in addition to the physical connectivity information of the connection string, the
context also specifies the database engine, mapping conventions, logging behaviour
and several other options.
Most applications require only one LightSpeedContext, representing the application
database and the way the application maps to that database. Those settings won’t
change over the course of the application, though they may of course be different for
different instances of the application. You’ll therefore normally create your
LightSpeedContext object as a static or singleton object – there’s no benefit, and some
disadvantages, to creating a new LightSpeedContext every time you need one.
Mindscape LightSpeed User Guide
57
LightSpeed Configuration Basics
As described above, the LightSpeedContext object contains the configuration and
connection settings for a particular database instance. The usual way to store these
settings is in the application configuration file – web.config for Web applications and
sites, appname.exe.config for desktop applications – though you can also set them in
code. LightSpeedContext supports a lot of configuration options, which we describe
more fully in the relevant sections of this user guide, and summarise in the Appendices.
In this section, we’ll describe the minimal configuration for connecting to a database.
LightSpeed Configuration Section
When you store settings in the application configuration file, you must declare and
implement a lightSpeedContexts section, and define your specific settings within that
section using the add element, as follows:
Declaring the lightSpeedContexts section
<configSections>
<section name="lightSpeedContexts"
type="Mindscape.LightSpeed.Configuration.LightSpeedConfigurationSection,
Mindscape.LightSpeed" />
</configSections>
Implementing the lightSpeedContexts section
<lightSpeedContexts>
<add name="Test" />
</lightSpeedContexts>
<!-- We will add more here shortly -->
Loading a LightSpeedContext from Configuration
To load a LightSpeedContext from configuration, pass the name of the configuration
file entry to the LightSpeedContext constructor:
Mindscape LightSpeed User Guide
58
Loading a LightSpeedContext from configuration
private static readonly LightSpeedContext _context = new LightSpeedContext("Test");
Specifying the Data Provider
LightSpeed needs to know what kind of database you are using. Specify this using the
dataProvider attribute:
Specifying the data provider
<lightSpeedContexts>
<add name="Test"
dataProvider="SQLite3"
/>
</lightSpeedContexts>
The following values are recognised for dataProvider:
Provider Name
Description
SqlServer2000
Microsoft SQL Server 2000 using System.Data provider.
SqlServer2005
Microsoft SQL Server 2005 using System.Data provider.
SqlServer2008
Microsoft SQL Server 2008 using System.Data provider.
SqlServer2012
Microsoft SQL Server 2012 using System.Data provider.
MySql5
MySQL 5 database using MySql.Data provider.
PostgreSql8
PostgreSQL 8 database through Npgsql provider.
PostgreSql9
PostgreSQL 9 database through Npgsql provider.
SQLite3
SQLite 3 database through System.Data.SQLite provider.
Oracle9
Oracle 9 (or higher) database through
System.Data.OracleClient provider.
Oracle9Odp
Oracle 9 (or higher) database through Oracle.DataAccess
provider.
Mindscape LightSpeed User Guide
59
Provider Name
Description
VistaDB4
VistaDB 4 database through VistaDB.4 provider.
SqlServerCE
SQL Server Compact 3.5 through System.Data provider.
SqlServerCE4
SQL Server Compact 4 through System.Data provider.
DB2
DB2 9.5 through IBM.DB2 provider.
WindowsAzureTableService Windows Azure Table storage provider.
AmazonSimpleDB
Amazon SimpleDB cloud database or compatible.
The default provider is SqlServer2005 (Microsoft SQL Server 2005).
Specifying the Connection String
Connection strings are stored in the <connectionStrings> element as per the standard
.NET configuration file schema. When configuring LightSpeed you can refer to a
connection string by name using the connectionStringName attribute:
Specifying the connection string
<lightSpeedContexts>
<add name="Test"
connectionStringName="Sample"
/>
</lightSpeedContexts>
<connectionStrings>
<add name="Sample"
connectionString="Data Source=sample.db3" />
</connectionStrings>
Other Configuration Options
Other common configuration options include:



pluralizeTableNames
identityMethod
quoteIdentifiers
Mindscape LightSpeed User Guide
60
See the chapter Controlling the Database Mapping for more information on these
options.
Mindscape LightSpeed User Guide
61
Querying the Database Using LINQ
The LightSpeed designer declares a strong-typed unit of work class that exposes
properties representing queries for different types of entity. To query the database
using LINQ, we need to create a unit of work of this special type, associated with our
specified configuration settings. We can then issue queries against it using the normal
LINQ syntax.
Creating a Strong-Typed Unit of Work
To create a strong-typed unit of work, we use the LightSpeedContext<TUnitOfWork>
class, where TUnitOfWork is the strong-typed unit of work class, and call
CreateUnitOfWork on that context. Because the unit of work class implements
IDisposable, this should normally be done in a using statement.
Creating a strong-typed unit of work for use with LINQ
public class Program
{
private static readonly LightSpeedContext<StoreUnitOfWork> _context =
new LightSpeedContext<StoreUnitOfWork>("Test");
public static void UseUnitOfWork()
{
using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
// Do work here
}
}
}
(In future we won’t normally show the LightSpeedContext. We’ve shown it here
because, when you use LINQ, it’s important to remember to use the strong-typed
generic version of LightSpeedContext.)
Mindscape LightSpeed User Guide
62
Writing LINQ Queries Using C# Syntax
The strong-typed unit of work exposes properties named after your entities. You can
write LINQ queries against these properties using the built-in C# LINQ syntax. (The
Visual Basic syntax is similar.)
Querying the database
using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
var todaysOrders = from o in unitOfWork.Orders
where o.OrderDate >= DateTime.Today
select o;
Console.WriteLine("Number of orders: " + todaysOrders.Count());
foreach (var order in todaysOrders)
Console.WriteLine("Order reference: " + order.OrderReference);
}
When you write a LINQ query against a LightSpeed query property, the query is
translated to SQL and executed on the database. For example, the LINQ where clause
is translated to a SQL WHERE clause. This means processing is efficient – for example,
LINQ does not bring back all Order entities and filter them on the client.
Writing LINQ Queries Using the Standard Query Operators
You can also write LINQ queries against the strong-typed unit of work using the LINQ
extension methods or standard query operators.
Mindscape LightSpeed User Guide
63
Querying the database
using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
var todaysOrders = unitOfWork.Orders
.Where(o => o.OrderDate >= DateTime.Today);
Console.WriteLine("Number of orders: " + todaysOrders.Count());
foreach (var order in todaysOrders)
Console.WriteLine("Order reference: " + order.OrderReference);
}
Writing LINQ Queries Against Hand-Coded Entities
If you use the visual designer, it creates a strong-typed unit of work class for you. If you
are writing entity classes by hand, you must create the strong-typed LINQ queries
yourself. To do this, call the Query<T> extension method on IUnitOfWork:
Hand coding a LINQ query
using Mindscape.LightSpeed.Linq;
// Bring extension methods into scope
// Method 1: Declare your own strong-typed unit of work class
public class StoreUnitOfWork : UnitOfWork
{
public IQueryable<Order> Orders
{
get { return this.Query<Order>(); }
}
}
// Method 2: Call Query explicitly on a weak-typed unit of work
IUnitOfWork unitOfWork;
// weak typed
var todaysOrders = unitOfWork.Query<Order>()
.Where(o => o.OrderDate >= DateTime.Today);
Mindscape LightSpeed User Guide
64
Common LINQ Techniques
To filter a query – that is, to tell LightSpeed which entities you want to return – use the
where keyword or the Where extension method.
To sort a query, use the orderby keyword or the OrderBy extension method. Sorting is
in ascending order by default: the orderby keyword allows you to specify the
descending modifier, which corresponds to the OrderByDescending method. The
orderby keyword supports sorting on multiple attributes; additional attributes
correspond to the ThenBy or ThenByDescending method.
var recentOrders = from o in unitOfWork.Orders
where o.CustomerId == customerId
orderby o.OrderDate descending
select o;
To perform paging of a query, use the Skip and Take extension methods. If you don’t
also specify an order, either explicitly in the LINQ query or implicitly on the entity class,
Skip and Take order entities by Id. You can combine Skip and Take if you want to page
through a result set.
var ordersToDisplay = unitOfWork.Orders
.OrderBy(o => o.OrderDate)
.Skip(pageStart)
.Take(pageCount);
To work with the entities returned from a LINQ query, use the foreach keyword to
iterate over the query, or use the ToList extension method to load the results into a list.
If you are only interested in a single entity, apply the First or Single extension method
to obtain it. First returns the first matching entity, ignoring any others; Single checks
that there is only one matching entity.
Mindscape LightSpeed User Guide
65
List<Order> allOrders = unitOfWork.Orders.ToList();
Order order = unitOfWork.Orders.Single(o => o.Id == orderId);
If you want to know how many entities fit the query criteria, apply the Count extension
method. If you want to know if any entities fit the query criteria, apply the Any
extension method.
int pendingOrderCount = unitOfWork.Orders
.Where(o => o.Status == OrderStatus.Pending)
.Count();
To perform a projection – that is, to select only a subset of the entity fields – use the
select keyword or the Select extension method. If you perform a projection, then you
will typically project into a non-entity type, and the data will not be associated with the
unit of work or cached in the identity map, and changes to the object will not be saved
when the unit of work is flushed. This is therefore typically used for presenting partial,
read-only information about an entity.
var orderSummaries = from o in unitOfWork.Orders
select new { OrderId = o.Id, o.OrderReference };
To bulk remove based on a query use the Remove extension method in the
Mindscape.LightSpeed.Linq namespace. This allows Remove by Query to be specified
using a simple expression, such as:
Mindscape LightSpeed User Guide
66
Remove
using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
unitOfWork.Orders.Remove();
unitOfWork.SaveChanges();
}
using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
unitOfWork.Orders.Where(o => o.Id > 20).Remove();
unitOfWork.SaveChanges();
}
To target database views you can use the .WithViewName extension method in the
Mindscape.LightSpeed.Linq namespace to specify the view which the query is to
operate over.
All of these methods are translated to SQL so that LightSpeed does not waste time and
bandwidth pulling back unwanted rows or columns. For example, if you specify Take(5)
then LightSpeed will limit the number of rows returned to 5; if you specify Count() then
LightSpeed issues a SQL COUNT query rather than materialising entities on the client.
See also Advanced Querying Techniques later in this book.
LINQ Expressions
LINQ allows you to write queries of arbitrary complexity. LightSpeed handles only
queries that can be translated to SQL on the database at hand. Consequently, if you
write complex queries, you may encounter NotSupportedException at runtime. This
indicates that LightSpeed was not able to translate the LINQ query to SQL. Consider
simplifying the query, and performing further operations on the client. You can use the
ToList() and AsEnumerable() operators to partition work between the database and the
client.
For known limitations on what LINQ expressions LightSpeed can translate to SQL, see
the Appendices.
Mindscape LightSpeed User Guide
67
Querying the Database Using Query Objects
LightSpeed offers another way of querying the database, using query objects. In the
query objects API, instead of writing your query using the LINQ syntax or operators, you
construct objects that represent the query specification, and pass them to an
appropriate method on IUnitOfWork. IUnitOfWork also provides some handy
overloads to make common queries more convenient.
Creating a Unit of Work
When you use query objects, you don’t need a strong-typed unit of work: you just use
the IUnitOfWork base interface. Consequently, you don’t need to use the generic
LightSpeedContext, and can instead use the non-generic base context class.
Creating a unit of work for use with query objects
public class Program
{
private static readonly LightSpeedContext _context = new LightSpeedContext("Test");
public static void UseUnitOfWork()
{
using (IUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
// Do work here
}
}
}
Query Expressions
The most commonly used query object is the QueryExpression object. You can pass a
QueryExpression directly to IUnitOfWork.Find<T> to load entities by criteria, like the
LINQ Where operator.
To create a QueryExpression, use the Entity.Attribute static method to represent the
attribute you want to query on, then apply comparison operators such as ==, <, and so
on.
Mindscape LightSpeed User Guide
68
Using a QueryExpression to query by criteria
IUnitOfWork unitOfWork;
// weak typed
IList<Order> orders = unitOfWork.Find<Order>(
Entity.Attribute("CustomerId") == customerId);
You can combine query expressions using Boolean operators such as && and ||:
IList<Order> orders = unitOfWork.Find<Order>(
Entity.Attribute("CustomerId") == customerId
&& Entity.Attribute("OrderDate") == DateTime.Today);
Query expressions also support the In, Like and Between methods for criteria that can’t
be represented using the built-in operators:
IList<Order> orders = unitOfWork.Find<Order>(Entity.Attribute("CustomerId").In(1, 3, 5));
Query expressions support traversal into associated entities using the dot syntax:
IList<Order> orders = unitOfWork.Find<Order>(Entity.Attribute("Customer.Name") == "Bob");
Sorting and Paging
To sort and page a query, pass Order and Page objects to the Find<T> method. You can
construct these objects using static methods and fluent builder methods on the Order
and Page classes.
Mindscape LightSpeed User Guide
69
Using Order and Page to select a specific range of entities
IList<Order> orders = unitOfWork.Find<Order>(
Entity.Attribute("CustomerId") == customerId,
Order.By("OrderDate"),
Page.Offset(20).LimitTo(10));
Query Objects
In some cases you need to specify additional querying options over and above the
criteria, sort order and paging. In these cases, you must create a Query object and pass
this to Find. The Query object allows you to specify projections, perform full text
searches and customise entity load graphs. Specific functions are covered in the
relevant sections of this user guide, or see the Query object in the API reference.
Single Entity Queries
The Find method returns a list of entities. If you expect a query to return only a single
entity, you can use the FindOne method to avoid the overhead of extracting that entity
from the list:
Order order = unitOfWork.FindOne<Order>(Entity.Attribute("OrderReference") == orderRef);
If you want to look up an entity by Id, there is a special FindById method:
Order order = unitOfWork.FindById<Order>(123);
Always use FindById for identity lookups, because it tries the lookup in the unit of
work’s identity map first, and queries the database only if the lookup fails. This greatly
improves efficiency if the entity is already part of the unit of work.
Mindscape LightSpeed User Guide
70
Count Queries
If you only need to know how many entities meet your query criteria, without bringing
back those entities, you can use the Count method instead of Find.
long orderCount = unitOfWork.Count<Order>(new Query(
Entity.Attribute("Customer.Name") == "Bob"));
The Count method doesn’t have an overload that allows you to pass a
QueryExpression; you must pass a full Query object. You can pass a QueryExpression in
the Query constructor.
Choosing Between LINQ and Query Objects
Because LightSpeed offers both LINQ and Query-based APIs for queries, you may
wonder which you should choose. In general, most developers prefer LINQ, because it
is tightly integrated into C# and Visual Basic – for example providing Intellisense
support – and is familiar from LINQ to Objects or other data access technologies such
as LINQ to SQL. LINQ also makes it much easier to write group and join queries, and to
perform projections through its convenient initialisation and anonymous type syntax.
However, in some cases LINQ’s static nature makes it unsuitable. For example, if the
user can choose a field to filter on, then it may be easier to pass that field name to
Entity.Attribute than to construct a LINQ query at run time. In addition, because LINQ
queries are translated into query objects, LINQ incurs a small translation overhead,
though this is normally insignificant compared to the cost of the database query.
Mindscape LightSpeed User Guide
71
Creating, Modifying and Deleting Entities
The previous sections show how to query the database using LightSpeed. In many
applications you will also want to save changes to the database – adding new entities,
modifying or deleting existing ones. As with querying, LightSpeed supports these
operations through the unit of work.
How Changes are Saved
LightSpeed saves changes to a unit of work, not an individual entity. When you add,
modify or delete an entity, it is not saved immediately. Instead, the unit of work just
notes that the entity needs to be saved. When you call IUnitOfWork.SaveChanges(),
LightSpeed saves all the entities that need it. This means that you can coordinate the
persistence of multiple related changes, and minimises the number of database roundtrips.
Adding a New Entity
To add an entity to the unit of work, call IUnitOfWork.Add. The entity itself can be
created however you like, typically using the new operator or a factory method.
Creating a new entity and adding it to the unit of work
Order order = new Order { OrderReference = orderRef };
unitOfWork.Add(order);
If a new entity is associated with another entity that is already part of a unit of work, it
automatically becomes part of the same unit of work. This saves you having to
remember to add the entity to the unit of work separately. Any kind of association will
trigger this.
Mindscape LightSpeed User Guide
72
Creating a new entity which implicitly becomes part of the unit of work
Customer customer = unitOfWork.FindById<Customer>(customerId);
Order order = new Order { OrderReference = orderRef };
customer.Orders.Add(order); // implicitly adds order to same unit of work as customer
Note that because LightSpeed saves units of work, you must add the entity to a unit of
work – whether explicitly or implicitly – in order for it to be saved. Just creating the
entity is not enough!
As part of adding the entity to a unit of work, LightSpeed assigns an Id to the entity.
Before a new entity becomes part of a unit of work, its Id is invalid and should not be
used.
Updating an Existing Entity
To update an existing entity, load it into a unit of work and set any required properties
to their new values.
Updating an existing entity
Customer customer = unitOfWork.FindById<Customer>(customerId);
customer.Name = "Bob";
LightSpeed automatically determines that the entity has changed, and marks it to be
saved.
Mindscape LightSpeed User Guide
73
Deleting an Existing Entity
To delete an existing entity, load it into a unit of work and call IUnitOfWork.Remove.
Deleting an existing entity
Customer customer = unitOfWork.FindById<Customer>(customerId);
unitOfWork.Remove(customer);
If an entity has dependent associations, LightSpeed cascade deletes the dependent
entities. This avoids database integrity errors due to foreign key constraints. For
example, if every Order is associated with a Customer, and you delete a Customer, then
all Orders associated with that Customer are also deleted. However, if the association
is not dependent – that is, if your model allows dangling Orders not associated with a
Customer – then the Orders are merely detached from the Customer and remain in the
database.
You can override the default cascade delete behaviour by setting
LightSpeedContext.CascadeDeletes in code or the cascadeDeletes attribute in
configuration, or by setting the Cascade Deletes option on an entity (which affects all
associations where that entity is the parent), or by setting the Is Dependent option on
an individual association.
In all cases, remember that entities are not deleted from the database immediately,
but will be deleted when you save the unit of work.
Saving the Unit of Work
When you have made all the changes you need to make, call
IUnitOfWork.SaveChanges:
Saving the unit of work
unitOfWork.SaveChanges();
Mindscape LightSpeed User Guide
74
SaveChanges validates all entities that are due to be added or updated, and will not
save an invalid entity. (Invalid entities may be deleted.)
By default, the saved entities remain part of the unit of work, in case you want to carry
out more changes on them. You can remove them, forcing LightSpeed to reload fresh
copies, by calling the SaveChanges(bool reset) overload and passing true. However, it
is usually clearer to start a new unit of work for the new batch of activity.
After you have finished with a unit of work, you must call Dispose.
Mindscape LightSpeed User Guide
75
Transactions
SaveChanges is automatically transactional: that is, if more than one entity needs to be
saved, LightSpeed guarantees that the changes to the database will be atomic and
durable (provided the database supports transactions). To achieve this, SaveChanges
automatically begins a transaction before sending the first change, and commits it after
sending the last change if all changes have been successful.
As far as LightSpeed is concerned, however, each SaveChanges is an independent
transaction. If you need multiple LightSpeed operations to be part of a single
transaction, you must specify that transaction yourself.
Using TransactionScope
The easiest way to control transactions with LightSpeed is to use the .NET
TransactionScope class. To do this, simply surround any calls to IUnitOfWork with the
standard TransactionScope block:
Using a system transaction with a LightSpeed unit of work
using (var transactionScope = new TransactionScope())
{
using (var unitOfWork = _context.CreateUnitOfWork())
{
var contribution = unitOfWork.FindById<Contribution>(1);
contribution.Description = "A description";
unitOfWork.SaveChanges();
}
transactionScope.Complete();
}
Using ADO.NET Transactions
However, not all data providers support TransactionScope. (Specifically, at the time of
writing, Oracle and PostgreSQL do not.)
Mindscape LightSpeed User Guide
76
For these cases, IUnitOfWork exposes a BeginTransaction method. This method returns
a standard ADO.NET IDbTransaction object. In this case, the usage pattern is slightly
different as we need to flush changes using the IUnitOfWork.SaveChanges method
before committing the transaction and then completing the unit of work.
Using an ADO.NET transaction with a LightSpeed unit of work
using (var unitOfWork = _context.CreateUnitOfWork())
{
using (var transaction = unitOfWork.BeginTransaction())
{
var contribution = unitOfWork.FindById<Contribution>(1);
contribution.Description = "A description";
unitOfWork.SaveChanges();
transaction.Commit();
}
}
Automatic Transactions
Remember that you only need to manually specify a transaction if you need to
coordinate at a larger scope than a single SaveChanges. LightSpeed automatically
ensures that all database flush operations run within a transaction. In most cases
therefore you will not need to create transactions explicitly.
Mindscape LightSpeed User Guide
77
Controlling the Database Mapping
When you’re working with LightSpeed, you’re working at the level of .NET domain
objects. In the ideal world, the database would be transparent to you: objects would
persist and could later be retrieved, but you wouldn’t need to know how or where that
persistence happened.
In reality, of course, this doesn’t happen. You may be working with an existing
database, which you want to map into a more usable domain model. You have to work
with database administrators who have specific rules on how the database should be
organised. You may need to control which fields get persisted and which do not. You
may need to move some business logic into the database for performance reasons.
LightSpeed adopts a principle of convention over configuration, so that if you just
define your domain model and do nothing more, it will be mapped to the database in a
sensible way. This chapter describes the default LightSpeed conventions, then goes on
to describe how you can override those conventions to meet your particular
requirements.
Mindscape LightSpeed User Guide
78
Understanding the Default Mapping
By default, LightSpeed maps the domain model to the database as follows.
Entity Classes Map to Tables
Each entity class maps to a database table. The name of the table is the name of the
entity class. For example, an Employee entity class is mapped to an Employee table.
If the LightSpeedContext.PluralizeTableNames configuration option is set to true, then
the name of each table is the plural of the entity class name. For example, a Person
entity class is mapped to a People table. (Pluralization uses English language rules, and
allows for most common irregular plural forms.)
The ‘table per class’ mapping changes if you use inheritance in your domain model.
See Domain Modelling Techniques for more information.
Entity Id Maps to the Id Column
The Entity<TId> base class defines an Id property which represents an identifying key
for the entity. The Id property maps to the Id column.
Consequently, in the default mapping, every table must have an Id column (which
should be the primary key).
The ‘Id column’ mapping changes if you use composite keys. You should only do this if
you are working with a legacy database that you cannot change to support a scalar
surrogate key. See Working with Legacy Databases for more information.
Fields Map to Columns
Each field in an entity maps to a database column. The name of the column is the
name of the field. For example, a FirstName field is mapped to a FirstName column.
The ‘column per field’ mapping changes if you use value objects to represent structures
or semantics that turn up repeatedly in the business model. See Domain Modelling
Techniques for more information.
Mindscape LightSpeed User Guide
79
It’s important to be aware that LightSpeed maps fields, not properties, to columns.
This allows you to decouple your API from your persistence model if required. For
example, you might choose not to present some attributes directly as properties,
instead allowing them to be accessed and modified only through domain methods. If
you are using the designer, of course, it cannot create domain methods for you, but
you can suppress the wrapper properties by setting the Generation option to FieldOnly.
Associations Map to Foreign Key Columns
Each one-to-many or one-to-one association in an entity is backed by a field which
maps the foreign key for that association. (Many-to-many associations are represented
by a pair of one-to-many associations, and are therefore backed by a pair of foreign
keys fields.) The foreign key maps to a column just as a normal field does. The foreign
key field name is that of the backreference (or EntityHolder field) , with an “Id” suffix.
For example, a Manager association is backed by a ManagerId field which is mapped to
a ManagerId column.
Id Values Come From KeyTable
When LightSpeed needs to assign an Id to an entity, by default it obtains the next value
from a table named KeyTable. (Actually, for efficiency, LightSpeed gets values in
blocks, rather than one at a time.) In the default mapping, therefore, each database
must contain a table named KeyTable with the appropriate schema. See Identity
Generation below for more information.
Default Mapping Example
The picture below shows a simple LightSpeed and how it is mapped in the database.
Note that:




The database table names are the same as the entity names.
Each database table has a primary key named Id. This isn’t shown on the
LightSpeed designer, but it is present in each LightSpeed entity.
The database column names are the same as the entity property names.
The database contains a foreign key column for each association, and the
foreign key name is the name of the association followed by “Id.”
Mindscape LightSpeed User Guide
80
Mindscape LightSpeed User Guide
81
Overriding the Default Mapping
In greenfield development, it’s a good idea to keep to the default mapping. This
ensures that terms are used consistently across the domain model and the database, so
that everyone is using the same language and confusion is avoided. In brownfield
development, this often isn’t possible, and even in greenfield projects there may be
cases where the default mapping is problematic.
Mapping Individual Tables and Columns
The most common scenario for overriding the default mapping is that you are working
with an existing database whose terminology you don’t want to carry forward into your
domain objects.
In this case, you can override table and column mappings on an ad hoc basis, specifying
an override for each table or column where you want to change the name.
Mapping Individual Tables and Columns in the Designer




To specify the name of the table for an entity class, select the entity, go to the
Properties grid, and set the Table Name option.
To specify the name of the column for a field, select the field, go to the
Properties grid, and set the Column Name option.
To specify the name of the identity column – that is, the column that
corresponds to the Id property – select the entity, go to the Properties grid,
and set the Identity Column Name option.
To specify the name of the foreign key column for an association, select the
association arrow, go to the Properties grid, and set the Column Name option.
If you have dragged a table into the model from a database, and you’re dissatisfied
with the inferred names, you can quickly rename an entity or field while retaining its
mapping by using the Refactor > Rename command and checking the “Keep existing
name as database table/column name” option.
Mindscape LightSpeed User Guide
82
If you have dragged a table into the model from a database, and the primary key
column has a name other than Id, LightSpeed will automatically create the identity
column mapping for you.
Mapping Individual Tables and Columns in Code




To specify the name of the table for an entity class, apply TableAttribute to the
class.
To specify the name of the column for a field, apply ColumnAttribute to the
field.
To specify the name of the identity column, apply TableAttribute to the class
and set the attribute’s IdColumnName.
To specify the name of the foreign key column for an association, apply
ColumnAttribute to the field containing the foreign key.
Defining Your Own Mapping Convention
Mapping table and column names works well for individual tables and columns, but if
you are working with a database which uses a regular convention that happens to be
different from LightSpeed’s, then you may want to encapsulate that convention rather
than mapping each and every table or column. For example, some organisations use a
sort of Hungarian notation to name database elements: tables are prefixed tbl,
columns are prefixed col, and so on. Other organisations like to use distinct primary
key names in different tables, so the Employee table’s primary key would be
EmployeeId while the Customer table’s primary key would be CustomerId.
Conventions such as this are represented by implementations of the INamingStrategy
interface. INamingStrategy allows you to map the names of most LightSpeed elements,
but for the time being we will focus on tables and columns. We could represent the
Hungarian convention as follows:
Mindscape LightSpeed User Guide
83
Representing Hungarian notation as a LightSpeed naming strategy
public class HungarianNamingStrategy : INamingStrategy
{
public string GetTableName(string defaultName, string className)
{
return "tbl" + defaultName;
}
public string GetColumnName(string defaultName, string fieldName)
{
return "col" + defaultName;
}
// other members
}
All members of INamingStrategy get passed a defaultName by LightSpeed. If you want
to accept the default convention in a particular case, just return defaultName.
Once you have implemented a naming strategy to represent your convention, you must
tell LightSpeed to use it. To do this, set LightSpeedContext.NamingStrategy in code, or
the namingStrategyClass attribute in configuration. When setting the naming strategy
in configuration, you must provide a full assembly-qualified type name.
Specifying the Hungarian convention in configuration
<add name="Test"
namingStrategyClass="MyApp.HungarianNamingStrategy, MyApp" />
Specifying the Hungarian convention in code
_context.NamingStrategy = new HungarianNamingStrategy();
Mindscape LightSpeed User Guide
84
Reserved Words
Occasionally you will want to use a name in your domain model which is a reserved
word in SQL. The classic example is Order, which clashes with SQL’s ORDER BY
keywords. To prevent errors in this case, set the quoteIdentifiers configuration
attribute, or set LightSpeedContext.QuoteIdentifiers in code.
Mindscape LightSpeed User Guide
85
Overriding Persistence Behaviour
By default, all fields in a LightSpeed entity are persistent: that is, LightSpeed expects to
be able to load them from the database, and includes them when saving the entity.
Excluding a Field From Persistence
Some entities need to hold non-persistent state. For example, an entity might have a
field which caches the result of an expensive calculation, or which represents some UI
status which needs to be passed around with the entity but is not part of its persistent
state. Such fields are known as transient fields.
To mark a field as transient in the designer, set its Transient option to true.
To mark a field as transient in code, apply TransientAttribute to the field.
Excluding a Field From Being Saved
Occasionally you will have a database column that you want to load, but not to save.
The classic example is a computed column: it is useful to have the result of the
computation, but trying to save a value back into that column would cause an error. In
this case you do not want the field to be transient because transient fields are not
loaded from the database – they are not persistent at all. You want the field should be
loaded but not saved.
To exclude a field from being saved in the designer, set its Load Only option to true.
To exclude a field from being saved in code, make the field readonly (ReadOnly in
Visual Basic).
Mindscape LightSpeed User Guide
86
Examples
Overriding persistence behaviour
[Transient]
private EditStatus _editStatus;
// neither loaded nor saved
private readonly decimal _discountedPrice;
// loaded, but not saved
Mindscape LightSpeed User Guide
87
Identity Generation
Every LightSpeed entity has an Id, which LightSpeed uses to uniquely identify the entity.
When a newly created entity becomes part of a unit of work for the first time,
LightSpeed assigns it an Id. LightSpeed has a number of ways of generating Ids for
entities. The default identity method is KeyTable, which is an efficient and portable
way of generating numeric Ids. This section describes how to override the default, and
the alternative options.
Identity Methods in LightSpeed
The available methods of generating Ids are defined in the IdentityMethod
enumeration. LightSpeed supports the following methods of generating Ids:
Identity Method
KeyTable
Id Type
Numeric
Sequence
Numeric
MultiSequence
Numeric
Guid
GuidComb
GUID
GUID
Identity Column
Numeric
Description
Uses a table in the database to store the next Id, and
advances this value every time a new block of Ids is
required.
Similar to KeyTable, but uses a native database
sequence object.
Similar to Sequence, but allows multiple independent
sequences (e.g. sequence per table).
Uses the normal .NET algorithm to create a GUID.
Uses a special algorithm to create GUIDs with special
sorting characteristics that make database indexing
more efficient.
The Id is generated by the database when the entity
is saved, using the database’s built-in identity column
or autoincrement functionality.
Specific methods are discussed in more detail below.
Setting the Identity Method Globally
You will normally choose a single method of generating Id values, which is used for all
entities in your model. This simplifies administration by applying a single consistent
Mindscape LightSpeed User Guide
88
policy across the entire database. To do this, set LightSpeedContext.IdentityMethod in
code, or the identityMethod attribute in configuration.
Specifying the Guid identity method in configuration
<add name="Test"
identityMethod="Guid" />
Specifying the Guid identity method in code
_context.IdentityMethod = IdentityMethod.Guid;
Overriding the Identity Method on a Per-Entity Basis
In a few cases, notably legacy databases where different tables have different identity
types – for example, some tables have GUID primary keys while others have numeric
primary keys – you may need to override the global identity method for specific tables.
To do this, select the entity that is mapped to that table, and set its Identity Method
option. (For hand-coded entities, apply TableAttribute and set the IdentityMethod
property.)
Some legacy databases require that the identity be part of the business data rather
than an opaque value. For information about this, see Using Natural Keys in the
Working with Legacy Databases chapter.
KeyTable Identity Generation
KeyTable identity generation uses a single table in your database that stores the
current identity value. This allows LightSpeed to secure a block of identities and use
them to set the identity value of newly created entities in your system. This works
because every identity in the database, even in different tables, is unique. KeyTable
works with numeric identity types (Int32 and Int64).
Mindscape LightSpeed User Guide
89
KeyTable is a great identity method if you need high performance from you database as
LightSpeed does not need to flush new entities to obtain identity values. That means
less round trips to the database and therefore a more speedy application. KeyTable is
also portable between database engines. For these reasons, KeyTable is the default
identity method in LightSpeed.
By default, KeyTable secures identity values in blocks of 10. You can override this by
setting LightSpeedContext.IdentityBlockSize in code, or the identityBlockSize attribute
in configuration. A large value means LightSpeed needs to consult the key table less
often, which improves performance, but can result in ‘wasted’ Ids if few entities are
added in each run of the program.
If you use the KeyTable method, you must create the key table in the database. To do
this, run the KeyTable.sql schema file for your database, which you can find in your
LightSpeed install directory.
Sequence and MultiSequence Identity Generation
Sequence identity generation is similar to KeyTable, but is natively supplied by the
database engine. This means using sequence identity generation is limited to databases
that support it: Oracle and PostgreSQL. Sequence identity generation has similar
characteristics to KeyTable, but is not portable because many databases do not support
sequence objects.
By default, Sequence and MultiSequence assume that the sequence increment is 10. If
the sequence increment amount is something other than 10, you must set the identity
block size to the sequence increment amount. To do this, set
LightSpeedContext.IdentityBlockSize in code, or the identityBlockSize attribute in
configuration. If the identity block size is different from the sequence increment
amount, this will cause errors.
If you use the Sequence method, you must create the sequence in the database. To do
this, run the Sequence.sql schema file for your database, which you can find in your
LightSpeed install directory. The provided Sequence.sql assumes the default identity
block size of 10; if you change the identity block size you must change the sequence
increment amount and vice versa.
Mindscape LightSpeed User Guide
90
If you use the MultiSequence method, you must create each of the sequences you
intend to use. MultiSequence is typically used with existing databases with a ‘sequence
per table’ policy, in which case the sequences will presumably already exist.
If you use MultiSequence, you must also implement a naming strategy to tell
LightSpeed which sequence to use for each table. Your naming strategy class must
implement the INamingStrategy interface, and return the per-table name from
GetMultiSequenceName. All other members can return the default name.
Specifying sequence names for the MultiSequence identity method
public class SequencePerTableNamingStrategy : INamingStrategy
{
public string GetMultiSequenceName(string defaultName, Type entityType)
{
// Example naming convention: seq_employee_ids, seq_order_ids, etc.
return "seq_" + entityType.Name.ToLower() + "_ids";
}
// other members can all return defaultName (unless using a custom mapping policy)
}
See Defining Your Own Mapping Convention above for how to make LightSpeed use
your naming strategy.
Guid and GuidComb Identity Generation
GUID identities are great for systems that require strong uniqueness (for example, to
support replication) and are also extremely practical from a generation perspective,
because LightSpeed never needs to consult the database to allocate an Id. However,
while this is not an issue for many systems, GUIDs can have an impact on application
aesthetics. For example, you may not want GUIDs present in your URLs if you are
developing a web application. Obviously, GUID methods work only with the Guid
identity type.
The Guid identity method uses the .NET GUID generator to generate Ids.
The GuidComb identity method uses an alternative algorithm, described in Jimmy
Nilsson’s article The Cost of GUIDs as Primary Keys, to trade randomness for database
Mindscape LightSpeed User Guide
91
INSERT performance. The COMB method generates GUIDs in a way that reduces the
indexing cost when a new primary key value is inserted. Consider GuidComb if you
want GUID Ids, have a very large data set and are performing a lot of inserts.
IdentityColumn Identity Generation
Identity Column identity generation supports auto-incrementing identity columns.
Identity Column identity generation is limited to databases that support autoincrement columns: SQL Server, SQL Server Compact, VistaDB, PostgreSQL, MySQL and
SQLite. Identity Column works with numeric identity types (Int32 and Int64).
This approach, while supported, is not a recommended method for identity generation.
Using this identity type means that LightSpeed cannot do optimized batching as well as
it can with other identity methods. This is because LightSpeed needs to obtain the new
identity value and update any in-memory associated objects before continuing the
flush process. Consequently, the IdentityColumn method should be used only with
legacy databases where the auto-increment columns cannot be converted to normal
columns, for example because of other applications that rely on the auto-increment
behaviour.
Identity Generation Options
Some identity methods can be configured in different ways. To specify configuration
options for an identity method, set the LightSpeedContext.IdentityMethodOptions
property. The options object must be appropriate to the identity method in use.
At the time of writing, the only identity methods with configuration options are
Sequence and MultiSequence, which can be configured using a
SequenceIdentityMethodOptions object. See the SequenceIdentityMethodOptions
class documentation in the API reference for information.
How Block Allocation Methods Work
The KeyTable and sequence identity methods reserve Ids in blocks to minimise the
number of database queries required to allocate Ids. Block reservations are held at the
LightSpeedContext instance level. This means that clients that share the same
Mindscape LightSpeed User Guide
92
LightSpeedContext instance will get Ids from the same reserved pool, whereas clients
that create different LightSpeedContext instances will have their own pools.
For example, suppose you are writing a Web application. You might choose to create a
LightSpeedContext per page. This would be wasteful because each page that
performed even a single insert would reserve 10 Ids from the KeyTable or sequence,
incurring its own database call. By contrast, if you created a singleton
LightSpeedContext, for example as a static field in Global.asax.cs, and each page
referred to that one context, then the first page that performed an insert would cause
10 Ids to be reserved, incurring a database call, but subsequent pages would receive Ids
from that reserved block (until it ran out), avoiding further database calls and
improving performance.
In general, therefore, you should have a single LightSpeedContext instance per
application (or one per database, if your application uses multiple databases). Of
course you can and often will create multiple independent units of work from this one
context – in the Web example you would have a unit of work per Web request, even
though the units of work shared the same LightSpeedContext object.
Mindscape LightSpeed User Guide
93
Working with Database Views
There are two ways to use database views. The first is to when you have an entity class
that is backed by a table in the normal way, but you want to load entities through a
view rather than directly from the table. This allows the same entity type to be
materialised from multiple different views, such as AllOrders, OverdueOrders or
RecentOrders. The second is to create an entity class which maps the view, just as if
the view were a table. This allows entities to be materialised from views when there is
no concrete table, for example when a view joins multiple tables to provide a businessmeaningful presentation of denormalised data.
In both cases, the view must contain a column containing unique identifying values. As
with a table, this column must be called Id, or must be mapped using the Identity
Column Name option or TableAttribute.IdColumnName.
Loading Entities Through a View
If an entity backs onto a table, but you sometimes want to load instances through a
predefined view for performance reasons, you can associate the view with the entity by
dragging the view from Server Explorer onto the entity shape. This adds a property to
the unit of work representing a query through that view. The property’s name is the
view name. The property can be used as follows:
Querying through a view using LINQ
// ProductsByPriceDescending is a view over the Product table
using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
var mostExpensiveProducts = unitOfWork.ProductsByPriceDescending
.Take(100)
.ToList();
}
You can also query a view using query objects, bypassing the designer and the
generated helper property. To do this, set Query.ViewName.
Mindscape LightSpeed User Guide
94
Querying through a view using query objects
// ProductsByPriceDescending is a view over the Product table
using (IUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
Query query = new Query
{
ViewName = "ProductsByPriceDescending",
Page = Page.Limit(100)
};
var mostExpensiveProducts = unitOfWork.Find<Product>(query);
}
We also offer this ability to perform ad-hoc queries using views if you are using LINQ
through the WithViewName extension method.
Querying through a view using the WithViewName extension method
// ProductsByPriceDescending is a view over the Product table
using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
var mostExpensiveProducts = unitOfWork.Products
.WithViewName(“ProductsByPriceDescending”)
.Take(100)
.ToList();
}
In either case, the view must have the same schema as the table because you are
loading it into the same type of entity. Entities loaded through a view can be modified
or deleted in the normal way, with the changes being applied via the underlying table.
Creating an Entity Class to Map a View
If you only have a view – that is, there is no backing table, or you don’t have access to
the backing table – then you can load entities from the view by creating an entity class
that maps the view. To do this, drag the view onto the designer surface (that is, the
background). The designer infers an entity class from the view columns just as it would
Mindscape LightSpeed User Guide
95
from table columns, and queries for this kind of entity will be passed to the view just as
if the entity were backed by a table.
In most cases, you will not be able to save changes to entities that are backed by views,
because databases generally do not allow inserts, updates or deletes against views.
You may therefore wish to mark the entity fields as Load Only so that application code
can’t erroneously make changes. However, if your database supports updateable
views, and if your view is updateable, then you can save changes against it in the
normal way.
Because a view does not have a primary key, if the identity column is not called Id then
the designer will not infer the identity column name. You will need to fix this up
manually in this case.
Mindscape LightSpeed User Guide
96
Invoking Stored Procedures
It is sometimes necessary to encapsulate very complex queries as stored procedures.
You can invoke a stored procedure from LightSpeed either to load a set of entities, to
calculate a single value such as a count or total, or just to execute it with no return
value.
To add a stored procedure to the designer, drag it onto the designer surface.
LightSpeed will create a SelectProcedure, ScalarProcedure or NonQueryProcedure
accordingly depending on the result schema of the procedure. For a SelectProcedure,
LightSpeed tries to identify the type of entity being returned; if no existing entity is
suitable then LightSpeed creates a new entity class based on the result schema. In any
case, LightSpeed generates a method on the unit of work to invoke the procedure.
Invoking a Stored Procedure Using LINQ
To invoke a stored procedure, call the appropriate method on the unit of work.
Invoking stored procedures using LINQ
using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
// SelectProcedure returns a collection of entities
var expensiveProducts = unitOfWork.GetProductsCostingMoreThan(10000);
// ScalarProcedure returns a value
var totalPendingOrderValue = unitOfWork.GetTotalPendingOrderValue();
// NonQueryProcedure does not return a value
unitOfWork.ArchiveInactiveCustomers(DateTime.Now.AddMonths(-6));
}
If the stored procedure has output or input-output parameters, these will appear as
out or ref parameters to the method.
Mindscape LightSpeed User Guide
97
Invoking a Stored Procedure Using Query Objects
If you are not using the designer, you must use query objects to invoke a stored
procedure. (The LINQ methods shown above use query objects internally, but are
generated by the designer so that you do not need to work with the query objects
directly.)
The key class for using stored procedures with query objects is ProcedureQuery.
ProcedureQuery encapsulates the name of the procedure and any parameters you
want to pass to it. You can pass a ProcedureQuery to one of three methods defined on
IUnitOfWork:



To load entities, call the Find method.
To get a single scalar value, call the Calculate method.
To execute an action (no return value), call the Execute method.
Invoking stored procedures using query objects
using (IUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
// Loading entities
ProcedureQuery entityQuery = new ProcedureQuery("GetProductsCostingMoreThan",
new ProcedureParameter("minCost", 10000));
var expensiveProducts = unitOfWork.Find<Product>(entityQuery);
// Getting a single value
ProcedureQuery valueQuery = new ProcedureQuery("GetTotalPendingOrderValue");
var totalPendingOrderValue = unitOfWork.Calculate(valueQuery);
// Executing an action
ProcedureQuery actionQuery = new ProcedureQuery("ArchiveInactiveCustomers",
new ProcedureParameter("olderThan", DateTime.Now.AddMonths(-6)));
unitOfWork.Execute(actionQuery);
}
The query object API uses ProcedureParameter objects to represent stored procedure
parameters. You can declare parameters with direction Output, InputOutput or
ReturnValue to receive values returned by the stored procedure through parameters or
as the return value.
Mindscape LightSpeed User Guide
98
Database Considerations for Stored Procedures
When loading entities through a stored procedure, the returned record set is treated
exactly the same as if it had been returned by performing a table SELECT. Therefore:


The set of columns in the record set must correspond in name and type to the
fields of the entity. The Column Name setting (or ColumnAttribute) is respected
as normal.
The returned record set must also contain an Id column. If the Identity Column
Name is set for the entity (or the entity has TableAttribute.IdColumnName),
then the returned record set must contain a column with this name instead.
When using stored procedures on Oracle, you must follow a special convention for
returning results. See the chapter Working with Database Providers for more
information.
Additional Support for Stored Procedures
For information about other ways of using stored procedures with LightSpeed, see the
chapter Working with Legacy Databases.
Mindscape LightSpeed User Guide
99
Building Applications with LightSpeed
You can use LightSpeed in a variety of application architectures, from Web pages to
rich clients and service-oriented distributed applications. In the following chapters,
we’ll discuss in more detail how to use LightSpeed in specific scenarios. First, however,
we need to address some areas which are common across application architectures.
Mindscape LightSpeed User Guide
100
Configuration
You can configure LightSpeed through code or through the configuration file
(web.config or appname.exe.config depending on the type of application). We
discussed the core configuration options in the Basic Operations chapter. In this
section we will review other configuration options.
For a full list of configuration settings, see the Appendices.
How to Configure LightSpeed
In general, it is best to configure LightSpeed through the configuration file. This
ensures that all instance-specific configuration is externalised from the code, so that
the code can be easily moved between different environments.
As mentioned in the Basic Operations chapter, to use the configuration file you must
declare and implement a lightSpeedContexts section. You can then define particular
configurations by adding elements to that section, using the add tag:
Declaring the lightSpeedContexts section
<configSections>
<section name="lightSpeedContexts"
type="Mindscape.LightSpeed.Configuration.LightSpeedConfigurationSection,
Mindscape.LightSpeed" />
</configSections>
Implementing the lightSpeedContexts section
<lightSpeedContexts>
<add name="Test" />
</lightSpeedContexts>
<!-- We will add attributes here shortly -->
Individual configuration options are specified as attributes of the add tag.
Mindscape LightSpeed User Guide
101
A few settings are not available through the configuration file. These settings can only
be configured in code. These are typically advanced settings such as custom strategy
classes which do not need to change between environments.
Loading the Configuration
A configuration is represented by a LightSpeedContext object. To load a
LightSpeedContext from configuration, pass the name of the configuration file entry to
the LightSpeedContext constructor:
Loading a LightSpeedContext from configuration
private static readonly LightSpeedContext _context = new LightSpeedContext("Test");
You can still modify the context object in code by setting its properties, for example if
you need to apply a setting which is not available through configuration.
Setting Up a LightSpeedContext in Code
You can create a blank LightSpeedContext using the default constructor:
private static readonly LightSpeedContext _context = new LightSpeedContext();
This will not load any settings from the configuration file. You can then set up the
context object by settings its properties.
You will usually use code-only setup only for prototypes and tests, because it leads to
maintenance headaches when the code needs to be moved between environments.
Context Per Application, Not Context Per Request
In general, you should not create multiple instances of LightSpeedContext. As shown
above, create a single (static) instance, and share it across your entire application. You
will not usually modify a context object after initialisation, because your configuration
Mindscape LightSpeed User Guide
102
will not change during a run. So it is safe to share a context object even across a highly
concurrent application such as a Web site.
By a single instance, we mean a single instance per database. If your application talks
to two databases – for example copying data between them – then of course you will
need two contexts. The point is that you should not create new contexts on every page
or every screen: you should reuse the same context instance or instances throughout
the lifetime of your application.
Preferring the singleton pattern is not just a matter of not creating superfluous objects
– redundant contexts can affect performance, for example resulting in superfluous
database queries to allocate entity Ids (see Identity Generation in the chapter
Controlling the Database Mapping).
There are some cases where you may want to create multiple contexts. Generally we
have found these would be either:


You are connecting to more than one data source, or
You are dealing with a multi-tenanted scenario and need to customize the
connection string per tenant
Terminology
If you’re familiar with LINQ to SQL or the Entity Framework, you may associate the
term ‘context’ with a domain model or database session, as in the LINQ to SQL
DataContext. A LightSpeedContext is not like a DataContext: it holds configuration
settings, not database data. The LightSpeed equivalent of a DataContext or
ObjectContext is a unit of work.
Mindscape LightSpeed User Guide
103
Configuration Settings
There are a lot of configuration settings on LightSpeedContext, which are described in
more detail in the relevant section and listed in the Appendices. This section lists some
of the most commonly used.
Attribute
Usage
connectionStringName
The database connection string. The actual string is looked
up from the name in the <connectionStrings> section. See
the Basic Operations chapter for more information.
dataProvider
The type of database engine. See the Basic Operations
chapter or the DataProvider enumeration for a list.
pluralizeTableNames
Indicates that LightSpeed should map entity classes to table
names using the plural form of the class name. For example,
a Person class would normally map to a Person table, but
with pluralizeTableNames in effect it would map to a People
table.
quoteIdentifiers
Quotes table and column names in SQL. This avoids conflicts
with reserved words, but causes some databases to behave
in a case-sensitive way, which can lead to mapping
problems.
schema
The database schema in which the entity tables are found.
identityMethod
Specifies how LightSpeed assigns Ids to new entities. See
Identity Generation in the Controlling the Database Mapping
chapter.
loggerClass
The type of logger to which LightSpeed should log SQL
statements and diagnostic and performance information.
See Logging in the Testing and Debugging chapter for more
information.
All of these (and the other) configuration settings have equivalent properties on the
LightSpeedContext class.
Mindscape LightSpeed User Guide
104
Localizing LightSpeed Messages
All of LightSpeed’s localizable resources such as error and validation messages are kept
in a single resources file (Resources.resx). This file is made available as part of the
LightSpeed installation and can be found in the Localization folder under the
LightSpeed installation directory.
To localize LightSpeed messages, translate the messages in Resources.resx, build the
translation into a satellite DLL and deploy the satellite DLL alongside your application.
A sample illustrating how to produce and deployed a localized satellite resource
assembly can be found in the Samples folder.
Localizing Property Names
Although you can localize messages such as validation messages via a satellite
assembly, in some cases, specifically validation, these messages contain placeholders
which LightSpeed will fill in with property names – that is, the same names you use in
your code. Normally, as per the ‘ubiquitous language’ of domain driven development,
you will use property names which are familiar to your users. But if you are serving
users in multiple locales then you may need to display different property names in each
locale, so you will have to convert from the .NET property names to localized display
names.
To do this, create an implementation of the IDisplayNamingStrategy interface to
perform the translation and return the translated name. Here is an example which
simply looks up the property name in the application resources, allowing you to localize
names through a satellite DLL.
Mindscape LightSpeed User Guide
105
Implementing IDisplayNamingStrategy
public class ResourceLookupStrategy : IDisplayNamingStrategy
{
public string GetDisplayName(string defaultName, Type entityType)
{
string resourceKey = entityType.Name + "_" + defaultName;
return ResourceManager.GetString(resourceKey); // uses current culture
}
}
To use this strategy, set LightSpeedContext.DisplayNamingStrategy in code, or the
displayNamingStrategyClass attribute in configuration.
Specifying a display naming strategy in configuration
<add name="Test"
displayNamingStrategyClass="MyApp.ResourceLookupStrategy, MyAssembly" />
Specifying a display naming strategy in code
_context.DisplayNamingStrategy = new ResourceLookupStrategy();
Mindscape LightSpeed User Guide
106
Customising How LightSpeed Connects to the
Database
By default, a LightSpeed unit of work maps to a database connection. The unit of work
opens the connection the first time it needs to talk to the database, and keeps it open
until you dispose the unit of work.
This usage reflects the recommended pattern of keeping units of work short-lived. For
example, in a Web application using ‘unit of work per request,’ the unit of work lives
just long enough to serve a page, so the database connection is quickly closed as well.
Some applications, particularly rich clients, use long-running units of work. This can
create problems for the ‘connection per unit of work’ strategy, because a large number
of idle clients can starve the database of connections, and because it increases the
chance of the connection going into a faulted state, leaving the unit of work saddled
with an unusable connection.
For cases such as these you can override the ‘connection per unit of work’ pattern by
implementing a custom connection strategy. You can use this to gain finer control over
how LightSpeed uses connections.
Implementing a Custom Connection Strategy
To implement a connection strategy, create a class that derives from
ConnectionStrategy and overrides the Connection property and the Dispose(bool)
method. Here is a simple connection strategy that opens a connection whenever it
needs one, and leaves it open just like the default behaviour, but also allows
application code to forcibly close the connection via a CloseConnection method.
Mindscape LightSpeed User Guide
107
Implementing ConnectionStrategy
public class CloseableConnectionStrategy : ConnectionStrategy
{
private IDbConnection _connection;
public CloseableConnectionStrategy(LightSpeedContext context)
: base(context)
{
}
protected override IDbConnection Connection
{
get
{
if (_connection == null)
{
_connection = Context.DataProviderObjectFactory.CreateConnection();
_connection.ConnectionString = Context.ConnectionString;
_connection.Open();
}
return _connection;
}
}
public void CloseConnection()
{
if (_connection != null)
{
_connection.Dispose();
_connection = null;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_connection != null)
{
_connection.Dispose();
_connection = null;
}
}
base.Dispose(disposing);
}
}
Mindscape LightSpeed User Guide
108
Using a Custom Connection Strategy
To use a custom connection strategy, set the UnitOfWork.ConnectionStrategy property
to an instance of your custom strategy.
Using a custom connection strategy
using (ModelUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
unitOfWork.ConnectionStrategy = new CloseableConnectionStrategy(_context);
}
You must do this before the unit of work first connects to the database – otherwise the
unit of work will use the default connection strategy. Do not change connection
strategies in the middle of a unit of work. If desired, you can set up the connection
strategy in the constructor of a strong-typed unit of work, or through
LightSpeedContext.UnitOfWorkFactory.
Note that the unit of work will not invoke any actions on your custom strategy other
than to get connections and to notify various operations. It is up to application code to
do this. For example, if you want to use the CloseableConnectionStrategy to close the
connection after loading a screen, you might write something like this:
private void OnLoad()
{
LoadDataToScreen();
// We know we won't be needing the database again for a while, so drop the connection
((CloseableConnectionStrategy)(_unitOfWork.ConnectionStrategy)).CloseConnection();
}
The connection is released, but the unit of work remains live, and will automatically
reinitiate a connection when required – for example, if you load new entities, traverse
an association, or call SaveChanges.
Note that some LightSpeed tasks, such as traversing an association, internally require a
connection. Your connection strategy will still be used in such cases, but your
Mindscape LightSpeed User Guide
109
application code may not be aware of it. For example, a data binding that traverses an
association could cause your strategy to reinitiate a connection: this connection would
remain open, and your application code would not know about it in order to reclaim it.
If this is a concern, you can override
ConnectionStrategy.OnDatabaseOperationComplete to receive notifications of
LightSpeed database activity, but care is required that you do not close a connection
while LightSpeed is still using it.
Mindscape LightSpeed User Guide
110
Building Web Applications
You can use LightSpeed in both ASP.NET Web Forms and ASP.NET MVC web
applications allowing you to develop in terms of your domain model and allow
LightSpeed to manage the data access. Developing with LightSpeed in the context of a
web application is largely the same as any other type of application; however the key
point of difference is around the way in which you will want to scope your UnitOfWork.
For web applications you will be concerned with scoping your UnitOfWork on a per
request basis and how to perform data access within your code behind or controller
code. This chapter will highlight how to cover both of these concerns in the context of
both ASP.NET Web Forms and ASP.NET MVC applications. Additionally if you are
deploying into a medium trust environment (this is often the case with shared hosting
providers) then you will want to review the section detailing specific changes you will
need to make to your LightSpeed configuration and designer settings to support
medium trust scenarios.
Mindscape LightSpeed User Guide
111
Building ASP.NET Web Forms Applications
Unit of Work Scoping
To scope your UnitOfWork instances on a per request basis you will want to make use
of the PerRequestUnitOfWorkScope<TUnitOfWork> helper class which is included as
part of the Mindscape.LightSpeed assembly.
First you will want to declare your LightSpeedContext so it is accessible for creating
UnitOfWork instances. We recommend that this is done as part of your Global.asax.cs
so it is always available.
Example declaration of a static LightSpeedContext in Global.asax.cs
public static LightSpeedContext<MyModelUnitOfWork> LightSpeedContext
= new LightSpeedContext<MyModelUnitOfWork>("Development");
We would then recommend that you set up a base class for your pages (or if you are
using a controller/presenter approach then in your base class for your
controllers/presenters) which can leverage the
PerRequestUnitOfWorkScope<TUnitOfWork> to provide you with access to your
UnitOfWork.
Example use of PerRequestUnitOfWorkScope<TUnitOfWork> in your base class
public class PageBase : System.Web.UI.Page
{
private PerRequestUnitOfWorkScope<ModelUnitOfWork> _unitOfWorkScopeHolder;
public PageBase()
{
_unitOfWorkScopeHolder =
new PerRequestUnitOfWorkScope<ModelUnitOfWork>(Global.LightSpeedContext);
}
}
Mindscape LightSpeed User Guide
112
The PerRequestUnitOfWorkScope class holds instances of your typed UnitOfWork
instances in the HttpContext.Items collection so they will be available for the duration
of the request.
The UnitOfWork will only be instantiated once you call the .Current property on the
PerRequestUnitOfWorkScope instance so it is safe to instantiate the holder class early
and set up a property accessor to pass through to the .Current property on the holder.
Example declaration of a UnitOfWork property on your base class
public ModelUnitOfWork UnitOfWork
{
get { return _unitOfWorkScopeHolder.Current; }
}
Finally you will need to dispose the UnitOfWork instance at the end of the request to
ensure the database connection is released. We recommend that this is done as part of
the EndRequest event and that you hook this in your Global.asax.cs
Example of PerRequestUnitOfWorkScope<TUnitOfWork> disposal code
protected void Application_EndRequest(object sender, EventArgs e)
{
var scope = new PerRequestUnitOfWorkScope<ModelUnitOfWork>(LightSpeedContext);
if (scope.HasCurrent)
{
scope.Current.Dispose();
}
}
Validation
LightSpeed provides a rich, extensible object-level validation framework. For more
details about the validation framework in LightSpeed please review the Validation
section in the Creating Domain Models chapter.
Mindscape LightSpeed User Guide
113
When you approach validation with ASP.NET you will be interested in handling both
client and server side validation concerns. You will implement your client side
validation concerns manually and then you can use the .IsValid property in your
postback events to determine if your updated LightSpeed entity is valid.
Example of testing a LightSpeed entity for validity on postback
// your manual assignment code would go here
if (!MyEntityInstance.IsValid)
{
// triggering any additional validation messages would go here
return;
}
You may also wish to surface the validation error messages for your entity and bind
those to a summary on the page. The pattern we would generally recommend for this
is to expose an Errors property on your page which returns a LightSpeed
ValidationErrorsCollection instance. This can then be returned from any of your entities
which are invalid.
Mindscape LightSpeed User Guide
114
Example of testing a LightSpeed entity for validity on postback
public Member Member
{
get
{
if (_member == null)
{
_member = new Member();
}
return _member;
}
set
{
_member = value;
}
}
public ValidationErrorCollection ValidationErrors
{
get { return Member.IsValid ? null : Member.Errors; }
}
// Example of binding the ValidationErrors collection on the page
<% if (IsPostBack && ValidationErrors != null)
{ %>
<div id="errors">
<h2>Remaining Details Required:</h2>
<ul>
<asp:Repeater runat="Server" ID="ErrorsRepeater">
<ItemTemplate>
<li><%# System.Web.UI.DataBinder.Eval(Container.DataItem, "ErrorMessage") %></li>
</ItemTemplate>
</asp:Repeater>
</ul>
</div>
<% } %>
Data Binding using EntityDataBinder
The Mindscape.LightSpeed.Web assembly contains a simple two way data-binder
which can be used to help perform binding to and from your LightSpeed entities. To
use the data binder you need to describe the binding behavior between form fields and
data accessible to your page instance.
Mindscape LightSpeed User Guide
115
An example describing a two way data-binding between two fields on a form and a
Member entity which has been exposed on the page instance
<lightspeed:EntityDataBinder runat="Server" ID="DataBinder">
<lightspeed:EntityDataBindingItem runat="Server"
BindingMode="TwoWay" BindingSource="Member" BindingSourceMember="Username"
TargetControl="SignUpUsername" TargetControlProperty="Text" />
<lightspeed:EntityDataBindingItem runat="Server"
BindingMode="TwoWay" BindingSource="Member" BindingSourceMember="FirstName"
TargetControl="SignUpFirstName" TargetControlProperty="Text" />
</lightspeed:EntityDataBinder>
An example which would bind validation errors to an errors repeater
<lightspeed:EntityDataBinder runat="Server" ID="DataBinder">
<lightspeed:EntityDataBindingItem runat="Server" BindingMode="OneWay"
BindingSource="this" BindingSourceMember="ValidationErrors"
TargetControl="ErrorsRepeater" TargetControlProperty="DataSource" />
</lightspeed:EntityDataBinder>
Like other data-bound controls in ASP.NET you need to explicitly call .DataBind() on the
control to ask it to perform a data-binding operation. Because the source data is
specified as part of the declarative mark-up for the control there is no DataSource
property which needs to be assigned.
For unbinding data back to your entities you will need to call .Unbind() in your postback
events after which it is sensible to validate your objects and then deal with any
resulting errors.
Mindscape LightSpeed User Guide
116
An example of calling DataBind and Unbind
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DataBinder.DataBind();
}
}
protected void SignUpButton_Click(object sender, EventArgs e)
{
DataBinder.Unbind();
if (!Member.IsValid)
{
DataBinder.DataBind();
return;
}
}
Samples
For example code you can review the Aptitude Test sample which has been installed
alongside LightSpeed.
Mindscape LightSpeed User Guide
117
Building ASP.NET MVC Applications
Unit of Work Scoping
To scope your UnitOfWork instances on a per request basis you will want to make use
of the PerRequestUnitOfWorkScope<TUnitOfWork> helper class which is included as
part of the Mindscape.LightSpeed assembly.
First you will want to declare your LightSpeedContext so it is accessible for creating
UnitOfWork instances. We recommend that this is done as part of your Global.asax.cs
so it is always available.
Example declaration of a static LightSpeedContext in Global.asax.cs
public static LightSpeedContext<MyModelUnitOfWork> LightSpeedContext
= new LightSpeedContext<MyModelUnitOfWork>("Development");
We recommend that you use the LightSpeedControllerBase<TUnitOfWork> class which
is provided as part of the Mindscape.LightSpeed.Web assembly as the base for your
controllers, as this provides a per request scoped UnitOfWork which uses
PerRequestUnitOfWorkScope<TUnitOfWork> and handles the disposal of any
UnitOfWork instances on your behalf by default.
Example controller declaration using LightSpeedControllerBase<TUnitOfWork>
public class MyController : LightSpeedControllerBase<ModelUnitOfWork>
{
protected override LightSpeedContext<ModelUnitOfWork> LightSpeedContext
{
get { return MvcApplication.LightSpeedContext; }
}
}
Mindscape LightSpeed User Guide
118
By default the LightSpeedControllerBase will dispose of any created UnitOfWork
instance as part of the OnResultExecuted handler or alternatively when the controller
instance is disposed. If you wish to manually control the disposal behaviour, for
example you may wish to dispose it at the end of the request to allow further access to
the UnitOfWork instance through an external
PerRequestUnitOfWorkScope<TUnitOfWork>, then you will need to set the
DisposeUnitOfWorkOnResultExecuted property to false to disable the automatic
behaviour.
Example of manual disposal code using the EndRequest event
public class MyController : LightSpeedControllerBase<ModelUnitOfWork>
{
public MyController()
{
DisposeUnitOfWorkOnResultExecuted = false;
}
}
// This code would reside in Global.asax.cs
protected void Application_Start(object sender, EventArgs e)
{
EndRequest += new EventHandler(OnEndRequest);
}
void OnEndRequest(object sender, EventArgs e)
{
var scope = new PerRequestUnitOfWorkScope<ModelUnitOfWork>(LightSpeedContext);
if (scope.HasCurrent)
{
scope.Current.Dispose();
}
}
Model Binding for LightSpeed Entities
If you are using the DefaultModelBinder which ships with ASP.NET MVC you may wish
to improve the experience in binding your LightSpeed entities. By default the
DefaultModelBinder will interrogate the Errors property on a LightSpeed entity causing
any errors to be concatenated together.
Mindscape LightSpeed User Guide
119
The Mindscape.LightSpeed.Web assembly includes a custom model binder called
LightSpeedEntityModelBinder which can be used with you ASP.NET MVC projects for
binding LightSpeed entities. To register this model binder to just handle the entity
types in your project, call the static LightSpeedEntityModelBinder.Register method,
passing in the assembly which contains your entity types. Only types which derive from
Mindscape.LightSpeed.Entity in that assembly will be associated with the
LightSpeedEntityModelBinder.
Configuring the use of the ModelBinder in Global.asax.cs
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
LightSpeedEntityModelBinder.Register(typeof(ModelUnitOfWork).Assembly);
}
One it has been set up your entities can be unbound either when unbinding takes place
for an action argument or manually when you call UpdateModel. The main limitation to
be aware of with the LightSpeedEntityModelBinder is that it will not traverse any
loaded associations.
An alternative to using the custom model binder provided with LightSpeed is to create
your own model binder, likely sub-classing DefaultModelBinder to afford yourself the
benefits it already provides. If you take this approach then you will primarily be
interested in correctly handling any errors after the model binding takes place. This can
be achieved as shown below.
Mindscape LightSpeed User Guide
120
An example of implementing BindModel when sub-classing DefaultModelBinder and
handling extracting error messages from your LightSpeed entity
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
object result = base.BindModel(controllerContext, bindingContext);
if (typeof(Entity).IsAssignableFrom(bindingContext.ModelType))
{
Entity entity = (Entity)result;
if (!entity.IsValid)
{
foreach (var state in bindingContext.ModelState
.Where(s => s.Value.Errors.Count > 0))
{
state.Value.Errors.Clear();
}
foreach (var error in entity.Errors)
{
if (error.ErrorMessage.EndsWith("is invalid"))
continue;
bindingContext.ModelState.AddModelError(error.PropertyName ?? "Custom",
error.ErrorMessage);
}
}
}
return result;
}
Validation
When you approach validation with ASP.NET MVC you will be interested in handling
both client and server side validation concerns. The standard approach for handling
validation concerns with ASP.NET MVC is to use Data Annotation attributes. While
LightSpeed has its own native validation framework you can use the
LightSpeedMvcValidatorProvider which is part of the Mindscape.LightSpeed.Web
assembly to automatically supply the appropriate Data Annotation attributes needed
to allow automatic validation to be applied by ASP.NET MVC.
Mindscape LightSpeed User Guide
121
To make use of this you need to first add a new instance of the
LightSpeedMvcValidatorProvider to your ModelValidatorProviders.Providers collection,
typically this would be performed in your Global.asax.cs.
Initializing the LightSpeed Validation Provider in Global.asax.cs
protected void Application_Start()
{
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LightSpeedMvcValidatorProvider());
}
The provider does not surface all LightSpeed validations because the Data Annotations
framework does not contain equivalents for all of them. The following LightSpeed
validations are surfaced:




ValidatePresenceAttribute
ValidateLengthAttribute
ValidateRangeAttribute (int and double ranges only)
ValidateFormatAttribute
For server side validation you will likely combine the use of a model binder with the
integrated LightSpeed validation to check if an entity is in a valid state following a
binding operation. Again for this we would check .IsValid. If you are using a model
binder as described in the previous section then any errors will be assigned into
ModelState so you can display these when you refresh your view.
Mindscape LightSpeed User Guide
122
An example of checking IsValid following a binding operation using UpdateModel
try
{
UpdateModel(myEntity);
}
catch (InvalidOperationException)
{
updateModelErrored = true;
}
if (updateModelErrored || !myEntity.IsValid)
{
// handle validation/binding failure
}
Samples
For example code, see the TV Guide MVC 2 sample or the Deals MVC 3 sample both of
which have been installed alongside LightSpeed.
Mindscape LightSpeed User Guide
123
Running LightSpeed in Medium Trust
LightSpeed can be used in a medium trust environment, but requires some
performance optimisations to be turned off, and some design conventions to be
modified.
Specifically you will need to apply the following changes.



Set LightSpeedContext.UseMediumTrustCompatibility to true. This is a static
property and therefore applies to all LightSpeedContexts in the AppDomain
(trust is set at an AppDomain level, so there would be no point allowing
different trust levels on different contexts).
If you are writing your model in code, declare special fields such as CreatedOn,
and all fields in value objects, to be read-write instead of the normal read-only.
(You should still not provide property setters for these fields: application code
should still treat them as immutable.)
If you are using the designer, set Medium Trust Compatibility to true on the
model. This tells the designer to generate special fields and value object fields
to be read-write instead of read-only.
Mindscape LightSpeed User Guide
124
ASP.NET Dynamic Data
Beyond using LightSpeed as the domain model in a web application, your LightSpeed
entities can be used to create ASP.NET Dynamic Data sites.
To create a Dynamic Data site using LightSpeed:





Create an ASP.NET Dynamic Data site using the Visual Studio New Project
command.
Add a LightSpeed model to the project.
Add a reference to Mindscape.LightSpeed.Web.DynamicData.dll.
Open the DynamicData > PageTemplates directory. For each .aspx page in this
directory:
Add the following directive immediately below the @Page directive
<%@Register TagPrefix="ls" Assembly="Mindscape.LightSpeed.Web.DynamicData"
Namespace="Mindscape.LightSpeed.Web.DynamicData" %>


Find any references on the page to asp:LinqDataSource and change them to
ls:LightSpeedLinqDataSource. Do not change any attributes or other content.
Declare a LightSpeedContext<MyModelUnitOfWork>. This is typically declared
as a static read-only field in Global.asax.cs.
Example declaration of a static LightSpeedContext in Global.asax.cs
public static LightSpeedContext<MyModelUnitOfWork> LightSpeedContext
= new LightSpeedContext<MyModelUnitOfWork>("Development");

In Global.asax.cs, locate the section entitled “IMPORTANT: DATA MODEL
REGISTRATION” and add the following line in place of the commented-out call
to model.RegisterContext:
Mindscape LightSpeed User Guide
125
Dynamic Data Model Registration
model.RegisterContext(
new LightSpeedDataModelProvider<MyModelUnitOfWork>(LightSpeedContext),
new ContextConfiguration
{
ScaffoldAllTables = true,
MetadataProviderFactory = t =>
new EntityDataAnnotationProvider(
new AssociatedMetadataTypeTypeDescriptionProvider(t))
}
);
Once you have completed these steps your Dynamic Data site will be configured to use
the LightSpeed data source. You can then customise the appearance of your site and
your entities using the usual Dynamic Data customisation techniques.
Handling Associations
When presenting associations, ASP.NET Dynamic Data calls ToString() on the associated
object. The ToString() implementation in the Entity class returns a generic string which
is not very friendly to end users. You should override ToString() in any class you want to
display on a Dynamic Data site.
Validation
By default, ASP.NET Dynamic Data does not recognise LightSpeed validation attributes.
Although validations still run, they run only on the server and ASP.NET Dynamic Data
does not display validation error messages in a usable form.
To enable integrated ASP.NET Dynamic Data validation, including client-side validation
and error message display, when registering the data context, set the
ConfigurationContext.MetadataProviderFactory to a call back which creates an
instance of EntityDataAnnotationProvider. You will need to add a reference to
Mindscape.LightSpeed.Web to bring this class into scope.
Mindscape LightSpeed User Guide
126
Dynamic Data Model Registration
model.RegisterContext(
new LightSpeedDataModelProvider<TestUnitOfWork>(LightSpeedContext),
new ContextConfiguration
{
ScaffoldAllTables = true,
MetadataProviderFactory = t =>
new EntityDataAnnotationProvider(
new AssociatedMetadataTypeTypeDescriptionProvider(t))
}
);
(Please notice the use of AssociatedMetadataTypeTypeDescriptionProvider as an
“inner” provider. This is required to enable “buddy classes” to work in cases where
these are needed.)
EntityDataAnnotationProvider does not surface all LightSpeed validations because
ASP.NET Dynamic Data does not contain equivalents for all of them. Validations which
are not surfaced still run, but without the integrated user experience. The following
LightSpeed validations are surfaced:




ValidatePresenceAttribute
ValidateLengthAttribute
ValidateRangeAttribute (int and double ranges only)
ValidateFormatAttribute
In addition, EntityDataAnnotationProvider depends on the use of designer naming
conventions to locate validations (because LightSpeed validations are specified on
fields but must be surfaced on properties). That is, the backing field for each property
must have the same name as the property, prefixed with an underscore. If you have
hand-coded entities which do not follow this convention, you will need to add Dynamic
Data validation attributes to your properties by hand.
Mindscape LightSpeed User Guide
127
Building WPF and Windows Forms
Applications
Desktop client development has some significant architectural differences when
compared to Web applications. These differences vary from the nature of scoping your
database connections through to data binding concerns and validation behaviours.
This section discusses some potential boundaries for a unit of work, tips for dealing
with long running units of work and what core LightSpeed features will give you a
productivity boost on the desktop.
Mindscape LightSpeed User Guide
128
Unit Of Work Scoping
The key difference with desktop applications is that there is the potential for them to
sit open and active for long periods of time – hours, days or weeks even. In these
situations, it’s obvious that you do not want to necessarily be maintaining an open
database connection the entire time.
It is important to try and identify or create the boundaries that your application will use
to scope data access. In a sense, you’re creating the same type of fundamental work
patterns as the request/response type nature of the web but based on your own
application workflow. The benefits of doing this are that your application code is
maintainable and your entity life cycle management is simple to work with.
Build with Design Patterns
While all applications should use a higher level design pattern for structure such as
MVP, MVVM or MVC, it’s doubly important for desktop applications as they otherwise
do not enforce any natural boundaries (such as managing the request/response life
cycle). It takes discipline to take the freedom of the desktop and impose your own
boundaries and stick to them.
The Unit of Work design pattern works best when tied to a certain section of actions to
be undertaken. In an MVC structured application, for example, this means that while
the rest of your application is doing various things, you are likely to only need to start
and finish a unit of work in your controller – usually in very short lived manner. A short
lived unit of work is the best way to use the pattern.
LightSpeed does not enforce that you use these patterns but you will find natural
scoping boundaries more easily if you do work with these structural patterns.
Working with Dialogs
One natural boundary in a desktop application is a pop up window or dialog. Take for
example an application where you may wish to edit an employee’s details. The user
double clicks an employee, a dialog appears and they can edit any of the employee
details. They can then elect to save those changes or cancel the operation.
Mindscape LightSpeed User Guide
129
Here’s how you may wish to structure this type of interaction:






Controller uses a unit of work to fetch the employee to edit
Controller closes the unit of work
Controller creates the view and exposes a model which the view can bind
against (in this case, we could bind directly to our employee entities properties)
At some later stage, after editing, the user clicks “Cancel” then the dialog
closes.
Or, at some later stage, after editing, the user clicks “Save” then the controller
creates a new short lived unit of work, attaches the entity, and saves the
changes.
Everything is nicely and neatly done!
This represents an easy way to work with short lived units of work along with the
boundaries enforced by your higher level design pattern to ensure you have a nice and
clean way of working with entities. Of course, application specifics may be different –
edit inline, certain users can edit only certain fields etc. – but the guidance above is a
good starting point no matter what the user workflow.
If the user is going to work with a complex graph of objects all within one dialog (or
other boundary), you’ll need to be wary of items that could be lazy loaded, or
otherwise require an active unit of work to be associated with the entity. If you wish to
make use of these capabilities you’ll need to look at using a longer running unit of work
so the entity can maintain a connection to the database. See Long Running Units of
Work below for guidance on this.
Concurrency
If you’re developing a multi-user system you will want to think about concurrency. For
example, you may enable optimistic concurrency checking and trap that exception on
save if another user has edited the same entity between you loading it and saving it.
How you deal with such a concurrency violation would be application specific but it is a
situation you will need to address. See Concurrent Editing in the chapter Implementing
Storage Policies with LightSpeed for more information.
Mindscape LightSpeed User Guide
130
Using Refresh Buttons
Often when building a desktop application there is the presumption that you can
maintain a live data feed in the UI because you are removed from the
request/response process. However, it’s wise to avoid this if you can, because
rebinding entire grids or views means that you end up fighting the nuances of the
user’s state (“they were editing field 3 in row 97 for the employee entity – we need to
reset that cell to edit mode after each binding update”).
The best solution here is to make liberal use of “refresh” buttons in your application.
You retrieve the data once and wait until the user has decided they wish to refresh the
information before you actively go looking for changes.
Again, this is where the design patterns help out. Having a controller able to call a
generic Refresh() method which rebinds the primary UI has the benefit of being easily
callable from elsewhere in the controller. For example, if a user elects to edit an
employee then when the controller has saved those changes it could also call the
Refresh() method to ensure that as the dialog closes the primary UI is also updated to
show the changes (and any other changes that other users may have made since the
last refresh).
Long Running Units of Work
If after employing all of these techniques for making your desktop applications you find
that there is still a need for a long running unit of work, then there are a number of
points to bear in mind:


If the database connection is lost the Unit Of Work will fail. To avoid this you
may want to use a custom connection strategy to reconnect to the database if
the connection fails for whatever reason. See Customising How LightSpeed
Connects to the Database in the chapter Building Applications with LightSpeed
for more information.
Be wary of stale data. Entities held in the unit of work’s identity map or the
global second-level cache may become stale with time. Use methods such as
UnitOfWork.Detach(Entity) to remove objects from the caches before
re-fetching to ensure you are obtaining fresh data.
Mindscape LightSpeed User Guide
131

Be wary of concurrency concerns. In a multi-user system it’s important to make
sure that data is not lost by saving stale data. LightSpeed always uses optimistic
concurrency, so it won’t prevent two users editing the same record at the same
time. Use features in LightSpeed such as Optimistic Concurrency Checking to
prevent accidental data loss.
Mindscape LightSpeed User Guide
132
Entity Support for Rich Client Frameworks
LightSpeed support several features which make delivering rich client applications easy
and fast. By using LightSpeed your entities automatically get:

INotifyPropertyChanged support.
All LightSpeed entities implement the INotifyPropertyChanged interface and all
properties will notify when changed. This means that when data binding to
LightSpeed entities the UI will update automatically when changes are made.

IDataErrorInfo support.
All entities implement the IDataErrorInfo interface. This is a core .NET
Framework interface that provides the functionality to offer custom error
information that a user interface can bind to. This means that by using the built
in LightSpeed validation capabilities your UI can display validation error
automatically if desired.

IEditableObject support.
All entities implement the IEditableObject interface. This is a core .NET
Framework interface that provides functionality to commit or rollback changes
to an object that is being used as a data source. IEditableObject is also applied
to the EntityCollection type which contains associated entities.
All of this support is built into LightSpeed and does not require the developer to do
anything special to get these benefits.
Mindscape LightSpeed User Guide
133
ClickOnce Deployment
The .NET Framework provides ClickOnce deployment to facilitate running rich client
applications from an intranet or Internet source.
When Visual Studio builds a ClickOnce package, by default it contains your application
and the assemblies it depends on, directly or indirectly. Applications which use
LightSpeed may therefore by default end up with some ADO.NET providers that they
not need. This can increase the download size of your ClickOnce package, and may
even cause errors if ClickOnce expects to find a dependency in the client global
assembly cache (GAC). For example, Visual Studio by default marks SQL Server
Compact as a prerequisite for LightSpeed, even if your application does not use this
database.
To remove dependencies and prerequisites that your application does not need, open
the Project Properties screen, go to the Publish tab, click the Application Files button
and set the Publish status to Exclude. Be careful not to exclude assemblies which your
application does use!
Mindscape LightSpeed User Guide
134
Building Silverlight Applications
You can use LightSpeed when developing Silverlight applications through the use of
WCF RIA Services to provide access to your data. LightSpeed exposes a custom domain
service provider to allow your domain model to be exposed through RIA Services in the
standard manner allowing you to focus on consuming that data in your Silverlight client
applications.
Mindscape LightSpeed User Guide
135
Using WCF RIA Services with LightSpeed
To get started with WCF RIA Services with LightSpeed you would start your application
in the usual manner, which is to create a standard Silverlight application and then
enable WCF RIA Services using the checkbox in the New Silverlight Application dialog.
The next step is to set up a domain model. You will start by creating a LightSpeed
model and designing it in the normal fashion. For more information on modelling
please refer to the chapters on Creating Domain Models and Domain Modelling
Techniques.
Because we will be shipping our objects over the wire make sure you include
references to System.ServiceModel and System.Runtime.Serialization so the
appropriate DataContract mark-up is generated for your entities. You will also want to
make sure you flag any associations with a DataMember attribute as appropriate to
ensure any child collections or related entities are sent to the client along with the
parent entity. For more information on how to define this please refer to the chapter
on Building Distributed Applications.
To expose our model using WCF RIA Services we need to create a domain service. You
will need to do this manually rather than using the wizards that are available.
Mindscape LightSpeed User Guide
136
Domain Service Basics
To start, add a reference to the Mindscape.LightSpeed.ServiceModel.DomainServices
assembly, which contains the helper classes you will need to build WCF RIA Services
projects with LightSpeed. To create your domain service you will need to base your
class on LightSpeedDomainService<TUnitOfWork> which provides the underlying
mechanics for handling WCF RIA Services requests.
To implement your derived class you will need to implement the
CreateLightSpeedContext method to return the strong-typed LightSpeedContext to be
used for creating units of work. The domain service caches the LightSpeedContext so
you don’t need to worry about ensuring it is a singleton.
Declaring a DomainService based on the LightSpeedDomainService<TUnitOfWork>
public class ModelDomainService : LightSpeedDomainService<ModelUnitOfWork>
{
private ModelUnitOfWork _unitOfWork;
protected override LightSpeedContext<ModelUnitOfWork> CreateLightSpeedContext()
{
return new LightSpeedContext<ModelUnitOfWork>("Development");
}
public ModelUnitOfWork UnitOfWork
{
get
{
if (_unitOfWork == null)
{
_unitOfWork = CreateTypedUnitOfWork();
}
return _unitOfWork;
}
}
}
Surfacing the Domain Entities
Once you have implemented the basics for the domain service you will want to expose
any entities which are to be handled at the client. This needs to be done not only for
Mindscape LightSpeed User Guide
137
the primary entities which are being exposed but also for any related objects which we
will be accessing.
To expose an entity you must at a minimum define a queryable method for that entity
type. This method must be marked with the[Query] attribute.
If you are intending to perform updates, you will also want to expose Insert, Update
and Delete methods for that entity type.
Below is an example of a set of methods defined for a Film entity. Note the [Query]
attribute on the query method.
Declaring the CRUD methods for a Film entity
[Query]
public IQueryable<Film> GetFilms()
{
return UnitOfWork.Films;
}
public void InsertFilms(Film film)
{
}
public void UpdateFilms(Film film)
{
}
public void DeleteFilms(Film film)
{
}
Generating and Using the Silverlight Data Source
Once you have defined your entities as part of the domain service you can re-compile
your solution and WCF RIA Services will code generate proxy classes in your Silverlight
application to allow for remote data access to occur. You will now be able to find your
exposed entities under the Data Sources tab, for example if we exposed Film and
Cinema entities then the view would be as below.
Mindscape LightSpeed User Guide
138
Now that your data is exposed you can continue programming normally and build up
your Silverlight application. All other standard Silverlight/WCF RIA Services approaches
can be applied such as exposing validation concerns by adding metadata classes to your
entities, or exposing custom methods as part of the domain service.
Mindscape LightSpeed User Guide
139
Building Distributed Applications
LightSpeed provides you with a number of options for building distributed applications,
whether it is just providing access to your data to remote users or implementing a fully
connected client/server style implementation.
When thinking about how you will design your system you will generally arrive at one
of three approaches.



You want to expose your data in a custom way. Rather than exposing the
entities themselves to external consumers you want to describe Data Transfer
Objects (DTOs) which will be populated from the data in one or more of your
entities.
You want to expose your entities directly, either through an RPC style API or via
XML or JSON over a RESTful interface.
You want to use LightSpeed from your client application but connect to a
service endpoint rather than the database directly.
LightSpeed supports all three of these approaches.
Lastly, LightSpeed also provides support for exposing LightSpeed entities through RIA
Services and Dynamic Data. You can find more information about building applications
with RIA Services in the chapter on “Building Silverlight Applications” and you can find
more information about building applications with ASP.NET Dynamic Data in the
chapter on “Building Web Applications”.
Mindscape LightSpeed User Guide
140
Distributed Entity Programming
If the design of your distributed system is intending to provide a tight coupling between
client and service by sharing your domain model then you will want to expose your
entities directly either by hand crafted WCF services or by using a
DistributedUnitOfWork to expose your service based UnitOfWork to your clients. If you
are intending to enforce a clean separation across service boundaries then you will
want to implement an approach using Data Transfer Objects and should review the
associated section below on using Data Transfer Objects with LightSpeed.
The Distributed Unit of Work
In our chapter on Basic Operations we introduced the unit of work. A unit of work is
used to scope your business transactions and manage loading, tracking and persisting
entities. In a distributed scenario you are disconnected from the database so a
LightSpeed unit of work is no longer valid, however from a design perspective the
pattern remains valid. So to assist with distributed scenarios LightSpeed has a
distributed version of the unit of work which allows the client to locally track objects
and then serialize changes back to the server when changes are to be persisted.
This distributed version of the unit of work, called DistributedUnitOfWork is found in
the Mindscape.LightSpeed.ServiceModel assembly. You create a
DistributedUnitOfWork instance in a similar fashion to instantiating a standard unit of
work. This then creates a scope which will delegate its operations to a remote unit of
work located at a known service endpoint. This affords you the convenience of
programming as if you were dealing with a local unit of work while being disconnected
from the database.
Mindscape LightSpeed User Guide
141
Instantiating a DistributedUnitOfWork instance
// DistributedModelUnitOfWork is a strongly typed DistributedUnitOfWork class
var context = new LightSpeedContext<DistributedModelUnitOfWork>()
{
// Setting the UnitOfWorkFactory is required to generate DistributedUnitOfWork
// instances (instead of base UnitOfWork instances)
//
// DistributedUnitOfWork handles the underlying transport concerns to connect
// to the service
UnitOfWorkFactory = new DistributedUnitOfWorkFactory<DistributedModelUnitOfWork>()
};
using (var unitOfWork = context.CreateUnitOfWork())
{
// you would perform your querying and persistence as normal
}
A distributed unit of work is represented in LightSpeed as the IDistributedUnitOfWork
interface. IDistributedUnitOfWork mirrors the IUnitOfWork interface and provides an
identical set of operations which allow you to load, add and remove entities, and to
save pending changes. IDistributedUnitOfWork is only limited on operations which deal
with database specific entities such as IDbReader and IDbCommand; in cases where
functionality is not supported a NotSupportedException will be thrown and these
specific methods have been noted in the XML documentation for the interface.
To create a DistributedUnitOfWork a LightSpeedContext object must be instantiated
with the UnitOfWorkFactory set to a new instance of a DistributedUnitOfWorkFactory
class. The factory is used to determine how to connect to the service endpoint using
WCF and by default will initialize itself using application configuration based on the
endpoint named LightSpeedDistributedUnitOfWorkEndpoint . Alternatively the factory
can be instantiated with a WCF EndpointAddress and Binding if you require runtime
configuration.
The DistributedUnitOfWorkService
When the DistributedUnitOfWork runs on the client, there needs to be a service
endpoint capable of servicing requests targeting the model being used by the client. To
host this there is a DistributedUnitOfWorkService class provided which allows you to
host an endpoint using WCF.
Mindscape LightSpeed User Guide
142
There are two ways this could be hosted, either you will manually host an actual
instance of the service within a dedicated process, for example a Console application or
a Windows Service; or you will have the service activated by IIS using Windows
Activation Services and a .svc file hosted in an ASP.NET website. Both approaches are
supported.
Manually Hosting the Service
To host the service manually you need to first create a UnitOfWork instance which will
be hosted by the service and then instantiate the service instance and pass that to the
constructor of a WCF ServiceHost. This will create a single instance of the service
hosting a single UnitOfWork which will be shared among all callers.
Manually hosting a DistributedUnitOfWorkService in single instance mode
var context = new LightSpeedContext<FilmFestival.Model.ModelUnitOfWork>("default");
using (var unitOfWork = context.CreateUnitOfWork())
{
using (var host = new ServiceHost(new DistributedUnitOfWorkService(unitOfWork)))
{
host.Open();
Console.WriteLine("Host has started - press ENTER to shut down");
Console.ReadLine();
}
}
You will also need to specify configuration for the endpoint as part of your
system.serviceModel configuration section. Here is an example using a
basicHttpBinding. The contract which is used is the
Mindscape.LightSpeed.ServiceModel.IDistributedUnitOfWorkContract.
Mindscape LightSpeed User Guide
143
Example configuration for a DistributedUnitOfWorkService hosted in single instance
mode
<system.serviceModel>
<services>
<service name="Mindscape.LightSpeed.ServiceModel.DistributedUnitOfWorkService">
<endpoint address="http://localhost:3000/UnitOfWork" binding="basicHttpBinding"
contract="Mindscape.LightSpeed.ServiceModel.IDistributedUnitOfWorkContract"/>
</service>
</services>
</system.serviceModel>
If you wish to have a new instance of the DistributedUnitOfWorkService created on a
per WCF instance basis, then you will need to subclass DistributedUnitOfWorkService,
providing the appropriate LightSpeedContext as a constructor argument to the base
class. An example of how to implement this is shown below.
Manually hosting a DistributedUnitOfWorkService in multi instance mode
public class MyUnitOfWorkService : DistributedUnitOfWorkService
{
public MyUnitOfWorkService()
: base(new LightSpeedContext("Development"))
{
}
}
using (var host = new ServiceHost(typeof(MyUnitOfWorkService)))
{
host.Open();
Console.WriteLine("Host has started - press ENTER to shut down");
Console.ReadLine();
}
As with hosting in single instance mode you will need to specify configuration about the
endpoint. Here is an example. The key difference from the example above is that we
need to ensure our service type name is specified in the name attribute for the service
configuration. The address, binding and contract are all the same.
Mindscape LightSpeed User Guide
144
Example configuration for a DistributedUnitOfWorkService hosted in multi instance
mode
<system.serviceModel>
<services>
<service name="Mindscape.LightSpeed.Samples.MyUnitOfWorkService">
<endpoint address="http://localhost:3000/UnitOfWork" binding="basicHttpBinding"
contract="Mindscape.LightSpeed.ServiceModel.IDistributedUnitOfWorkContract"/>
</service>
</services>
</system.serviceModel>
Supported Bindings
Because the DistributedUnitOfWork has been built on WCF it should support any of the
standard transports and bindings available within WCF. From a support perspective
however the implementation has been developed and tested on only the Named Pipe,
TCP and HTTP bindings.
Configuring Your Client for a DistributedUnitOfWork
Before you are able to receive results on your client you first need to configure the
client to use a DistributedUnitOfWork instead of a standard UnitOfWork. While the
configuration for a standard UnitOfWork revolves around assigning the connection
string and database provider, the configuration required for a DistributedUnitOfWork
focuses on the details of the endpoint that it needs to connect to.
The primary aspect of configuration that is required to use a DistributedUnitOfWork is
to override the UnitOfWorkFactory property on your context with an instance of a
DistributedUnitOfWorkFactory class. Any runtime details about the endpoint can be
configured through the constructor for this class, otherwise it is assumed you have a
system.serviceModel endpoint named LightSpeedDistributedUnitOfWorkEndpoint
configured.
Mindscape LightSpeed User Guide
145
Example of creating a DistributedUnitOfWorkFactory class and a
DistributedUnitOfWork
var context = new LightSpeedContext<DistributedModelUnitOfWork>()
{
UnitOfWorkFactory = new DistributedUnitOfWorkFactory<DistributedModelUnitOfWork>()
};
using (var unitOfWork = context.CreateUnitOfWork())
{
// use as normal, UnitOfWork will be a DistributedModelUnitOfWork instance
}
Example configuration you would specify in system.serviceModel
<system.serviceModel>
<client>
<endpoint name="LightSpeedDistributedUnitOfWorkEndpoint"
address="http://localhost:3000/UnitOfWork" binding="basicHttpBinding"
contract="Mindscape.LightSpeed.ServiceModel.IDistributedUnitOfWorkContract"/>
</client>
</system.serviceModel>
Example of creating a DistributedUnitOfWorkFactory with runtime configuration about
the endpoint
var context = new LightSpeedContext<DistributedModelUnitOfWork>()
{
UnitOfWorkFactory =
new DistributedUnitOfWorkFactory<DistributedModelUnitOfWork>(binding, address)
};
Mindscape LightSpeed User Guide
146
Example of creating a DistributedUnitOfWorkFactory with a typed model
public interface IMyTypedUnitOfWork
{
System.Linq.IQueryable<Member> Members{ get; }
}
public class MyDistributedUnitOfWork : DistributedUnitOfWork, IMyTypedUnitOfWork
{
public MyDistributedUnitOfWork()
: base()
{
}
public MyDistributedUnitOfWork(LightSpeedContext context)
: base(context)
{
}
public System.Linq.IQueryable<Member> Members
{
get { return this.Query<Member>(); }
}
}
var context = new LightSpeedContext<MyDistributedUnitOfWork>()
{
UnitOfWorkFactory = new DistributedUnitOfWorkFactory<MyDistributedUnitOfWork>()
};
Executing Operations at the Client
Once you have configured your client to use a DistributedUnitOfWork you can create a
UnitOfWork in the standard manner by calling LightSpeedContext.CreateUnitOfWork().
Subsequent calls to execute queries will trigger a service call as will calls to
SaveChanges().
Data Contracts
If the project containing your LightSpeed model references System.ServiceModel then
the Visual Studio designer will automatically emit DataContract and DataMember
attributes to mark up your model classes allowing them to be correctly serialized by the
WCF DataContract formatter.
Mindscape LightSpeed User Guide
147
By default all value properties are marked with a DataMember attribute including
foreign key identity fields, but all associations are omitted from serialization. This is to
allow you to more correctly specify what structures should be serialized in distributed
scenarios rather than potentially opting in the whole domain model for serialization.
It is highly recommended that you review which associations should be serialized when
designing your model for distributed scenarios.
Building WCF Services using Entities
If you don’t wish to use a DistributedUnitOfWork to remote your entities between your
client and service then the other approach which can be used is to expose your entities
using a hand crafted WCF service.
Review the section above regarding Data Contracts and ensure that you have designed
your model for the distributed scenarios you wish to support. Unlike with a Data
Transfer Object approach, because you have a single model to work with you will not
be able to tailor your messages on a per request basis so we would recommend that
you avoid prematurely opting in associations to your DataContracts.
If you choose to include EntityCollections into serialization you will need to use a
special version of the DataContract formatter known as the NetDataContractFormatter
which allows correct serialization of collection types which have been marked with a
DataContract attribute.
Samples
There are two samples available which make use of the DistributedUnitOfWork which
will give you a practical view of how the DistributedUnitOfWork can be used across the
two standard types of applications you are likely to be using it with.
The first sample is the ATM sample within which the Teller project is an example of a
WPF application which uses the DistributedUnitOfWork as part of a long running
stateful client application.
The second sample is the Film Festival sample within which the Website uses the
DistributedUnitOfWork in a per request fashion as part of a stateless ASP.NET MVC
web application.
Mindscape LightSpeed User Guide
148
For a sample which demonstrates building a hand crafted WCF service which exposes
entities then you should review the ATM sample. The ATMClient console application
project makes use of several service calls which deal with entities which have been
shared with the Website service.
Mindscape LightSpeed User Guide
149
Building WCF Services using Data Transfer Objects
If the design of your distributed system is intending to provide a loose coupling
between client and service by sharing contracts then you will want to build your system
using Data Transfer Objects to enforce a clean separation across service boundaries.
You will then want to be able to map your domain entities to and from these Data
Transfer Objects. If you are intending to use a tight coupling where you share the
details of your domain model across the service boundary then you should review the
associated section above on Distributed Entities.
The design of your contracts is beyond the scope of this document and is not
specifically a role which LightSpeed provides. However, we do provide assistance
around importing and exporting your entities.
Here is a basic example which is based around this LightSpeed model containing
relationships between Contributions, Comments and Members of a system.
Mindscape LightSpeed User Guide
150
Given this model we might wish to represent a data transfer object which provides the
details of a member to form part of our published service contract.
Example of a Data Transfer object which would represent a member
[DataContract]
class ApplicationMember
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string UserName { get; set; }
[DataMember]
public string Email { get; set; }
}
We need to achieve several steps to deal with data transfer objects within our system.



We need to project LightSpeed Member entities into ApplicationMember
instances so we can send these objects out of our system and over the wire.
We need to allow inbound ApplicationMember to be mapped back to Member
entities.
We need to support both creating new instances and updating existing
instances.
The first step can be achieved by either creating a mapping function or by using LINQ to
project the data as required. Assuming you are able to use LINQ then this would be the
preferred approach.
Example of projecting Member entities to ApplicationMember instances
var members = unitOfWork.Members.Select(m => new ApplicationMember()
{
Id = m.Id,
Email = m.Email,
UserName = m.UserName
}).ToList();
Mindscape LightSpeed User Guide
151
The second and third steps can be achieved by using IUnitOfWork.Import which
provides functionality for mapping arbitrary data transfer objects back against the
entity store. It will conditionally create new entities or update existing entities based on
the value of the Id property held on the data transfer object.
Calling IUnitOfWork.Import
members.ForEach(m => unitOfWork.Import<Member>(m));
In our example the call to the Import method returns the actual Member entity
instance that was attached to the UnitOfWork. As with the example above we do not
make use of this, but if you need to perform any subsequent processing against the
entity then it is available for use.
The default mapping approach used by the Import method comes with two caveats.
1. We expect that the data transfer object will have an Id property which we can
use to check if there is already an entity with that Id in the database, and load
it if that is the case so we can perform an update. If no Id property is present
then that aspect of the mapper will be ignored and you will always be dealing
with new entities.
2. The mapper will use reflection to assign properties of the data transfer object
which exactly match the value fields on the entity. In the example you will
notice that ApplicationMember has a case sensitive match with the Member
entity for the property UserName. If the property does not exactly match, then
assignment will not happen.
Providing Custom Mapping for IUnitOfWork.Import
The default mapping approach provided with IUnitOfWork.Import will work well for
one to one style mapping and constrains you to the use of a key property to ensure you
get conditional insert/update behaviour. Lastly associations are also not currently
traversed as part of the mapping so in the earlier example if an ApplicationMember
held Contribution and Comments collections then these would not be imported back
into the UnitOfWork.
Mindscape LightSpeed User Guide
152
If you need finer control over the behaviour of the mapping then IUnitOfWork.Import
provides an overload which allows you to provide a custom function which allows you
specifically control how mapping occurs.
The mapping function takes two arguments, the generic type argument and the source
object we are passing to be imported.
An example of a custom mapping function used with IUnitOfWork.Import
members.ForEach(m => unitOfWork.Import<Member>(m, (t, o) =>
{
var am = o as ApplicationMember;
var member = unitOfWork.Members.SingleOrDefault(m2 => m2.Id == am.Id);
member.UserName = am.UserName;
return member;
}));
Compatibility with LightSpeed 3 Generated DTOs
As part of LightSpeed 3 we offered code generation templates which would generate
you some 1:1 style DTO’s and associated mapping helper methods. These have been
deprecated however the code generation will still occur for these but the code itself
now relies on the presence of a compiler conditional to be included. If you wish to
continue using these templates then you must set the LS3_DTOS compiler conditional
in the project containing your model file.
Mindscape LightSpeed User Guide
153
Samples
If you wish to review a practical sample which uses Data Transfer Objects with
LightSpeed then you should review the ATM sample within which the ATMClient
console application project makes use of several service calls which rely on DTOs which
have been defined in the Contracts project.
Mindscape LightSpeed User Guide
154
Testing and Debugging
Testing and debugging are essential parts of the software development lifecycle.
LightSpeed has been designed with testability in mind, and provides several features to
facilitate testing, debugging and tuning a LightSpeed model or application.
Mindscape LightSpeed User Guide
155
Unit Testing
When undertaking unit tests it’s essential that the tests be repeatable. This means that
developers need to ensure that the database is in an expected state at the beginning of
each test run. To achieve this, tests can either be run against a real database with
transactions or run against fake objects in memory. This guide explores both
approaches. Sample code uses the NUnit unit testing product, but can easily be
adapted to any unit testing framework.
Using a Real Database
Setup a test version of your database. Typically we have set up two databases for a
project:


ApplicationDatabase
ApplicationDatabase_Test
All of the test data will be stored in the ApplicationDatabase_Test.
Next, create a transaction test fixture. The benefit of this is that all our test fixture
classes can derive from the custom test fixture class and be ready with the
transactional pattern for testing in place.
Mindscape LightSpeed User Guide
156
An example of the transactional TestFixtureBase
[TestFixture]
public abstract class TestFixtureBase
{
private static readonly LightSpeedContext<MyModelUnitOfWork> _context =
new LightSpeedContext<MyModelUnitOfWork>();
static TestFixtureBase()
{
// configure LightSpeed
_context.ConnectionString = @"Data Source=ORMapper;Initial Catalog=ORMapper;
Integrated Security=SSPI";
_context.Logger = new ConsoleLogger();
}
private TransactionScope _transactionScope;
private MyModelUnitOfWork _uow;
[SetUp]
public virtual void SetUp()
{
_transactionScope = new TransactionScope();
_uow = _context.CreateUnitOfWork();
}
[TearDown]
public virtual void TearDown()
{
_uow.Dispose();
_transactionScope.Dispose();
}
}
In this code all tests are run within a TransactionScope and the changes are rolled back
upon completion of the test.
Additionally, note that in the SetUp method we are creating a new unit of work and in
the TearDown method we are destroying it by calling IUnitOfWork.Dispose. The
creation of new units of work for each test is important because we want complete test
isolation; that is, no test can affect the result of any other test. We achieve this
isolation by having each test run in a new unit of work.
Once this is done the development of the actual tests can commence.
Mindscape LightSpeed User Guide
157
A simple unit test
[Test]
public void UpdatingContributionDescription()
{
var contribution = _uow.Contributions.Single(c => c.Id == 1);
contribution.Description = "Some new description";
_uow.SaveChanges(true);
var updatedContribution = _uow.Contributions.Single(c => c.Id == 1);
Assert.AreNotSame(contribution, updatedContribution);
Assert.AreEqual("Some new description", updatedContribution.Description);
}
This test finds the Contribution with Id 1 and then updates its Description attribute. We
then call SaveChanges to flush our pending update to the database. Notice we pass
true to the SaveChanges method. This is an idiomatic LightSpeed unit test pattern
applicable whenever we want to verify a database update was made correctly. The
SaveChanges method causes all pending updates to be flushed to the database, and the
true argument signals that LightSpeed should additionally clear the identity map – the
unit of work’s cache of the entities it is managing, which serves to prevent us loading
the same Entity more than once. By clearing the identity map, we guarantee that
subsequent requests for previously loaded objects hit the database and therefore allow
us to validate that the database state of an entity is as we expect.
Using Fakes
In situations where in memory objects would be preferred there are helper classes
included in LightSpeed to create fake entities and a test unit of work.
LightSpeed includes two classes to help support faking queries and entities:
TestUnitOfWork and EntityFactory. Both of these are in the
Mindscape.LightSpeed.Testing namespace.
TestUnitOfWork allows you to inject a fake result for a query. To do this, call the
TestUnitOfWork.SetCollectionResult method, passing in the desired fake result.
Subsequent calls to Find or FindBySql, or LINQ entity queries, will return the fake result
Mindscape LightSpeed User Guide
158
rather than querying the database. TestUnitOfWork also allows faking of other query
methods:





Call TestUnitOfWork.SetSingleResult to specify a fake result for FindOne or
FindById
Call TestUnitOfWork.SetExpectedCountResult to specify a fake result for Count,
or the LINQ Count() and LongCount() operators
Call TestUnitOfWork.SetExpectedCalculateResult to specify a fake result for
Calculate, or for LINQ aggregate operators such as Sum()
Call TestUnitOfWork.SetProjectionResult to specify a fake result for Project, or
for LINQ projection queries (where specific properties are selected into a
named or anonymous type)
Call TestUnitOfWork.SetSearchResult to specify a fake result for Search
Note that fake results are returned directly – any query expression or LINQ Where
operator is not applied! Your test fixture should set up the expected fake results.
Creating a TestUnitOfWork and adding then selecting a fake entity
[Test]
public void FetchObjectByIdFromTestUnitOfWork()
{
var uow = new TestUnitOfWork();
var entity1 = EntityFactory.Create<Comment>(EntityState.Default, 1000);
uow.Add(entity1);
Assert.IsNotNull(uow.FindById<Comment>(1000));
}
EntityFactory helps you to create entities with predictable ID values and in states other
than the New state. Thus, you can use EntityFactory to fake entities that have been
loaded from the database (and therefore begin in the Default state).
Using Mocks
If you are interested in interaction based unit testing (often characterized by the use of
mocks, fakes or stubs), LightSpeed exposes a few key interfaces that facilitate such an
Mindscape LightSpeed User Guide
159
approach. The primary extension point providing substitutability in LightSpeed is the
IUnitOfWork interface. Implementing (or mocking) IUnitOfWork provides complete
control over all major LightSpeed operations such as querying and persistence. In order
to make your test context use your mock IUnitOfWork, create an implementation of
IUnitOfWorkFactory that returns your custom implementation and then assign your
custom IUnitOfWorkFactory to the UnitOfWorkFactory property found on the
LightSpeedContext object.
Some places in LightSpeed internally require a UnitOfWorkBase, not just an
IUnitOfWork. Consider mocking UnitOfWorkBase rather than the interface, or using the
TestUnitOfWork fake instead.
Mindscape LightSpeed User Guide
160
Logging
To aid in development, LightSpeed can be configured to log diagnostic messages. The
logger is any object that implements the ILogger interface: this gives you the flexibility
to log according to the requirements of the application and environment. For example,
you can log to the Visual Studio Output window during development, then turn off
logging in production. Or you could create a custom logger which would allow you to
selectively turn on logging for production diagnostics.
Built-In Loggers
The framework provides two built-in loggers:


ConsoleLogger writes logs to the console window.
TraceLogger writes logs to all application trace listeners. Trace listeners are
defined by the .NET Framework and can be specified in the configuration file
via the <system.diagnostics> element. The .NET trace infrastructure also
supports filtering of messages.
Both built-in loggers log both SQL and debug messages.
Enabling Logging
To enable logging, set LightSpeedContext.Logger in code, or the loggerClass attribute in
configuration. When setting the logger class in configuration, you must provide a full
assembly-qualified type name.
Enabling logging in configuration
<add name="Test"
loggerClass="Mindscape.LightSpeed.Logging.TraceLogger, Mindscape.LightSpeed" />
Mindscape LightSpeed User Guide
161
Enabling logging in code
_context.Logger = new TraceLogger();
You can display additional logging information by setting
LightSpeedContext.VerboseLogging. This is not available in configuration because it
should be set only during debugging.
Disabling Logging
To disable logging after it has been turned on, set LightSpeedContext.Logger to null.
_context.Logger = null;
Building a Custom Logger
In addition to the built-in loggers, you can create your own custom logger. You could
do this for example to integrate with your application’s wider logging infrastructure, to
take advantage of logging platforms such as log4net, or to support finer control over
logging. To create a custom logger, implement the ILogger interface.
ILogger has two methods: LogSql and LogDebug. LightSpeed calls LogSql for each SQL
statement or batch sent to the database. It calls LogDebug primarily for performanceoriented messages such as cache hits and misses and database access times.
LogSql and LogDebug are declared as receiving values of type object. The exact data
that LightSpeed passes to these methods may change, but you can safely call ToString
on these objects to get readable log text. In the LogSql method, you may also attempt
to cast the object to CommandLog. If the object is a CommandLog then you can access
individual properties in order to log them individually or log them in a more structured
way such as to a database.
Mindscape LightSpeed User Guide
162
A custom logger which uses the application’s logging infrastructure
public class IntegratedLogger : ILogger
{
private void LogToApplicationLog(string text)
{
// passes text to whatever logging service or platform the application uses
}
public void LogSql(object sql)
{
LogToApplicationLog(sql.ToString());
}
public void LogDebug(object text)
{
LogToApplicationLog(text.ToString());
}
}
Note that the VerboseLogging setting does not affect what information is available in
the CommandLog object. CommandLog always contains full logging information.
VerboseLogging affects only how much information is included in
CommandLog.ToString().
Column Name Logging
For each logged data parameter the column name is also logged.
Mindscape LightSpeed User Guide
163
Profiling
You can use the logging infrastructure to perform simple profiling.
Query Patterns
You can review the SQL logs to determine what queries LightSpeed is issuing and to
look for inefficient patterns such as N+1 lazy loads. When looking for performance
bottlenecks, it can be wise to identify a piece of code you want to profile, and turn
logging off after executing that code – otherwise you may end up with a very large SQL
trace, making it to track down whether you have traced a single inefficient run of code
or a large number of efficient runs!
Timing Queries
When LightSpeed logs a SQL statement or batch, it also prints out the time taken to
execute that statement. You can also access the time taken directly using the
CommandLog.TimeTaken property.
Using a Custom Logger for Profiling
In some cases you may be able to use a custom logger to search for patterns or
problems by drilling into the CommandLog object. For example, to detect poorlyperforming queries which might be candidates for tuning at the database level or
refactoring into stored procedures, you could create a custom logger which logs only
queries that take a long time to run. Or to detect N+1 problems you could create a
logger which logs SQL statements on a per unit of work basis, and highlights where a
unit of work has executed more statements than a threshold.
Mindscape LightSpeed User Guide
164
A custom logger which uses CommandLog properties to detect slow queries
public class AlertingLogger : ILogger
{
public void LogSql(object sql)
{
CommandLog commandLog = sql as CommandLog;
if (commandLog != null)
{
if (commandLog.TimeTaken > LongQueryThreshold)
{
SendLongQueryAlert(commandLog);
}
}
}
public void LogDebug(object text) { /* do nothing * }
}
Using Profilers with the Interception API
LightSpeed 5 introduces a dedicated API for profiling internal performance. This
exposes LightSpeed events to external profiling tools, such as the ASP.NET MVC MiniProfiler, allowing them to intercept and modify the events. The API is available in the
Mindscape.LightSpeed.Profiling namespace, which contains the Interceptor class. To
connect a profiler to LightSpeed, you need to provide an implementation of this, such
as in the figure below.
Mindscape LightSpeed User Guide
165
An interceptor that sends LightSpeed events to ASP.NET MVC Mini-Profiler
public class MiniProfilerInterceptor : Interceptor
{
public override IDbConnection CreateConnection(Func<IDbConnection> baseCreator)
{
var realConnection = baseCreator();
return ProfiledDbConnection.Get((DbConnection)realConnection, MiniProfiler.Current);
// or return new ProfiledDbConnection(...) depending on which version you are using
}
public override IDbCommand CreateCommand(Func<IDbCommand> baseCreator)
{
var realCommand = baseCreator();
return new ProfiledDbCommand((DbCommand)realCommand, null, MiniProfiler.Current);
}
}
You can then attach the profiler through the web.config using the new interceptorClass
attribute. Alternatively, this can be done by setting the LightSpeedContext.Interceptor
property.
In web.config
<lightSpeedContexts>
<add name="Development"
interceptorClass="LightSpeedMiniProfilerInterceptor.MiniProfilerInterceptor,LightSpeedMin
iProfilerInterceptor" />
</lightSpeedContexts>
Mindscape LightSpeed User Guide
166
Domain Modelling Techniques
LightSpeed aims to enable you to model your domain in as natural a way as possible.
This means allowing you to use object-oriented constructs such as inheritance, to
represent ‘kind of’ relationships between entity classes, and value objects, to assign
business semantics to an attribute or group of attributes. This chapter shows how to
use these object-oriented techniques and how they map to relational database storage.
Mindscape LightSpeed User Guide
167
Inheritance
Inheritance, when used wisely, can greatly assist in developing expressive,
maintainable domain models that neatly capture potentially complex domain
behaviours. LightSpeed supports inheritance through the popular single table
inheritance and class table inheritance patterns, which encapsulate different ways of
mapping an inheritance hierarchy to a relational model. Regardless of the database
mapping, the inheritance relationship is modelled using the familiar .NET inheritance
feature.
To derive one entity class from another:


Designer: Choose the Inheritance arrow from the toolbox and drag an arrow
from the derived to the base entity; or select the derived entity, go to the
Properties grid, and set its Base Class to the desired entity type.
Code: Use the normal C# or Visual Basic inheritance syntax.
Discriminators
The key aspect of implementing single or class table inheritance is to define a
“discriminator” column. A discriminator column is simply a column used by LightSpeed
to determine the type of entity to instantiate when loading a row from the underlying
table. Quite often this column is also a foreign key to an associated reference data
table. E.g. Employee has an EmployeeTypeId.
Every inherited class in single or class table inheritance must specify a discriminator
column. All classes in an inheritance hierarchy must specify the same discriminator
column.
Furthermore, each class in the hierarchy (except the root class) must specify a
discriminator value. This tells LightSpeed, “If the discriminator column contains this
value, materialise the row as this type of entity.” Each class must therefore specify a
different discriminator value. If LightSpeed encounters a row which doesn’t match any
of the derived class discriminator values, it treats it as an instance of the root class.
Mindscape LightSpeed User Guide
168
To specify the discriminator for a derived class:


Designer: Select the inheritance arrow from the derived to the base class, and
fill out the Discriminator Name, Discriminator Type and Discriminator Value
settings. If you set the Discriminator setting on the root class, LightSpeed will
default the name and type for you.
Code: Apply DiscriminatorAttribute to the derived class, specifying the
discriminator attribute name and the value that identifies this class.
Single Table Inheritance
By default, LightSpeed maps inheritance hierarchies to the database using the popular
Single Table Inheritance pattern (STI). As the name suggests, the key idea behind this
pattern is that all of the data for a particular inheritance hierarchy is stored in a single
table. As with most design patterns there are various trade-offs associated with the STI
pattern, the most obvious being the classic time/space tradeoff. The STI pattern trades
space for time – by storing the data in one table, querying and persisting the data
becomes inherently simple and efficient. However, for hierarchies where the entities
have different attributes, the underlying table may become sparsely populated. That
said, most modern database systems are reasonably good at optimizing unused table
space.
Mindscape LightSpeed User Guide
169
You don’t need to do anything special to specify single table inheritance: if you follow
the steps above, single table inheritance is what you will get.
Class Table Inheritance
When an inheritance hierarchy contains derived classes with a lot of state that is
specific to those derived classes, single table inheritance results in an unnatural
database design where the table contains many columns that are applicable only to
specific subclasses. For situations like these, LightSpeed also supports Class Table
Inheritance. In this mapping mode, there is a separate table for each class in the
hierarchy, with each table containing columns only for the fields introduced in that
class (plus an Id column). This provides a more natural database design at the expense
of producing more complex and therefore less efficient queries (because LightSpeed
must join the tables on each query).
Here is how a class table inheritance structure appears in the domain model, and how
it maps to the database.
Class table inheritance is set up in much the same way as single table inheritance: you
must provide a “discriminator” column on the table corresponding to the root class,
and specify a discriminator attribute and value on each derived class. In addition, you
must specify class table inheritance:
Mindscape LightSpeed User Guide
170


Designer: For each inheritance arrow in the hierarchy, set Inheritance Type to
ClassTableInheritance.
Code: Apply InheritanceMappingAttribute to the root class of the hierarchy,
with an InheritanceMappingKind of ClassTableInheritance
Implementing Common Services in a Base Class
LightSpeed supports a third form of inheritance, known as concrete table inheritance,
which is not discriminated. In concrete table inheritance, each entity type maps to a
table, and that table contains columns for each field on the entity type, including
inherited ones.
Concrete table inheritance is not polymorphic, so you cannot have associations to a
base class or perform queries for a base class. As a general rule, any class that is a base
class for concrete table inheritance should be abstract.
Concrete table inheritance is useful when:



You have a few fields that appear in every table in your database, and want to
save re-entering them on each entity class.
You have some common services such as helper methods which need access to
class internals (and therefore cannot be extension methods), but do not
contribute fields to the entity.
You don’t need to load objects polymorphically in a single query.
Which Should I Choose?
If you need polymorphism but your derived classes don’t introduce extra state (only
behaviour), or at least not too much extra state, use single table inheritance.
Class table inheritance is a good fit when you need polymorphism, but have a lot of
columns that make sense only for child entities. However, class table inheritance can
introduce a significant performance penalty due to the need to perform joins in the
database query, and to update multiple tables when saving, especially if you have a
very deep or wide hierarchy. In addition, class table inheritance imposes some
limitations (for example around cascade deletes) that are not an issue for single table
Mindscape LightSpeed User Guide
171
inheritance. Therefore, you should prefer single table inheritance over class table
inheritance unless the derived classes add a lot of new columns.
If you don’t need polymorphism, you should use concrete table inheritance because it
provides the best performance and most natural database mapping.
Mindscape LightSpeed User Guide
172
Value Objects
Many attributes of an entity can be represented by primitive values such as integers or
strings. However, it’s sometimes useful to represent an attribute by a type with more
specific meaning. Such an attribute might be a single column, for example representing
a Salary column by a Money type instead of Decimal, or it might be multiple columns,
for example representing LocationX and LocationY columns by a Point or Position type
instead of a two separate Doubles.
In these examples, Money, Point and Position are value objects. They are not entities,
because they do not have identity of their own: they are just a way of representing
entity attributes in a more business-meaningful way. Value objects are idiomatic in
domain-driven design and are discussed in detail in Eric Evans’ book of the same name.
Defining a Value Object Type in the Designer
To define a value object type in the designer, drag a Value Object icon from the
Toolbox into the model. You can name the type and add properties just as you do with
entities.
Notice that a value object type does not have a base class or identity type, because a
value object represents an attribute of an entity, and does not have identity in itself.
Creating a Value Object Member in the Designer
Once you have defined a value object type, you can create an entity property of that
type by choosing the Value Object Property connector from the Toolbox and dragging a
line from the entity to the value object type. You can edit the name of the property
through the line label or the Name property.
Mindscape LightSpeed User Guide
173
Value Objects in Database-First Development
Relational databases don’t have a concept of value objects, so when you drag a table
onto the designer, LightSpeed can’t automatically infer value objects from the table
schema.
After dragging the table on, however, you may identify a set of columns which you
want to represent as a value object member. If these columns’ names have a common
prefix (see Value Object Database Mappings below) then you can extract them to a
value object by selecting the columns, right-clicking the multiple selection and choosing
Extract to Value Object. Depending on whether a value object type suitable for
mapping these columns is already defined, the designer will offer to map the columns
to an existing value object type, to add the columns to an existing value object type, or
to create a new value object type (and a member of that type).
Defining a Value Object Type in Code
At the code level, a value object type is just an ordinary CLR type – class or struct. As
with entities, LightSpeed is interested in the fields of the type, not the properties:
therefore, you must not use automatic properties (because the backing field for an
anonymous property does not have the ‘right’ name for LightSpeed to map it).
Value object types should be immutable. All fields should be read-only, and the
wrapper properties should be get-only.
Mindscape LightSpeed User Guide
174
A simple value object type
public class Money
{
private readonly decimal _amount;
// Fields should be read-only
public Money(decimal amount)
{
_amount = amount;
}
public decimal Amount
{
get { return _amount; }
}
// Do not use automatic properties
}
Creating a Value Object Member in Code
Fields of value object type are defined in the same way as fields of primitive types,
except that you must apply ValueObjectAttribute to the field.
A member of Money type
public class Employee
{
[ValueObject]
private Money _salary;
public Money Salary
{
get { return _salary; }
set { Set(ref _salary, value); }
}
}
Setting Value Object Properties
Value objects are immutable. This means you cannot modify the properties of the
value object directly.
Mindscape LightSpeed User Guide
175
employee.Salary.Amount = 50000;
// Compiler error – Amount property is read-only
The reason for this is that value objects represent values. Suppose you have an
Employee entity with a Salary of $50000. If the Employee gets a raise, then their Salary
changes to a new value of $60000. It would be wrong to think that $50000 has
mutated into $60000. $60000 is a different value from $50000, so it must be a
different instance. (Remember, value objects don’t have identity.)
Even if you created a mutable value object, you could not set properties this way,
because value objects don’t have access to the Entity.Set method which is essential for
notifying LightSpeed of changes needing saving – not to mention for UI interfaces such
as IEditableObject and INotifyPropertyChanged.
So when you set a value object property, you must always set it to a new instance of
the value type.
Setting value object properties
employee.Salary = new Money(50000);
hq.Location = new Position(-41.289, 174.777);
A consequence of this is that when using data binding or data grids you must provide a
way to edit your custom value types. In the Location example, the entity has a single
Location, which will appear as a single column in a data grid. That column will need to
display and allow editing of the Position value, but the editor will need to keep
producing new Position objects. You cannot, for example, provide two text boxes, one
bound to Location.X and one bound to Location.Y.
Value Object Database Mappings
By default, value object column names are prefixed with the name of the value object
field on the containing class.
Mindscape LightSpeed User Guide
176
For example, suppose we have a Site entity with a Location property of type Position,
and that Position has the properties X and Y. Then these would be mapped to columns
named LocationX and LocationY in the Site database table.
You can map the column name suffixes associated with the fields in the value object in
the same way as you map entity column names: by setting the Column Name option in
the designer, or by applying ColumnAttribute in code.
For example, suppose that Position.X were mapped to Longitude and Position.Y to
Latitude. Then the Site Location property would be mapped to the LocationLongitude
and LocationLatitude columns in the database.
You can map the column name prefix associated with an occurrence of a value object
by setting Column Name Prefix on the connector in the designer, or by applying
ColumnAttribute to the value object reference in code.
For example, suppose that Site.Location were mapped to the Coordinates prefix. Then
LightSpeed would map this to CoordinatesX and CoordinatesY columns in the database.
The following table shows how modifiers on a field in a value object (such as Position.X)
and modifiers on an occurrence of a value object (such as Site.Location) affect the
column name mapping.
Site.Location
Position.X
No modifier
Prefix “Coordinates”
LocationX
CoordinatesX
LocationLatitude
CoordinatesLatitude
No modifier
Column “Latitude”
In legacy databases, the columns of a value object may not share a common prefix, or
there may not be a consistent set of suffixes across different sets of columns that you’d
like to map to the same value object type. In this case, you can map individual fields of
the value object at the occurrence level using ValueObjectColumnAttribute. At the
time of writing, this is not supported in the designer and can only be used on handcoded members.
Mindscape LightSpeed User Guide
177
Mapping value object columns to an irregular database schema
public class Site : Entity<int>
{
[ValueObjectColumn("X", "XPos")]
[ValueObjectColumn("Y", "YPos")]
private Position _location;
[ValueObjectColumn("X", "Easting")]
[ValueObjectColumn("Y", "Northing")]
private Position _surveyCoordinates;
}
Mindscape LightSpeed User Guide
178
Reference Data and Lookups
Many domains contain reference data – data that is generally static, and that users are
not expected to modify. Typical examples include lists of countries or currencies.
Reference data is also known as lookup data, referring to the fact that you use look
things up in the reference data, but you don’t change it.
A transactional entity – that is, one which is taking part in a unit of work – may refer to
reference data, and it’s convenient to materialise this reference data as an entity for
lookup purposes. For example, a Customer might have a reference to a Country so that
you can display the country name. But for large data sets it’s not desirable for a
reference entity to have a collection of all the entities associated with it. For example,
if you have tens of millions of customers, you probably don’t want each Country entity
to have a collection of the customers in that country.
For this scenario, LightSpeed supports one-way associations. To make an association
one-way, select the association arrow and delete the collection name. You should also
cache the reference data entity using the Cached and Cache Expiry settings.
(To make a one-way association between hand-coded entities, omit the
EntityCollection<T> field on the reference entity class, and apply
NoReverseAssociationAttribute to the EntityHolder<T> field in the transactional entity
class.)
One-way associations should be used only with reference data. There is an efficiency
impact if you use a one-way association and the referenced entity is not cached.
Therefore, one-way associations are useful only when the associated entities can be
loaded once and rarely change.
Mindscape LightSpeed User Guide
179
Working with Models in the Visual
Designer
LightSpeed is equipped with a powerful visual model designer, which provides
numerous features for working more productively. The chapter Creating Domain
Models described the core tasks and workflow of building a model using the designer.
This chapter provides more details about designer productivity and customisation
features.
Mindscape LightSpeed User Guide
180
LightSpeed Model Explorer
The LightSpeed design surface shows your domain model – entities, associations and
value objects – and data access elements, such as views and stored procedures. Most
of the time, this is all you will work with and all you need to see. For some advanced or
infrequent actions, however, you will need to use the LightSpeed Model Explorer. This
shows a tree view of your model file, including non-visual elements, and is the only way
to create, edit or delete non-visual elements. Some of the techniques in this chapter
require you to use the LightSpeed Model Explorer instead of the model design surface.
To open the LightSpeed Model Explorer, open the Visual Studio View menu and choose
Other Windows > LightSpeed Model.
Mindscape LightSpeed User Guide
181
Workflows for Rapid Application Development
A traditional challenge for object-relational mapping is keeping the model in sync with
the database schema. Changes need to be repeated in both places, which increases
work, creates the chance of mistakes and breaks your flow as you switch tasks.
LightSpeed supports workflows which avoid duplication and speed up application
development.
Driving the Database from the Domain Model
The most convenient way to work with LightSpeed is to do your modelling in the visual
designer, creating entities and associations using the toolbox, adding properties using
the Ins key or context menu, and configuring properties using the property grid. You
can then use the Update Database command to synchronise the database schema to
the model.
This makes for a very rapid development workflow because you are working entirely
within one tool, the LightSpeed designer, and synchronising to the relational world
requires just a couple of mouse clicks and is normally very quick.
This ‘model first’ approach does not provide fine control over the database design. You
may occasionally find that you need to go into your database tool to tweak column
definitions or to set up additional options such as indexes. At the very least, you will
want to review the generated schema before putting it into production. Nevertheless,
this approach makes for extremely efficient rapid application development and allows
you to get your model working with a real database with the minimum of extra steps.
Modelling Using Your Database Tools
An alternative approach is to focus first on the database schema. You can build the
database using such tools as SQL Server Management Studio or MySQL Workbench,
then drag the tables from the Visual Studio Server Explorer into the LightSpeed model
to create entity classes. You can also use the Update From Source command to
synchronise existing entities to the underlying database tables, for example if you have
added a column to a table.
Mindscape LightSpeed User Guide
182
The ‘database first’ approach is familiar from older designers such as LINQ to SQL and
the ADO.NET DataSet designer. LightSpeed offers a more rapid development
experience because it allows you to update existing entities in a non-destructive way –
that is, LightSpeed preserves any additional options you’ve applied to the entities,
rather than requiring you to delete and recreate them. It is particularly efficient if you
need to be very specific about the schema of your database – for example, if you have
a lot of columns where you need to control the numeric precision and scale.
The disadvantage is that it requires you to swap between the database design tool and
the LightSpeed domain model. This can disrupt your mental flow, especially if you
need to add a lot of LightSpeed-side options such as validation – by the time you have
finished creating a large table and are ready to flip back to LightSpeed, the details of
the columns at the top of the table will no longer be at the front of your mind. Still,
many users feel more comfortable with a database-centric workflow, and of course it is
pretty much mandatory if you are working with a legacy database that may be
maintained by another team in the organisation.
Which Should I Choose?
You don’t have to! LightSpeed doesn’t require you to choose a ‘database first’ or a
‘model first’ workflow and stick with it. You can use whichever option is most
convenient at any given time.
Some developers like to drag the first few tables on ‘database first’ style, because this
makes it easier to hook LightSpeed up with the right data provider and connection
string, then evolve the rest of the model in a ‘model first’ style.
As another example, you might also find that you create most of your entities ‘model
first’ but occasionally jump over to the database to design a table that requires
particular fine tuning, and bring that table in ‘database first.’ And perhaps while you
are working on that table you realise you need to make some changes to other tables;
and since you are in the database tool, it’s easier to stay in the database tool to make
those changes, rather than jumping back to LightSpeed and breaking your flow.
LightSpeed doesn’t insist that you follow a single regimented workflow. You can use
whichever tool is more convenient at any given moment, and sync across quickly and
painlessly.
Mindscape LightSpeed User Guide
183
Reorganising Model Elements in a Database First Workflow
The relational database model doesn’t have a way to express some of the richer
concepts of an object-oriented domain model. When you drag a table into a model,
LightSpeed maps it into the object world, but you may need to fix up the results to fit
your desired semantics. LightSpeed provides a number of commands to help with this.
Convert between one-to-many and one-to-one associations. When importing from a
database LightSpeed always infers a foreign key as indicating a one-to-many
association. To convert it to a one-to-one association, right-click the association arrow
and choose Convert to One-to-One Association. You can convert in the other direction
by right-clicking a one-to-one association arrow and choosing Convert to One-to-Many
Association.
Move a property between a base and derived class. When mapping a table to a single
table inheritance hierarchy, the designer defaults to importing columns into the base
class (the one associated with the table). If a property applies only in a derived class,
you can move it by right-clicking the property and choosing Move to Derived Class.
(LightSpeed allows you to move the property into several derived classes if required.)
Conversely, if you decide a derived class property is actually more widely applicable,
you can right-click it and choose Move To Base Class. Moving properties preserves
attributes such as validation and column mapping so this is safer than recreating the
property by hand as well as quicker.
Extract a set of properties into a value object. By default the designer maps all
columns directly into properties of the entity class. If you want to model a particular set
of columns as a value object, select them, right-click and choose Extract to Value
Object. Depending on what other value objects exist in the model you may be offered
the choice of creating a new value object type, mapping the columns to an existing
value object type or moving the selected column(s) into an existing value object
property of the entity. See Value Objects in the Domain Modelling Techniques chapter
for more information.
Hide simple through entities. LightSpeed represents many-to-many associations as
through associations, which are mediated by a “through entity.” In most cases the
through entity has no attributes other than its associations to the entities with the
many-to-many association. In this case you can suppress the through entity in the
Mindscape LightSpeed User Guide
184
designer by right-clicking the through association and choosing Convert to Auto
Through Entity. If you find you need to add properties or attributes to the through
entity, you can reverse this by choosing Convert to Explicit Through Entity. See Manyto-Many Associations below for more information about modelling many-to-many
associations.
Configuring Database Synchronisation
For some databases, LightSpeed offers configurable round-tripping policies to control
database-specific aspects of synchronisation. To see whether there are any
configuration options for your database, see the chapter Working with Database
Providers. At the time of writing, the only database with a configurable policy is
MySQL.
To specify a round-tripping policy, open the LightSpeed Model Explorer, right-click the
root model node, and choose Add New [Database] Roundtripping Policy.
Mindscape LightSpeed User Guide
185
Enums and Other User-Defined Types
The designer knows about most common data types, such as numeric types, strings and
dates, and includes support for some database-defined types such as SQL Server 2008
spatial types. You can also add your own types to the designer. A particularly common
use for this to represent integer columns as .NET enumerations (enums).
To add a type to the designer, open the LightSpeed Model Explorer, right-click the root
model node, and choose Add New User-Defined Type.
Adding an Enum Type to the Designer
To add an enum type to the designer, specify the following options for the newly
created user-defined type:






Name: The name to be displayed in the Data Type drop-down, e.g. Priority.
CLR Type Name: The type name of the enum, as it should be emitted into the
generated code. Typically, this will be the fully-qualified type name, e.g.
MyCompany.Shipping.Priority, though you can omit the namespace if the enum
is part of the same namespace as the model, or if the namespace is imported
through the Imported Namespaces collection.
Data Type: Leave this as Int32 for most enum types. If your enum is backed by
a short or long integer type, specify the appropriate backing type. (This is used
only for database synchronisation. It doesn’t affect the entity definition, or the
enum type definition itself.)
Is Value Type: Leave this as True for enum types.
Is Standard Data Type: Leave this as True for enum types.
Converter Type: Leave this blank for enum types.
Adding a Database-Defined Type to the Designer
The designer already supports some database-defined types. If you want to use a type
which is defined by your database but not supported by the designer, you can add it as
a user-defined type by specifying the following options:
Mindscape LightSpeed User Guide
186






Name: The name to be displayed in the Data Type drop-down, e.g.
SqlHierarchyId.
CLR Type Name: The .NET type name of the type, as it should be emitted into
the generated code. This is typically defined by the ADO.NET provider, e.g.
Microsoft.SqlServer.Types.HierarchyId. You must fully qualify the name, or add
the containing namespace to the Imported Namespaces collection.
Is Value Type: Set this to True or False depending on whether the ADO.NET
type is a struct or a class. (This is important if you have nullable instances of
the type.)
Is Standard Data Type: Set this to False for database-defined types.
Database Type Name: Set this to the SQL name of the database-defined type,
e.g. hierarchyid. This is used for database synchronisation.
Converter Type: Leave this blank for types that are defined by the ADO.NET
provider.
Adding a User-Defined Type with Custom Mapping
The third main use case for user-defined types is when you have a type that has a
custom mapping from the database to the CLR. This can be because the type is a
standard one but it is mapped to the database in a non-standard way, such as a
Boolean which is mapped to a Y/N string column in the database, or an enum which is
stored using its string name instead of its integer value, or it may be because the
database column contains data which doesn’t map to a standard type, such as a string
column in a legacy database which contains several pieces of data packed together as
comma separated values.
You can add such a type as a user-defined type by specifying the following options:



Name: The name to be displayed in the Data Type drop-down, e.g. Money or
YesNo.
CLR Type Name: The .NET type name of the type, as it should be emitted into
the generated code. This may be a .NET type defined in your code, e.g.
MyCompany.Money, or a standard type, e.g. System.Boolean.
Is Value Type: Set this to True or False depending on whether the CLR type is a
struct or a class. (This is important if you have nullable instances of the type.)
Mindscape LightSpeed User Guide
187




Is Standard Data Type: Leave this as True if the type is stored in the database
using a standard designer type such as string or integer. Set it to False if it uses
a non-standard type, such as a database-defined type or a SQLCLR type.
Data Type: If you left Is Standard Data Type as True, set this to the standard
type used for storing this type in the database. In the Yes/No example, this
would be String. This is used for database synchronisation and does not affect
the entity definition.
Database Type Name: If you set Is Standard Data Type to False, set this to the
SQL name of the type in the database. This is used for database
synchronisation and does not affect the entity definition.
Converter Type: Set this to the name of the class which converts between the
database representation and the CLR type, such as
Mindscape.LightSpeed.FieldConverters.YNBooleanFieldConverter. The class
must implement the IFieldConverter interface (see also the FieldConverter
class). See Mapping Database Types to Domain Types in the chapter Working
with Legacy Databases for more information.
Custom mappings are primarily intended for existing databases where the data format
is already established. When creating new databases, prefer to use standard
LightSpeed conventions, and where it’s appropriate to use a domain type such as
Money, consider mapping it using a value object rather than a custom mapping.
Using a User-Defined Type
Once you have declared a user-defined type, you can use it just like any other type. It
appears in the Data Type drop-down, and can be entered or shown against the
property in the usual way.
Mindscape LightSpeed User Guide
188
Refactoring in the Designer
Refactoring is the process of improving your code without changing its observed
behaviour. Familiar refactorings include Rename, Extract Method, etc. The LightSpeed
designer provides several commands to help you refactor your model. All of these can
be found by right-clicking and choosing Refactor.
Refactoring
Rename
Applies To
Property or
entity
Create Partial Class
Entity
Create Partial Classes
Model
Convert to Manual
Implementation
Property
Extract Interface
Entity
Mindscape LightSpeed User Guide
Description
Performs a solution-wide rename, i.e. all
references elsewhere in your code to the
property or entity are updated. You can
optionally keep the property or entity mapping
to the same column or table name if you do
not also want to rename your database object.
Creates an empty partial class for the entity,
where you can start adding your own code.
Creates empty partial classes for all entities in
the model.
Removes the automatically generated code for
the selected property and copies it into a
partial class file (you must have created a
partial class file first), so that you can manually
edit it, for example to add business logic or
error checking.
Creates an interface declaring the properties
of the selected entity. (Note that the entity is
not automatically declared to implement the
new interface: you must add this declaration
by hand via a partial class file.) This refactoring
is not available in Visual Basic.
189
Custom Views
Models, especially large models, can be hard to navigate. LightSpeed provides several
options to help you visualise your model more easily.
Filtering
You can filter your view to show only selected entities. This can be useful for locating
the part of the model you’re interested in or for visualising model behaviour.
To filter the model, open the LightSpeed Model Explorer, select how you want to filter
the view and enter the string to be matched. For the convenience of keyboard users,
you can also use a prefix character on the filter string instead of clicking the “Filter by”
option.
Filter By
Name
Prefix
(none)
Tag
#
Effect
Shows only entities whose names contain the filter string.
The string is treated as a regular expression, so you can
perform simple pattern matching.
Shows only entities with a matching tag. Entities with no
tags at all are always shown. Tags are a convenient way of
labelling a related set of entities. You can set tags using the
Tags property. An entity can have more than one tag.
Mindscape LightSpeed User Guide
190
Filter By
Aggregate
Prefix
@
!
Effect
Shows only entities which are part of the named aggregate
specified by the filter string. An entity is part of the named
aggregate if it has an association on which that aggregate is
specified.
Inverts the filter: that is, entities which match the filter are
hidden instead of shown. (This can be specified only through
a prefix, not through the Filter By drop-down.)
You can optionally show additional entities which, although they don’t match the filter
themselves, are related to entities that do. This help you to see the filtered entities in
context.
Show Option
Show selected
Show associated
Show load graph
Show inheritance
Effect
Shows only entities which match the filter.
Also shows entities which have an association (one to many, one to
one or through association) with the entities which match the
filter. Only one level of association is followed; indirectly
associated entities are not shown.
Also shows entities which are eager loaded by an entity which
matches the filter. The full eager load graph is shown, not just
immediately associated entities. Only “always eager load”
associations are considered; optionally loaded associations via
named aggregates are not shown (but can be shown using the @
filter).
Also shows the base and derived classes of the entities which
match the filter. Sibling classes are not shown: if you need to
understand the full hierarchy, enter a filter which matches the root
of the hierarchy.
QuickViews
To save the current filter, right-click the model and choose View > Save Current as
QuickView.
Mindscape LightSpeed User Guide
191
To reload a saved filter, right-click the model, choose the View submenu and select the
name of the saved view. Note that LightSpeed saves and re-applies the filter definition:
if the model has changed, the result of the filter may be different from before.
You can rename, edit and delete QuickViews via the Quick Views folder in the
LightSpeed Model Explorer.
The View submenu also contains commands to quickly filter by tag, if any tags are
defined in the model, and to remove all filtering and show the entire model.
Mindscape LightSpeed User Guide
192
Linked Models
Another solution to the problem of large, complex models is to replace a single model
file with multiple linked model files. Linked model files work well when your domain
consists of a number of distinct subdomains, with relatively few links across subdomain
boundaries. Don’t use linked model files if you have a lot of associations across
subdomain boundaries – cross-file associations require quite a bit of maintenance and
if you have too many of them you may find they are more trouble than they are worth!
To link a set of model files, you must do two things:


Set the Name of each model to the same value. (If you are specifying a
namespace in the model, then this must be the same for all of the linked
models too.)
Choose one of the models to be the “main” model. For all of the other models,
set Is Linked Child to True.
If you use the Paste as Link command to create entity links, or the Refactor > Split
Model At Association command to split an existing model into two files, it will set these
properties for you.
Code Generation
Each linked model file in a set is code-generated separately. The code generation is
done in such a way that the generated files combine to produce a single model using
partial classes. For example, you will end up with a single strong-typed unit of work
class, with queryable properties for all of the entity types, although the implementation
for this class will be spread across three files.
Because LightSpeed depends on partial classes to combine code, all linked model files
must be in the same project.
Creating Associations Across Model File Boundaries
You can represent an entity from one file in another file by dragging an Entity on from
the Toolbox and setting its Name to the desired linked entity, and settings its Is Link
Mindscape LightSpeed User Guide
193
property to True. You can then create associations to the linked entity. You must create
the associations in both files.
For example, suppose you have two files, Sales.lsmodel and Logistics.lsmodel. The Sales
subdomain contains an entity named PurchaseOrder and the Logistics subdomain
contains an entity named Shipment. You want to create a one-to-many association
between these entities, so that a PurchaseOrder has a collection of Shipments and a
Shipment has a reference to a PurchaseOrder. To do this:







Open Sales.lsmodel and drag on an Entity from the Toolbox.
Set the entity’s Name to Shipment and Is Link to True.
Click OneToManyAssociation in the Toolbox and drag an arrow from
PurchaseOrder to Shipment.
Open Logistics.lsmodel and drag on an Entity from the Toolbox.
Set the entity’s Name to PurchaseOrder and Is Link to True.
Click OneToManyAssociation in the Toolbox and drag an arrow from
PurchaseOrder to Shipment.
Save both models and rebuild your project.
Creating and Maintaining Entity Links
A quick way to create a link is to select the “main” entity definition, choose Edit > Copy
or press Ctrl+C, switch to the target file, right-click the model background and choose
Paste as Link. This will automatically set up the entity link’s properties, and can also link
the two model files for you.
Important: An entity link is not aware of the entity it links to. Therefore, you must
manually keep properties in sync between the link and the original entity. In particular,
if the entity has an Identity Type other than Int32, you must set the Identity Type on
the link to be the same as the Identity Type on the entity. If you are using model-first
database synchronisation or migrations, and the entity overrides the Table Name,
Identity Column Name or Schema, then you must also replicate these settings to the
link. You can check that these settings are in sync using the Check Links menu
command.
To see the definition of a linked entity, right-click the link and choose Go To Linked
Entity.
Mindscape LightSpeed User Guide
194
Changing the Designer Defaults
When you create an entity in ‘model first’ style – that is, by dragging an Entity icon
from the toolbox – LightSpeed provides defaults for its various settings. You can
change the defaults for newly created entities by specifying a defaults policy.
To do this, open the LightSpeed Model Explorer, right-click the root model node, and
choose Add New Defaults Policy. You can then edit the defaults through the Policies
folder.
You can change the defaults for:




Identity type. The Identity Type for newly created entities.
Storage options. The Track Create Time, Track Update Time, Soft Delete and
Optimistic Concurrency Checking options for newly created entities.
Base class. The base class for newly created entities. Model-wide base classes
are usually there to provide a few common fields or services, so this defaults to
concrete table inheritance
New in LightSpeed 5, defaults can also be specified for generating association
DataMember attributes.
The defaults policy affects only newly created entities, not existing entities. If you
create an entity by dragging a table from Server Explorer, LightSpeed uses the table
definition to infer the identity type and storage options, ignoring the defaults policy,
though the base class is still respected.
Mindscape LightSpeed User Guide
195
Customising the Generated Code
As discussed in Creating Domain Models, LightSpeed models are ultimately just code,
and the visual designer works by generating that code for you. LightSpeed offers
several ways to customise and extend this generation process.
Using Your Own Code Generation Templates
Code generation in LightSpeed uses a set of NVelocity templates, written in VTL
(Velocity Template Language) and found in the installation directory under Tools >
Designer > Templates. You can edit these templates to change the way that the
designer generates LightSpeed code.
However, it’s recommended that you don’t edit the templates at the installation
location for two reasons. First, whenever you upgrade LightSpeed, it will reinstall its
templates over the top of yours. Second, if you’re working with other developers, or
across multiple machines, you need to make sure the custom templates get copied to
each machine.
To solve these problems, LightSpeed allows you to keep your templates with the
project. By doing this, you eliminate the risk of the templates being overwritten during
install, and you can treat them as part of the project rather than a machine setting —
for example putting them in source control so that when another developer gets the
project sources they get the current templates as well.
To do this, copy the template files from the installation location to a suitable projectspecific location. Be sure to choose the template appropriate for your project language
(under the C# or VB directory in the installation location).
Then go into Solution Explorer, select the project node and look at the Properties grid.
If the project contains a LightSpeed model (a .lsmodel file), you’ll see an extra entry
here called LightSpeed Template File. (You may need to open the .lsmodel file to make
the extra entry appear.) Edit this to point to your copy of the “main” template
(typically Base.vm):
Mindscape LightSpeed User Guide
196
From this point on, whenever the model changes, the code will be regenerated using
your copies of the templates.
There are a couple of maintenance considerations for custom templates:
1. Visual Studio won’t automatically regenerate code when you change the template.
Changes to the templates will only take effect next time you edit your model. If you
don’t actually need to do anything to the model, just move something and move it back
again – that will be enough to trigger regeneration.
2. We occasionally ship updates to the default templates, to reflect new features or
options, or to fix bugs. If these updates are relevant to you, you’ll want to fold them
into your custom templates. It’s therefore a good idea to keep a copy of the
“Mindscape version our custom templates are based on” around. That way, when we
update the templates, you can use a diff and merge tool to find the changes between
Mindscape LightSpeed User Guide
197
the Mindscape versions and merge them into your files (or to merge your diffs from the
baseline onto the new baseline).
Extending the Designer Metamodel
The designer allows you to specify entity and property options that are relevant to
LightSpeed. It’s sometimes useful to be able to specify other options which aren’t
relevant to LightSpeed but which logically belong on the entity or property, and to emit
those options through a custom template.
For example, suppose you have a user interface framework which uses
DisplayNameAttribute to render the name of each property. You could specify
DisplayNameAttribute manually, using the Custom Attributes collection, but this could
become inconvenient if you have a lot of properties. You might wish instead to add a
Display Name setting to entity properties, so that you could readily edit it in the grid,
and to generate DisplayNameAttribute using that Display Name setting.
You can do this in the LightSpeed designer using extension properties. The default code
generation templates ignore extension properties, but custom templates can use them
to generate whatever code is required.
To define an extension property, open the LightSpeed Model Explorer, right-click the
root model node, and choose Add New Extension Property Definition. You can specify
the following settings for your extension property:




Name: The name by which templates refer to the extension property.
Extends: The kind of model elements to which the extension applies.
Data Type Name: The CLR type name of the type of data that can be stored in
the extension property. This must be fully qualified, e.g. System.Int32 or
System.String. If the type is not defined in mscorlib.dll or System.dll, then the
name must be assembly-qualified, or the assembly must be included in the
Design Time Assemblies collection.
Category, Display Name and Description: Additional options for how the
extension property is displayed in the Visual Studio Properties grid.
Mindscape LightSpeed User Guide
198
Once you have defined an extension property, it appears in the properties grid for
every element of the kinds you specified in Extends, and you can enter values for it as if
it were a built-in property.
To use an extension property in a template, there are three methods which you can call
from the template:



HasExtendedProperty(name): Returns true if the user set a value for the
named extension property on the element at hand, or false if the user did not
set a value (or the extension property does not apply to this kind of element).
GetExtendedPropertyValue(name): Returns the value set by the user for the
named extension property on the element at hand, and throws an exception if
the user did not set a value. You should always call HasExtendedProperty
before calling this method.
GetExtendedPropertyValue(name, defaultTo): Returns the value set by the
user for the named extension property on the element at hand, or the
defaultTo value if the user did not set a value (or the extension property does
not apply to this kind of element). You can safely call this without calling
HasExtendedProperty.
The value returned from GetExtendedPropertyValue is the actual value of the property.
This may not be suitable for emitting into the generated code directly. For example, if
GetExtendedPropertyValue returns a string, and you want to emit that string into a
DisplayNameAttribute declaration, you will usually want to quote that string. Or if it
Mindscape LightSpeed User Guide
199
returns an enum value, you will usually want to emit it qualified with the enum type
name.
[DisplayName(Full Name)]
[DisplayName("Full Name")]
// would be an error
// correct
[Priority(High)]
[Priority(Priority.High)]
// would be an error
// correct
To convert a literal value to a code fragment for that literal value, call
$Translator.TranslatePrimitive from your custom template.
Generating DisplayNameAttribute from the DisplayName extension property
#if ($field.HasExtendedProperty("DisplayName"))
[DisplayName($Translator.TranslatePrimitive($field.GetExtendedPropertyValue("DisplayName")))]
#end
Using TranslatePrimitive ensures that values are converted to literals in a way which is
correct for both the value and the target programming language.
(Of course, if the extension property is intended to be used in a VTL expression such as
a #if test, or if it represented a member name such as a property in a strongly-typed
resource class, then you will not want to quote it. This is one of the reasons why
GetExtendedPropertyValue returns a value rather than a code fragment.)
Using T4 Templates with the LightSpeed Designer
You can also use Visual Studio’s T4 templates with a LightSpeed model. This technique
is useful when you want to generate separate files from the model, rather than
tweaking or extending the entity code as you would if you were customising the built-in
templates. For example, you could write a T4 template to generate WCF service
interfaces or ASP.NET MVC controllers, perhaps shaped by your own extension
properties.
Mindscape LightSpeed User Guide
200
To create a T4 template which works with a LightSpeed model, create a .tt file in your
project and add the following declarations at the top:
<#@ template
inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
<#@ assembly name="Mindscape.LightSpeed.Generator.Model.dll" #>
<#@ assembly name="Mindscape.LightSpeed.Generator.Integration.Dsl.Mapping.dll" #>
You must also add the paths to the designer assemblies to your project’s Reference
Paths collection (Project > Properties > Reference Paths). Alternatively, you can specify
the path in the assembly directives. The designer assemblies are not redistributable
and you should not add them to your project. T4 needs them to process the template
but you do not need them at run time.
You must then specify the file extension for the generated file, using the output
directive:
<#@ output extension=".txt" #>
Finally you must specify the LightSpeed model from which to generate code, using the
LightSpeedModel directive. The processor attribute is always
LightSpeedModelDirectiveProcessor; the requires attribute should specify
fileName='lsmodel_file'. The following directive hooks the T4 template up to a
LightSpeed model named Sample.lsmodel:
// Line breaks added for clarity
<#@ LightSpeedModel processor="LightSpeedModelDirectiveProcessor"
requires="fileName='Sample.lsmodel'" #>
You can now write T4 code as normal. The LightSpeed model is available through the
this.Model reference.
Mindscape LightSpeed User Guide
201
<#
foreach (Entity entity in this.Model.Entities)
{
#>
// Emit entity code here
<#
}
#>
The designer object model is not documented so you will need to ask in the LightSpeed
forum or use the Visual Studio Object Browser to determine the programmatic names
of metamodel classes and properties (though they usually correspond to the display
names shown in the toolbox and property grid).
Mindscape LightSpeed User Guide
202
Many-to-Many Associations
As discussed in the chapter Creating Domain Models, LightSpeed represents
many-to-many associations as through associations. A through association between A
and B is implemented using a through entity, which represents an association between
one A entity and one B entity. A given A entity may be associated with multiple
through entities, each of which links on to one B entity, and vice versa.
The designer provides two ways of presenting through associations. You can choose to
show just the many-to-many relationship, treating the through entity as an internal
implementation detail, which is the most convenient approach in most cases. Or you
can choose to show the through entity explicitly, which provides you with fine control
and extensibility at the expense of visual clutter.
Using an Auto Through Entity
The first option is referred to as using an auto through entity. Auto through entities
avoid cluttering up the diagram when there’s no additional data associated with the
relationship, and you don’t need fine control over database mapping, eager loading,
etc. To specify an auto through entity, select the through association arrow and enter
a name for the through entity in the Auto Through Entity box:
Mindscape LightSpeed User Guide
203
If you have an explicit through entity and it’s not adding any value, you can convert it to
an auto through entity by right-clicking the through association and choosing Convert
to Auto Through Entity.
Using an Explicit Through Entity
The second option is referred to as using an explicit through entity. In this case the
through entity appears on the diagram surface as a fully-fledged entity, allowing you to
customise features such as database mapping and eager loading, and to associated
extra data with the relationship. For example, suppose you represent tagging by a
many-to-many association between Contributions and Tags, and you want to record
which user applied each Tag to each Contribution. You can’t store that information on
the Contribution or the Tag. The best place to store it is therefore on the through
entity which represents the contribution–tag association. With an explicit through
entity this is easy because you can work with the through entity just like any other
entity.
To specify an explicit through entity, select the through association arrow and choose
the through entity from the Through Entity drop-down.
When you show an explicit through entity, you must also explicitly model the one-tomany associations between the main entities and the through entity. This allows you
to control details such as foreign key column mapping.
Mindscape LightSpeed User Guide
204
If you have an auto through entity and you need to add extra data or fine-tune the
database mapping, you can convert it to an explicit through entity by right-clicking the
through association and choosing Convert to Explicit Through Entity.
How Do Auto and Explicit Through Entities Differ?
Fundamentally, they don’t. They result in the same code and the same database
schema. They are not different things, just different ways of showing the same thing
on the design surface.
Mindscape LightSpeed User Guide
205
Designer Shortcuts and Tips
Speeding Up Property Entry
You can add a new property to an entity by pressing the Ins key while the entity or any
of its properties is selected.
You can set the type of a property by entering the type name before the property
name when editing the property, as if declaring a C# variable. For example, if you type
int Height, then the property is named Height and is of type Int32.
As in C#, you can use the question mark suffix to make the property nullable (e.g. int?
Height).
Using the Ins key and inline type editing, you can easily enter multiple properties
without taking your hands away from the keyboard. This can be much quicker than
using the mouse when entering a lot of properties.
Custom Attributes
You can apply custom attributes to generated properties using the various Custom
Attributes collections. You can set custom attributes on entities and properties via the
Custom Attributes option, and for associations you can set them on either end of the
association and the foreign key (via options such as Collection Custom Attributes and
Backreference Id Custom Attributes).
Custom attributes are applied to the wrapper property, not the backing field. Most
LightSpeed attributes have to go on the backing field, so you can’t use custom
attributes to apply LightSpeed attributes – it’s usually more convenient to use the
Mindscape LightSpeed User Guide
206
designer equivalents anyway. Rather, they are intended for attributes consumed by
other frameworks, such as BrowsableAttribute or DisplayNameAttribute.
When you enter an attribute in the Custom Attributes dialog, you must fully qualify the
attribute name, e.g. System.ComponentModel.Browsable. If you’re applying a lot of
attributes from the same namespace, you can get around this by adding the
namespace to the Imported Namespaces list (via the LightSpeed Model Explorer).
Grabbing an Image of Your Model
To get an image of your entire model, press Ctrl+C or choose Edit > Copy. You can then
paste this into Word, PowerPoint or Paint as a bitmap.
If you want only a subset of your model, select the elements you want to include in the
image before copying.
If you are filtering the view, the copied image will show only the elements that are
included in the current filter.
Using Reminder Notes
The toolbox includes a ‘reminder’ icon which you can use to add notes to your model.
You can link a note to a URL by filling out the Link URL property; in this case, the note
will display a More… link which takes you to that URL. For example, a “to do” note
could be linked to an entry in a bug tracking system.
Use Get Started for Configuration File Entries
Once you’ve hooked your model up to a database, you can use the Get Started
command to see the LightSpeed configuration file entries. You can paste from the Get
Started screen into your web.config or app.config. Be aware that the provided entries
are a starter set: you may still need to configure other settings by hand, such as
identityMethod or quoteIdentifiers.
Mindscape LightSpeed User Guide
207
Rearranging Properties
To rearrange entries in an entity’s properties list – for example, to alphabetise them or
to group related items together – right-click a property and choose Move Up or Move
Down.
Assigning Keyboard Shortcuts to LightSpeed Commands
To assign a keyboard shortcut to a LightSpeed designer command:





Open Tools > Options > Environment > Keyboard, or Tools > Customize >
Keyboard.
In the “Show commands containing” checkbox, enter LightSpeed.
The list box shows a list of LightSpeed designer commands.
Choose the command you want, type the desired keystroke into the “Press
shortcut keys” box and click Assign.
When you’ve finished mapping commands, click OK.
Using a Custom Base Class for Your Entities
There are two ways to use a custom base class for your entities.
One way is to create the base class normally using the designer, draw inheritance
arrows and set the inheritance type to Concrete Table Inheritance. This works well with
designer-database synchronisation because the designer can know about fields defined
in the base class, but for larger models can result in a lot of arrows cluttering up the
diagram. If the clutter becomes a problem, you can hide these arrows by selecting
them and setting Show On Diagram to false.
The alternative approach is to define the base class in code, create an External Class
Reference to that class via the LightSpeed Model Explorer, and set each entity’s Base
Class to the external reference via the Properties window. This avoids lots of
inheritance arrows, but will result in a warning that the external class is being excluded
each time you sync to the database.
Mindscape LightSpeed User Guide
208
Changing Property or Entity Names When Other Code Already Uses
Them
If you want to change the name of a property or entity in the domain model, but you
have lots of code that already uses the existing name, and you don’t want to change
the database schema either, you can use the Refactor > Rename command to help you
out. Right-click the property or entity and choose Refactor > Rename. Enter the new
name and make sure that “Keep existing name as database column/table name” is
ticked. LightSpeed will update all references to the property or entity in your code, and
create a mapping between the renamed element and the existing database name.
Writing Custom Property Getter or Setter Code
To write custom code for property getters and setters, select the property you need to
write custom code for, go to the Properties window and change the Generation option
to FieldOnly. You can then write your own property getter and setter in the partial
class. You can also right-click the property and choose Refactor > Convert to Manual
Implementation; however, this will also convert the backing field to a manual
implementation, which will prevent LightSpeed from applying subsequent changes to
property settings.
Setting Validation Options Which Aren’t In the Properties Window
To set validation options that aren’t available in the Properties window, like
ValidateUriAttribute.UriKind or custom error messages, use the LightSpeed Model
Explorer to locate the validation, and edit its properties. See Validation in the Creating
Domain Models chapter for details.
Foreign Key Field Attribute in Designer
From LightSpeed 5 onwards the designer supports setting the ForeignKeyFieldAttribute
on one-to-one associations.
Mindscape LightSpeed User Guide
209
Show Data Command
Right-clicking on an entity and selecting ‘Show Data’ will open the Visual Studio table
editor, allowing you to view the table data for that entity. This is available for SQL
Server, MySQL and SQLite. For non-SQL Server databases you will need to have the
appropriate Visual Studio integration package installed. For this to work you will need
to have a connection to the database defined in the Server Explorer (which you will be
prompted to create if you’re going the model-first route).
Mindscape LightSpeed User Guide
210
Advanced Querying Techniques
The Basic Operations chapter shows how to carry out most queries using LightSpeed.
Using the techniques shown there, you can perform filtering, ordering and paging; and
if you are using LINQ you can easily perform more advanced queries using the standard
LINQ operators or the C# and Visual Basic language integrated syntax.
This chapter describes additional query functionality such as full text search, speeding
up query times by using compile queries and how to exploit database-specific or userdefined functions in your queries. This chapter also describes how you can complex
queries if you are using query objects instead of LINQ.
Mindscape LightSpeed User Guide
211
Full Text Search
The search engine capabilities in LightSpeed provide an easy to use free text search
implementation that is database engine independent. It provides developers a
mechanism to perform Google-style queries and have them match any entities within a
defined scope – for example, provide any entity that has the word ‘car’ in it.
To implement full text search in your LightSpeed application you need to use a search
provider. Out of the box, LightSpeed ships with a single search provider which is built
on top of the open-source Lucene project. Lucene provides a very high performance
and scalable search infrastructure. Developers can plug in their own search provider by
implementing the ISearchEngine interface.
To use a full text search you first need to instruct LightSpeed to use the search engine
and where it can store the search index on disk. This is done via configuration of the
LightSpeedContext by either XML configuration file or in code.
Configuring the default search provider for LightSpeed in the config file
<add name="Test"
connectionStringName="Test"
dataProvider="Sqlite3"
searchEngineClass="Mindscape.LightSpeed.Search.LuceneSearchEngine"
searchEngineFileLocation="c:\foo\bar\baz"/>
Make sure that the search engine file location directory is writable by your application
process as it will be written to as new entities are saved.
Indexing Data
LightSpeed does not index the entire database – it will only index data on entities that
you care about. To enable indexing, select each of the properties that you want to
include in the search index and set their Include in Full Text Index option to true. To do
this in hand coded entities, apply IndexedAttribute to the fields:
Mindscape LightSpeed User Guide
212
Instructing LightSpeed to store a value in the search index
[Indexed]
private string _registration;
In advanced scenarios you can override the Entity.SearchData method to provide
custom indexing data to be indexed. Note that you will still need to set Include in Full
Text Index on one of the entity’s properties (or apply IndexedAttribute to one of its
fields) to notify LightSpeed that you wish the entity to be indexed; however, when you
override Entity.GetSearchData LightSpeed will use your custom implementation instead
of getting indexing data from the attributed field.
Overriding the GetSearchData() method to combine entity data for one entity
protected override string GetSearchData()
{
if (Warrant != null)
{
return Warrant.SerialNumber + " " + Registration;
}
return Registration;
}
Overriding the GetSearchData method provides a powerful mechanism for collapsing
indexed data into one entity. For example, when searching for a Car you may want to
include engine details. These are stored in an Engine entity, but you can add them to
the Car index data by returning them from Car’s GetSearchData method.
If updating the fields that are being indexed or overriding GetSearchData, be sure to
call a Rebuild of your search index to ensure that the search engine index is up to date
with your changes.
Building the Search Index
By default LightSpeed will update the search index when you call
UnitOfWork.SaveChanges. The changes that have occurred as part of the save will be
Mindscape LightSpeed User Guide
213
updated in the search index. However, if you are adding a search index to an existing
database it’s important to rebuild the search index from the entire database, not just a
single change set. LightSpeed provides the Rebuild method for rebuilding the entire
search index. Call Rebuild with the types of entities that you want to have indexed.
Rebuilding the entire search index
_context.SearchEngine.Rebuild(IsolationLevel.ReadCommitted,
typeof(Comment), typeof(Tag), typeof(Car), typeof(Contribution));
Rebuilding the search index should not be needed often. Calling Rebuild() can be an
expensive operation, and for larger database solutions you may wish to use Lucene
outside of the LightSpeed framework to achieve the best performance.
Performing Searches
Performing searches using LightSpeed is done with a LightSpeed query object. This first
example demonstrates searching for a comment with the word “video” in it.
Basic search over one entity type
Query query = new Query();
query.SearchQuery = "video";
using (var unitOfWork = _context.CreateUnitOfWork())
{
unitOfWork.Find<Comment>(query);
}
Searching with LightSpeed is not limited to only returning one type of entity. The next
example demonstrates searching for comments and tags at the same time.
Mindscape LightSpeed User Guide
214
Basic search over several entity types
Query query = new Query();
query.SearchQuery = "video OR Mindscape";
using (var unitOfWork = _context.CreateUnitOfWork())
{
var results = unitOfWork.Search(query, typeof(Comment), typeof(Tag));
Assert.AreEqual(3, results.Count);
}
You can combine a full text search and a normal SQL query to limit the text search.
Advanced search combining normal query and search index
using (var unitOfWork = _context.CreateUnitOfWork())
{
Query query = new Query();
query.SearchQuery = "video";
query.QueryExpression = Entity.Attribute("Member") == unitOfWork.FindById<Member>(41);
var count = unitOfWork.Count<Comment>(query);
Assert.AreEqual(1, count);
}
Advanced Operator Support
As LightSpeed is using Lucene by default as a search engine developers can use any of
the advanced operators that Lucene supports by specifying them in the SearchQuery
string.
Further Search Reading
If you are developing a significant solution with LightSpeed and working with Lucene
for search, we recommend that you read about Lucene and understand how to
configure and operate it for the best performance. See the Lucene Web site for more
information.
Mindscape LightSpeed User Guide
215
Invoking SQL Functions
Several SQL functions are built into LightSpeed and can be used in queries. If you are
using LINQ, you need only specify the corresponding CLR method and LightSpeed will
translate it to SQL. You can also extend this mapping to custom functions. If you are
using query objects, you can specify some functions using query expression member
functions, but in other cases must emit the SQL function explicitly.
Mapping SQL Functions in LINQ
When you write queries using LINQ, you write them using .NET objects. To use a
function in the query, you just write the appropriate .NET method or property in your
query, and LightSpeed will translate it to the equivalent SQL function. For example, if
you want to perform a case-insensitive comparison, you could use the .NET
String.ToUpper method to force all your strings to upper case:
from e in unitOfWork.Employees
where e.LastName.ToUpper() == searchText.ToUpper()
select e;
LightSpeed translates this to the SQL UPPER function:
SELECT ... FROM Employee WHERE UPPER(Name) = @p0
Many standard .NET methods and properties are built into the LightSpeed provider.
However, your database may provide SQL functions that don’t have a .NET equivalent,
or you may have created user-defined functions that you want to use in queries.
In this case, you can register a mapping between a .NET method and a SQL function.
Once this is done, you can use the .NET method in a query, and LightSpeed will
translate it to the specified function.
Mindscape LightSpeed User Guide
216
To register a mapping between a .NET method and a SQL function, call
ServerFunctionDescriptor.Register. You can register a member method or an extension
method: this allows you to create methods on existing classes purely to have
something to map to SQL. For example, suppose you wanted to call SQL Server’s
(admittedly antiquated) DIFFERENCE function, which returns how similar two strings
sound. There is no String method that maps naturally to DIFFERENCE, but you can
define and register an extension method:
Mapping a .NET method to a SQL function
// Declaring the extension method
public static class SoundexExtensions
{
public static int SimilarityTo(this string first, string second)
{
throw new NotImplementedException();
}
}
// Mapping the method
MethodInfo similarityTo = typeof(SoundexExtensions).GetMethod("SimilarityTo");
ServerFunctionDescriptor.Register(similarityTo, "DIFFERENCE");
Once a method is mapped, you can use it in a LINQ query just as if it were built into
LightSpeed:
Using a mapped method
from t in UnitOfWork.Towns
orderby t.Name.SimilarityTo("radavleetsa") descending
select t;
The resulting SQL calls the SQL function to which the .NET method was mapped:
SELECT ... FROM Town ORDER BY DIFFERENCE(Name, @p0) DESC
Mindscape LightSpeed User Guide
217
Mapping to a Custom Function
The same technique applies to mapping .NET methods to user-defined functions.
However, you must be careful to specify the server-side function name in exactly the
way the database wants to see it. For example, SQL Server requires that user-defined
function names be prefixed with the schema. So when mapping your .NET method you
must specify “dbo.MyFunction” instead of just “MyFunction.”
ServerFunctionDescriptor.Register(clrMethodInfo, "dbo.MyFunction");
Mapping Argument Order
When you map a .NET method to a SQL function, by default, the expression to which
the .NET method is applied becomes the first argument to the SQL function.
Sometimes this is not appropriate. For example, suppose you mapped the
String.IndexOf method to the SQL Server CHARINDEX function. CHARINDEX requires
the string to be looked for as the first argument, not the string to look in.
ServerFunctionDescriptor.Register provides an overload which takes an implicit
argument index. If you use this, the expression to which the .NET method is applied –
the ‘implicit’ argument – will appear at that (0-based) index in the SQL function’s
argument list.
ServerFunctionDescriptor.Register(indexOfMethod, "CHARINDEX", 1);
Mapping Member Functions
Microsoft SQL Server allows you to define custom .NET types and methods within the
database. A major use case for this is SQL Server 2008’s spatial data support, where
the comparison functions are member functions of the geography and geometry data
types. Member methods are not called using the usual SQL syntax, and must therefore
be mapped specially. To indicate that the translation of a .NET method is a member
Mindscape LightSpeed User Guide
218
method and must be emitted with member syntax, prefix the server method name
with a dot.
MethodInfo method = typeof(SqlGeography).GetMethod("STDistance");
ServerFunctionDescriptor.Register(method, ".STDistance");
// note the dot
Invoking SQL Functions Using Query Objects
The mapping step in LINQ is needed because the C# and Visual Basic compiler
type-check LINQ expressions, so you can only call SQL functions by representing them
as .NET methods on a suitable .NET type. If you are using query objects, you can pass
SQL function names as strings, avoiding the need for mapping. To do this, use the
Function method, passing the name of the SQL function and any required arguments:
Invoking a SQL function using query objects
unitOfWork.Find<Town>(
Entity.Attribute("Name").Function("DIFFERENCE", "radavleetsa") > 2);
As with mapped functions in LINQ, the expression by default becomes the first
argument, but you can override this by specifying an implicit argument index. Also as
with LINQ mappings, you can specify that the function should be called using member
syntax by prefixing it with a dot.
Entity.Attribute("UserName").Function(1, "CHARINDEX", " ") > 0;
Entity.Attribute("Location").Function(".STDistance", searchLocation) < distance;
Mindscape LightSpeed User Guide
219
Compiled Queries
Compiled queries allow you to significantly speed up your query times by preprocessing LINQ queries or LightSpeed query objects into a compiled form. These can
then be executed repeatedly in different units of work and with different parameters.
The performance increase is gained by reducing query processing overhead as it only
needs to be conducted once, instead of each time you run the query.
Compiling a LINQ based query
To use compiled queries when writing a LINQ query first bring the
Mindscape.LightSpeed.Linq namespace into scope via a using statement and then you
can call the Compile() extension method on a LINQ query. This will return you a
CompiledLinqQuery instance which can be later executed.
Two overloads are available; one for queries that return collections and another for
values, such as Count(), Sum() or First(). The code below illustrates the use:
Compiling a LINQ query
using Mindscape.LightSpeed.Linq;
// bring extension methods into scope
var query = from p in UnitOfWork.Penguins
where p.Age < 9
select p;
CompiledQuery<IList<Penguin>> youngPenguins = query.Compile();
CompiledQuery<int> youngPenguinCount = query.Compile(q => q.Count());
Note that at this point you need a unit of work to compile the query. No entities are
loaded, but a LightSpeedContext is required in order to build the right SQL statement
for your database. This also provides handy query properties to write the query against.
You can throw away the ‘compilation’ unit of work once this is done, and provide a
‘real’ unit of work when you execute the query.
Compiling a query object based query
To use compiled queries when writing a query object based query you need to use the
CompiledQuery class which can be found in the Mindscape.LightSpeed.Querying
namespace. This class has static methods for compiling Find, Count, Calculate and
Project queries. The code below illustrates the use:
Mindscape LightSpeed User Guide
220
Compiling a query object
Query query = new Query(typeof(Penguin), Entity.Attribute("Age") < 9);
CompiledQuery<IList<Penguin>> cquery = CompiledQuery.CompileFind<Penguin>(query,
_unitOfWork);
Executing a compiled query
A compiled query is represented by a CompiledQuery<T> object, where T is the type of
data to be returned — typically a list of entities, but it could also be a numeric type for
count and aggregate queries, or a list of non-entity objects for projections.
To run a compiled query, call its Execute() method, passing the unit of work in which
you want to execute it:
Running a compiled query
IList<Penguin> ps = youngPenguins.Execute(unitOfWork);
int pcount = youngPenguinCount.Execute(unitOfWork);
The unit of work must be associated with the same LightSpeedContext instance as you
used to compile the query. It is highly recommended to use a singleton
LightSpeedContext as this guarantees the preceding requirement. This
LightSpeedContext should be treated as immutable.
Parameterising compiled queries
In practice you probably won’t re-run the exact same query again and again. One or
more of the parameters will vary from run to run. For example, you might be
performing a search by name, with the name coming from user input. To represent a
parameter in a compiled query, using the CompiledQuery.Parameter method:
Mindscape LightSpeed User Guide
221
Adding a parameter
var query = from p in UnitOfWork.Penguins
where p.Name == CompiledQuery.Parameter<string>("name")
select p;
CompiledQuery<IList<Penguin>> namedPenguins = query.Compile();
Now when you call CompiledQuery.Execute, you must supply an
ICompiledParameterValues which provides the values for each parameter. The simplest
way to do this is to use an anonymous type and the CompiledQuery.Parameters
method:
Executing a compiled query, providing a value for the parameter
var results = namedPenguins.Execute(unitOfWork, CompiledQuery.Parameters(new { name =
"Gloria" }));
(Note: anonymous types, though convenient, are relatively slow. Depending on the
performance characteristics of your query, it may be worth using an optimised
implementation of ICompiledParameterValues based on a switch statement or a
dictionary.
Mindscape LightSpeed User Guide
222
Exploring the Query Object
Most advanced query operations are most easily achieved using LINQ. However, it is
sometimes necessary or preferable to use query objects. This section summarises the
features of the Query object.
Basic Querying Operations
The QueryExpression property specifies the criteria for the query – in effect the
“where” clause.
The Order property controls how the results are ordered, and the Page property allows
you to select a sub-range of records by their index in the sort order.
For full information about these properties, see the Basic Operations chapter.
Controlling Entity Load
The AggregateName property specifies a named aggregate to load. An aggregate
specifies a pattern of eager loads, allowing associations or blobs to be optionally loaded
depending on a particular page or screen’s requirements. See the Performance and
Tuning chapter for details.
Projections
You can use the Projection collection to load a specific set of columns (or computed
expressions). When doing this you must call the IUnitOfWork.Project method instead
of IUnitOfWork.Find. The results are not materialised into entities and do not become
part of the unit of work. You can access the results via an ADO.NET IDataReader for
raw access, or have them materialised into data objects using the
IUnitOfWork.Project<T> overload. The latter is similar to LINQ projections using the
Select operator.
When performing a projection, you can also set the Distinct property to deduplicate
the results.
Mindscape LightSpeed User Guide
223
Examples:
If you want to perform a single column projection which returns a primitive type or a
string then you can use the Project<T> overload as shown below:
Projecting a single column and returning a list of Int32s
var query = new Query(typeof(Contribution));
query.Projection.Add("ContributorId");
var results = unitOfWork.Project<int>(query);
To deduplicate the results in the query above, we would use the Distinct property as
shown below:
Projecting a single column and returning a distinct list of Int32s
var query = new Query(typeof(Contribution));
query.Projection.Add("ContributorId");
query.Distinct = true;
var results = unitOfWork.Project<int>(query);
If we want to project more than one column to a known type, that type needs to be
declared with properties that match the name of the projected columns we are going
to be returning. An example with two properties is shown below but the same
approach applies regardless of how many columns are involved in the projection.
Mindscape LightSpeed User Guide
224
Projecting multiple columns and return a list of a specified type
struct MyProjectionResult
{
public int ContributorId { get; set; }
public int ApprovedById { get; set; }
}
var query = new Query(typeof(Contribution));
query.Projection.Add("ContributorId");
query.Projection.Add("ApprovedById");
var results = unitOfWork.Project<MyProjectionResult>(query);
If you require a custom mapping to be applied or don’t require object instances to be
returned to you then you can use the Project() method which will return you a
DataReader instance.
Projecting and returning a DataReader instance
var query = new Query(typeof(Contribution));
query.Projection.Add("ContributorId");
query.Projection.Add("ApprovedById");
using (var reader = unitOfWork.Project(query))
{
while (reader.Read())
{
// do work
}
}
LightSpeed 5 includes improved support for conditionals in projections, such as
HasValue.
Views
Specify the ViewName property to run the query against a view instead of against the
entity’s normal backing table. See Working with Database Views in the chapter
Controlling the Database Mapping for details.
Mindscape LightSpeed User Guide
225
If you are using LINQ you can use the .WithViewName extension method
Controlling Aliasing
Aliasing for a LightSpeed query is specified through the Mappings property. The
Mappings object allows you to specify a mapping between a type or query and a name.
This alias is then either used automatically when LightSpeed builds the query, or you
can use it when building up a query or a join to indicate which alias should be used
when building the query. Managing aliasing manually should only be required for very
precise control over queries as LightSpeed will generate the required aliasing
automatically for all queries.
Manually specifying aliasing for a query
var query = new Query(typeof(Contribution));
query.Mappings.Add<Contribution>("t0");
query.Mappings.Add<Member>("t1");
One word of caution when manually specifying aliasing for a query is that the Mappings
collection is used by LightSpeed to understand all of the types involved in the query, so
if a mapping is added in to the collection but not subsequently used then a CROSS JOIN
will be applied to join that table in to the query to ensure that any arbitrary criteria that
may reference that aliased entry can be resolved.
Joins
Joins allow you to define expressions that will allow multiple entities to participate in a
query. A Join as defined as part of a query is directly translated into a SQL join when
LightSpeed translates a query into the underlying SQL statements required for the
query.
LightSpeed allows you to define three types of joins:

Inner: The intersection between the two entity sets as intersecting on the
supplied keys
Mindscape LightSpeed User Guide
226


Outer: All rows in the left hand entity set paired with corresponding rows in
the right hand entity set or null if no corresponding join can be made. This
conforms to the syntax of a LEFT OUTER JOIN in SQL.
CrossJoin: The Cartesian product of the two entity sets.
To specify a join, you must assign an instance of the Join class to the Join property.
Multiple joins can be specified by use of the .And() method which is available on any
Join instance. LightSpeed offers several static methods to allow for easy instantiation of
Join instances according to the type required and allows for generic specification of the
two entity types involved.
For more advanced scenarios such as joining against a sub-query there are additional
overloads available. If you are interested in such scenarios please review the API
documentation for the static Join instantiation methods named Inner() and Outer().
When performing a join, LightSpeed will automatically opt in all fields from the joined
entity or query unless you have explicitly defined a projection for the query. The
rational for this is that you are surfacing entities rather than individual columns. If your
intention is to only make use of data from specific fields then we would strongly
recommend you declare a set of appropriate projections to match the data you intend
to use. Review the documentation in the Projections section in this document for more
information on how to achieve this.
Examples:
To specify an Inner Join you can use the Inner<TLeft, TRight>() method as shown below:
Specifying an inner join between two entity types
var query = new Query(typeof(Contribution));
query.Join = Join.Inner<Contribution, Member>("ContributorId", "Id");
Mindscape LightSpeed User Guide
227
To specify an Outer Join you can use the Outer<TLeft, TRight>() method as shown
below:
Specifying an outer join between two entity types
var query = new Query(typeof(Contribution));
query.Join = Join.Outer<Contribution, Member>("ApprovedById", "Id");
To specify a Cross Join you can use the CrossJoin<TLeft, TRight>() method as shown
below:
Specifying a cross join between two entity types
var query = new Query(typeof(Contribution));
query.Join = Join.CrossJoin<Contribution, Member>();
If you need to specify multiple joins, you can use the .And() method to chain your join
instances together. An example of this is shown below:
Specifying multiple joins using Join.And()
var query = new Query(typeof(Contribution));
query.Join = Join.CrossJoin<Contribution, Member>().And(
Join.Inner<Contribution, Comment>("Id", "ContributionId")
);
In advanced scenarios you may require LightSpeed to join against a sub-query, for
example to surface specific projected fields from the sub-query while using it to filter or
scope the top level query. The .Inner() and .Outer() methods provide an overload which
allows you to join on another Query instance which will perform a join against that sub
query of the type you have nominated. LightSpeed does not currently offer this with
CrossJoins and these overloads are not available on the generic versions of these
methods.
Mindscape LightSpeed User Guide
228
An example of how this might be used is shown below:
Specifying a join between an entity type and a query
var innerQuery = new Query(typeof(Member), Entity.Attribute("Username") == "jb");
var query = new Query(typeof(Contribution));
query.Join = Join.Outer(typeof(Contribution), innerQuery, "ContributorId", "Id");
Grouping
The Grouping property allows you to declare a grouping which will be applied to the
query which will be ultimately translated into a SQL GROUP BY statement.
A grouping is specified by assigning a Group instance to the Group property on the
query object. The Group class provides a static method for instantiating Group
instances and the Group class provides the .AndBy() method which allows you to
specify multiple grouping columns.
If you are performing a grouping query you will always need to obtain your results
using a call to Project or Project<T> as LightSpeed will by default only select back the
columns which are being grouped on. You can alternatively specify additional columns
to be selected by explicitly declaring a projection set for the query, note that if you do
so you need to ensure you include the columns being grouped on as this will override
the default behaviour. Specifying a Projection set also allows you to add aggregates
into the query.
LINQ translations of grouping statements will typically perform two underlying calls,
one to perform the grouping query and then a second batch to load the entities
involved in the query to assign them into the corresponding resulting grouped
collections allowing further client side projections to take place against those entities.
Mindscape LightSpeed User Guide
229
Examples:
To perform a standard grouping operation where the result will be a single column of
the grouping key you can specify this as shown below:
A basic grouping operation
var query = new Query(typeof(Contribution));
query.Group = Group.By("ContributorId");
var results = unitOfWork.Project<int>(query);
Alternatively you can specify a Projection set and use the Group.BySelection() method
to indicate that LightSpeed should group by every column in the projection set. You
need to ensure that this will generate a valid query for your target database, for
example aggregates will not be supported by most database providers.
Asking LightSpeed to group by the selection defined in your Projection set
struct MyGroupingResult
{
public int ContributorId { get; set; }
public int ApprovedById { get; set; }
}
var query = new Query(typeof(Contribution));
query.Group = Group.BySelection();
query.Projection.Add("ContributorId");
query.Projection.Add("ApprovedById");
var results = unitOfWork.Project<MyGroupingResult>(query);
Mindscape LightSpeed User Guide
230
Alternatively you can use the .AndBy() method to chain your grouping expressions
together, so to rewrite the above query using this approach would look like this:
Using AndBy to chain grouping expressions
struct MyGroupingResult
{
public int ContributorId { get; set; }
public int ApprovedById { get; set; }
}
var query = new Query(typeof(Contribution));
query.Group = Group.By("ContributorId").AndBy("ApprovedById");
var results = unitOfWork.Project<MyGroupingResult>(query);
Finally you can return aggregates as part of the result set by declaring these in your
Projection set while independently specifying the grouping keys using Group instances.
An example of this is shown below:
Returning Aggregates as part of your projected result set
struct MyGroupingResult
{
public int ContributorId { get; set; }
public int Views { get; set; }
}
var query = new Query(typeof(Contribution));
query.Group = Group.By("ContributorId");
query.Projection.Add("ContributorId");
query.Projection.Add(Entity.Attribute("Views").Function("SUM"));
var results = unitOfWork.Project<MyGroupingResult>(query);
Subexpressions
Subexpressions allow you to define common expressions that can be referenced by
name in the query. See Subexpressions below for details.
Mindscape LightSpeed User Guide
231
Unions and Intersections
You can use the ComposedQueries collection and the ComposeMethod property to
compose queries using the SQL UNION, UNION ALL or INTERSECT operator.
Hints
The Hints property allows you to pass index or table hints to databases where this is
supported. See the Performance and Tuning chapter for more information.
Using direct SQL statements
LightSpeed 5 has added the support for specifying a direct SQL statement on the Query
object by setting the RawSql property with the statement text. If this property is set
then all other properties on the query will be ignored and the specified SQL statement
will be directly used. The returning result set is expected to either be a full entity if
used with Find or the required projection if used with Project.
Other Querying Properties
The Identifier property is used when querying for a single entity by Id.
The IdentifiersOnly property specifies that LightSpeed should select only entity Ids, not
full entities.
The IncludeDeleted property specifies that the query should also return soft-deleted
entities. See the Implementing Storage Policies with LightSpeed chapter for details.
The SearchQuery property invokes full text search. See Full Text Search above.
Mindscape LightSpeed User Guide
232
Subexpressions
When you’re writing a query, you may find that an operation over an associated
collection comes up repeatedly. For example, you may want to perform a calculation
over the collection, and both order and filter by the result of this calculation. To avoid
retyping the common expressions each time – and having the database engine
re-evaluate them each time – you can define subexpressions for them, then use these
subexpressions as if they were actual attributes of the entity.
To define a subexpression, add it to the Query.Subexpressions collection. You must
specify the name of the subexpression, the query expression it encapsulates, and the
column in the target table on which to join to the main table.
Defining a subexpression
query.Subexpressions.Add(
"TotalFreight",
Entity.Attribute("Orders.Freight").Function("SUM"),
"CustomerId");
// name
// expression
// join column
Once a subexpression is defined, you can use it by specifying its name. You can use a
subexpression name anywhere you would normally use a field name – typically in a
query expression, sort order or projection.
Mindscape LightSpeed User Guide
233
Using subexpressions in a query
Query query = new Query(typeof(Client));
// Define TotalFreight and AverageValue subexpressions
query.Subexpressions.Add(
"TotalFreight",
Entity.Attribute("Orders.Freight").Function("SUM"),
"CustomerId");
query.Subexpressions.Add(
"AverageValue",
Entity.Attribute("Orders.Value").Function("AVG"),
"CustomerId");
// Order by the aggregates
query.Order = Order.By("TotalFreight").AndBy("AverageValue");
// Filter by the aggregates
query.QueryExpression =
Entity.Attribute("TotalFreight") > 10000
&& Entity.Attribute("AverageValue") < 9000;
// Use the aggregates in a projection
query.Projection.Add("Name");
query.Projection.Add("TotalFreight");
query.Projection.Add("AverageValue");
var clientsPayingHighFreightForLittleValue = _unitOfWork.Project<FreightSummary>(query);
In LINQ, you can create sub expressions using the let keyword; no special APIs are
required.
Mindscape LightSpeed User Guide
234
Working with Metadata
Most of the time when you’re working with LightSpeed, you’re working with specific
entities and using those entity classes. Sometimes, however, a utility method needs to
be able to work with any kind of entity, rather than being specialised to a particular
entity type.
An example might be generic copy or compare methods, which could be useful in
resolving optimistic concurrency conflicts. A generic compare method could compare
the attribute values of one entity to those of another entity of the same type, and a
generic copy method could that copy all or selected values. Such methods need to
know what attributes an entity has, but it would be tedious to re-implement them for
each entity class (and maintain them as the class evolved).
To help you implement such methods, LightSpeed provides a metadata API which you
can use to enumerate, examine and update entity attributes.
Mindscape LightSpeed User Guide
235
The LightSpeed Metamodel
LightSpeed provides three classes that represent entity metadata:




EntityInfo represents an entity class.
FieldInfo represents an attribute of an entity.
AssociationInfo represents an association between entities. It is derived from
FieldInfo because associations are a particular kind of field.
ValidationInfo represents details of a validation which is being applied to an
entity and will be based on a validation rule.
Note that an EntityInfo represents a class, not an individual entity instance. Similarly, a
FieldInfo represents a class member, not an instance of the field on a particular entity
instance.
Referencing the Metadata Assembly
As metadata is only required in occasional cases, the metadata classes are not included
in the main LightSpeed assembly. To use the metadata classes, you must add a
reference to the Mindscape.LightSpeed.MetaData assembly, found in the installation
directory under the Bin folder.
Mindscape LightSpeed User Guide
236
Getting Class Information
If you have an entity instance, you can get the metadata for that entity using the
EntityInfo extension method.
Getting the metadata from an entity instance
using Mindscape.LightSpeed.MetaData;
// bring extension method into scope
var classInfo = order.EntityInfo();
Remember that the Entityinfo represents the metadata for the type of the entity: it is
not specific to the entity instance.
You can also get the metadata for an entity type without having an entity instance by
using the EntityInfo.FromType static method:
Getting the metadata from an entity type
var classInfo = EntityInfo.FromType(typeof(Order));
Getting Class Settings
EntityInfo has a number of properties relating to the entity class, primarily around
storage policies. See the API documentation for information about these properties.
You can also determine which other classes are part of the same single or class
inheritance hierarchy as the entity class, using the InheritanceHierarchy property.
(Concrete inheritance is not considered because concrete inheritance is not
polymorphic and is not used in database mapping.)
Mindscape LightSpeed User Guide
237
Getting Field and Association Information
EntityInfo exposes the fields of the entity in several different ways.
The Fields collection lists all fields defined on the entity class – both ‘value’ fields and
associations. To get just the value fields, use the ValueFields collection; to get just the
associations, use the Associations collection.
Each of these collections has a corresponding collection with the “Flattened” prefix –
FlattenedFields, FlattenedValueFields and FlattenedAssociations. In single or class
table inheritance scenarios, these contain all fields of the relevant type from across the
inheritance hierarchy. (In the absence of discriminated inheritance, they are the same
as the non-flattened equivalents.)
The FieldInfo and AssociationInfo classes have a number of properties relating to the
field or association definition. For example, you can get the data type using the
FieldType property, or the cardinality of association using the AssociationType
property. Again, see the API documentation for information about these properties.
Fields and Properties
The LightSpeed metamodel represents fields, not properties, because fields are what
LightSpeed works with. Normally there is a one-to-one correspondence between fields
and properties, but if you are hand-coding entities, or have overridden the default
designer behaviour using the Generation option, then this may not be the case. In this
case, remember that the field collections represent fields, not properties.
You will also notice that FieldInfo has three name properties: FieldName,
PropertyName and HumanName. FieldName returns the name of the actual backing
field which LightSpeed uses for persistence. PropertyName returns the name of the
CLR property which application code uses to get or set that field. As noted above, in
hand-coded cases there may not be such a property. Finally, HumanName returns the
field name tidied up and split up into words for end user display.
For example, if you create a property named OrderReference in the designer, the
metadata name properties will be as follows:

FieldName: _orderReference
Mindscape LightSpeed User Guide
238


PropertyName: OrderReference
HumanName: Order Reference
Getting Validation Information
ValidationInfo exposes information about a validation that is being applied to a field on
an entity. To get this information you can enumerate the Validations collection on a
FieldInfo.
Each ValidationInfo instance provides a reference to the validation name which you can
use for comparison purposes, the .NET type of the validation class if you want to use
that for comparison or for any further work within your code and then a dictionary of
name/object pairs which contain the actual data associated with the validation
instance.
For example for a length validation you will find a LengthValidationRule entry with
Minimum and Maximum entries in the dictionary which hold integer values as set on
the validation instance.
Mindscape LightSpeed User Guide
239
Getting and Setting Fields through Metadata
Getting Field Values
To obtain the value of a field on a particular entity instance, call the FieldInfo.GetValue
method:
Getting the value of a field on an entity instance
var referenceField = classInfo.Fields.First(f => f.PropertyName == "OrderReference");
object reference1 = referenceField.GetValue(order1);
object reference2 = referenceField.GetValue(order2);
GetValue accepts any entity, but throws an exception if the entity is not of the type on
which the FieldInfo is defined. This can be an issue when inheritance is in play: if you
are processing entities from across the inheritance hierarchy, then you will probably be
using a flattened collection to ensure you process all fields, but a particular entity
instance won’t have the fields declared in sibling or derived classes. To check if the
FieldInfo is defined on the entity, call IsDefined:
Using IsDefined to check that it is safe to get a field value
var classInfo = EntityInfo.FromType(typeof(SalesItem));
// Not defined on all SalesItem subclasses
var publisherField = classInfo.FlattenedFields.First(f => f.PropertyName == "Publisher");
if (publisherField.IsDefined(salesItem))
{
object publisher = publisher.GetField(salesItem);
}
FieldInfo also provides a TryGetValue method which combines the check with the
retrieval, but using IsDefined and GetValue is usually more convenient if you want to
cast the value to a particular type.
Mindscape LightSpeed User Guide
240
From LightSpeed 5 onwards, FieldInfo includes extra information on properties:



Validations (which returns a list of the validations defined on entities of its
type)
IsNullable
IsIdentityField
Getting Association Values
When you use FieldInfo.GetValue to retrieve an association value, this causes
LightSpeed to load the association if it not already loaded. For to-one associations, the
return value is the associated entity, not the EntityHolder which implements the
association.
Setting Field Values
Conversely, you can set the value of a field on an entity instance using SetValue:
Setting the value of a field on an entity instance
var referenceField = classInfo.Fields.First(f => f.PropertyName == "OrderReference");
referenceField.SetValue(order, newReference);
As with GetValue, you can use IsDefined to check that the field exists on the entity
instance at hand, or TrySetValue to combine the check with the set.
When you call FieldInfo.SetValue, you are directly setting the underlying CLR field.
(Remember, the LightSpeed metamodel represents fields, not properties.) The
property setter is not called. If you have custom logic in the property setter, for
example to validate or transform input values, FieldInfo.SetValue bypasses that custom
logic.
Mindscape LightSpeed User Guide
241
Performance and Tuning
LightSpeed makes it easy to work with your persistent data as .NET objects, abstracting
away many details of your application’s use of the database. In practice, however, no
application layer can completely abstract away the database. To get good
performance, it’s important to understand what is happening at the level of the
database connection and the database engine, and what options you have for
improving performance at this level.
Mindscape LightSpeed User Guide
242
Controlling How Entities Load
By default, LightSpeed loads entities on demand. That is, if an entity has an association
to another entity or a collection of other entities, the association is loaded the first
time an application accesses it. This requires another query to the database, to
retrieve the associated entity or the members of the collection. (Once the association
is loaded, LightSpeed remembers that it’s loaded and doesn’t perform another
database query.) For example:
var
var
var
var
var
var
order = unitOfWork.FindById<Order>(1);
reference = order.Reference;
customer = order.Customer;
customerName = order.Customer.Name;
lines = order.Lines;
line0value = order.Lines[0].Value
//
//
//
//
//
//
queries the database for an Order
scalar property, no database query
queries the database for a Customer
customer is loaded, no database query
queries the database for OrderLines
line is loaded, no database query
This is a safe, conservative strategy which avoids superfluous loads. It is referred to as
lazy loading because it lazily doesn’t do any work it doesn’t need to.
However, when a unit of work needs a number of related entities, it can be imperfectly
efficient. In the code fragment above, LightSpeed issued three database queries. If we
knew ahead of time that the application was going to need the Customer entity and the
Lines collection, it would have been more performant to perform all three queries in a
single go.
The problem gets worse when dealing with collections of entities. For example:
var overdues = unitOfWork.Orders.Where(o => o.DueDate < today);
foreach (var o in overdues)
// queries the database for Orders
{
var customer = o.Customer; // queries the database for a Customer
Console.WriteLine("Order {0} for customer {1} is overdue", o.Reference, customer.Name);
}
This code fragment results in one query for the orders, then, for each order, another
query for the customer. If the first query returned 1000 orders, then we would do
Mindscape LightSpeed User Guide
243
1000 queries for customers – a total of 1001 queries. This problem is called the N+1
problem because it means you need N+1 queries to process N objects.
LightSpeed makes it easy to avoid these problems using eager loading.
Eager Loading
Eager loading means loading associated entities before they are needed. In LightSpeed,
eager loading specifically means loading associated entities using the same database
query as the source entity, avoiding an extra round trip. Here is how our N+1 code
fragment works with eager loading.
var overdues = unitOfWork.Orders.Where(o => o.DueDate < today);
foreach (var o in overdues)
// queries the database for Orders *and* Customers
{
var customer = o.Customer; // Customer is already loaded, no database query
Console.WriteLine("Order {0} for customer {1} is overdue", o.Reference, customer.Name);
}
By using eager loading we replace 1001 trips to the database to a single trip! Of course,
that one trip now has to do more work, so you should eager load only when you know
you are going to need the associations.
To implement eager loading, select an association that you want to be eager loaded,
and:


If you want parent entities to eager load their children, set Eager Load
Collection to True.
If you want child entities to eager load their parents, set Eager Load
Backreference to True.
In the example above, because you want the Order entity to eager load its associated
Customer, you would select the Order to Customer association and set Eager Load
Backreference to True. If you also wanted the Customer entity to eager load its
collection of Orders, you would also set Eager Load Collection to True. (An association
can be eager loaded in both directions.)
Mindscape LightSpeed User Guide
244
For hand-coded associations, you implement eager loading by applying the EagerLoad
attribute to the association field:
Implementing eager loading on hand-coded associations
public class Order : Entity<int>
{
[EagerLoad]
private readonly EntityHolder<Customer> _customer = new EntityHolder<Customer>();
[EagerLoad]
private readonly EntityCollection<Line> _lines = new EntityCollection<Line>();
}
Eager loading cascades, allowing you to load a whole object graph in a single database
round-trip. For example, if Customer.Orders is eager loaded, and Order.Lines is eager
loaded, then when you load one or more Customers, LightSpeed will automatically
fetch not only the orders for all those customers but also the lines for all those orders.
Fine Grained Control Using Named Aggregates
It often happens that some parts of an application want to eager load an association,
but other parts do not. For example, a page which displays the details of an order
knows that it will be displaying the order lines, so would want to eager load the Lines
collection. But a page which displays a list of overdue orders doesn’t need to display
the individual order lines, so it doesn’t want to eager load the Lines collection – but it
might want to eager load the customers so that it could show the user who each
overdue order belonged to.
To support conditionally eager loading an association, LightSpeed uses named
aggregates. ‘Aggregate’ is a term from domain-driven design that refers to a bounded
object graph that we need to satisfy a particular application use case. (In the example
above, there were two aggregates. The order details page was interested in an “order
plus lines” aggregate. The overdue orders page was interested in an “order plus
customer” aggregate.) Named aggregates allow you to name particular object graphs,
and tell LightSpeed to eager load different aggregates in different situations.
Mindscape LightSpeed User Guide
245
To specify that an association is part of a named aggregate, select the association
arrow, and enter the aggregate name in the Collection Aggregates or Backreference
Aggregates box (or both). An association can be part of multiple named aggregates –
separate the aggregate names with semicolons.
For hand-coded associations, specify the AggregateName property on the EagerLoad
attribute. You can specify the attribute multiple times.
Specifying that a hand-coded association is part of a named aggregate
public class Order : Entity<int>
{
[EagerLoad(AggregateName = "WithAllDetails")]
private readonly EntityHolder<Customer> _customer = new EntityHolder<Customer>();
[EagerLoad(AggregateName = "WithAllDetails")]
[EagerLoad(AggregateName = "WithLines")]
private readonly EntityCollection<Line> _customer = new EntityCollection<Line>();
}
To eager load a particular aggregate using LINQ, specify it in your query using the
WithAggregate() method:
Eager loading a named aggregate using LINQ
// Loads orders and associated lines, but not associated customers
var orders = unitOfWork.Orders
.Where(o => o.CustomerId == customerId)
.WithAggregate("WithLines")
.ToList();
In LightSpeed 5 a new feature was added where if a model defines named aggregates,
constants based on their names are emitted. This allows you to write code such as
WithAggregate(StoreAggregates.WithProductInfo), so you don’t have to specify the
named aggregate as a string.
Mindscape LightSpeed User Guide
246
To eager load a particular aggregate using query objects, specify it in using the
Query.AggregateName property:
Eager loading a named aggregate using query objects
// Loads orders and associated lines, but not associated customers
Query query = new Query(Entity.Attribute("CustomerId" == customerId))
{
AggregateName = "WithLines"
};
var orders = unitOfWork.Find<Order>(query);
In both of the above samples, changing “WithLines” to “WithAllDetails” would eager
load both the lines and the customers. Omitting the aggregate would mean both the
lines and the customers were lazy loaded.
The aggregate name is an attribute of a query, and affects all associations in the
aggregate, not just the associations on the starting object. For example, if both
Customer.Orders and Order.Lines are marked with the “WithAllDetails” aggregate,
then loading a Customer with the WithAllDetails aggregate will load the customer’s
orders, and all the lines of those orders. This allows you to load deep object graphs in a
single database access if required.
Mindscape LightSpeed User Guide
247
Controlling How Entity Data Loads
By default, when LightSpeed loads an entity, it loads all the fields of that entity. This is
an efficient strategy because most fields are small and the overhead of loading them is
very low compared to the cost of having to go back to the database to load them on
demand.
However, some entities contain bulky data which is rarely used. For example, an
employee record might contain a high-resolution security photograph of the employee.
Most uses of an Employee don’t need the high-resolution photograph, so downloading
it is a waste of time and bandwidth. You therefore typically want to mark the Photo
field as lazy loaded – that is, to be loaded only when accessed, in a similar way to
associations.
Of course, there are probably cases where the Photo field will be required. For
example, if you are working on a screen that prints security passes, you’ll always want
the high-resolution photograph. For situations like these, you want to be able to eager
load the field just like a normal entity field.
As with associations, LightSpeed handles these requirements through named
aggregates.
To mark a field as lazy loaded, select the field and enter one or more aggregate names
in the Aggregates box. (Unlike associations, you can’t specify that a field is always lazy
loaded. If you never plan to eager load a field, just enter an aggregate name that you
never plan to use.)
For hand-coded fields, apply the EagerLoad attribute with the AggregateName property
to the field, and implement the wrapper property using the Get method.
Mindscape LightSpeed User Guide
248
Lazy loading a field
public class Employee : Entity<int>
{
[EagerLoad(AggregateName = "WithSecurityBlobs")]
private byte[] _securityPhoto;
public byte[] SecurityPhoto
{
get { return Get(ref _securityPhoto); }
set { Set(ref _securityPhoto, value); }
}
// MUST use Get method here
}
The marked field will now be loaded only when you access it:
using
{
var
var
var
}
(IUnitOfWork unitOfWork = _context.CreateUnitOfWork())
employee = unitOfWork.FindById<Employee>(1); // does not load security photo
name = employee.Name;
// already loaded, no database query
photo = employee.SecurityPhoto; // queries database to load SecurityPhoto column
However, if you supply one of the aggregates that the field is part of, using the
WithAggregate method or the Query.AggregateName property, then the field will be
eager-loaded as if it were a normal field:
using (StaffUnitOfWork unitOfWork = _context.CreateUnitOfWork())
{
var employee = unitOfWork.Employees.
.WithAggregate("WithSecurityBlobs")
.Single(e => e.Id == 1); // loads security photo
var photo = employee.SecurityPhoto;
// already loaded, no database query
}
The aggregate name is an attribute of a query, and affects all fields in the aggregate,
not just the fields on the starting object. For example, if Site.Employees is eager loaded
or is marked with the “WithSecurityBlobs” aggregate, then loading a Site with the
Mindscape LightSpeed User Guide
249
WithSecurityBlobs attribute will load all the employees on that site, including their
SecurityPhoto fields.
Mindscape LightSpeed User Guide
250
Understanding Named Aggregates
Associations and fields have different default behaviours, and it is possible to get
confused about how aggregates and the WithAggregate operator affect the loading of
fields. This section summarises load behaviour so you can understand exactly what
gets loaded and when under different circumstances.








If you don’t do anything to a field, it will always be loaded when the entity
loads.
If you specify one or more aggregate names on a field, it will be loaded only
when it is accessed, incurring a separate database access, unless you specify
one of those aggregates in your query.
If you don’t do anything to an association, it will be loaded only on demand,
incurring a separate database access.
If you specify eager load on an association, it will always be loaded when the
entity loads, as part of the same database access.
If you specify one or more aggregate names on an association (and don’t
specify eager load), it will be loaded only on demand, unless you specify one of
those aggregates in your query, in which case it will be loaded when the entity
loads, like an eager load association.
WithAggregate doesn’t affect fields or associations that aren’t part of a named
aggregate. They will stay eager or lazy depending on how they were set up.
WithAggregate propagates across associations, so you can use a named
aggregate to load an entire object graph.
You can specify multiple named aggregates on a field or association to control
which subsets of entity data and/or the object graph get loaded in different
situations.
Visualising Aggregates
You can use the designer to help you visualise named aggregates. See Custom Views in
the chapter Working with Models in the Visual Designer.
Mindscape LightSpeed User Guide
251
Bulk Updates and Deletes
The normal cycle for working with an existing database row begins by loading it as an
entity. You then modify or remove the entity, and save the changes to the database.
This results in an UPDATE or DELETE statement for each affected row.
In some bulk operations, the same change is being applied to all affected rows, and no
entity operations are required. In this case, loading each row as an entity, then sending
a statement per row, is not efficient. It would be more efficient to send a single
UPDATE or DELETE with an expression indicating which rows to apply to it.
This can be done using the IUnitOfWork bulk Update and Remove methods.
Bulk Updates
IUnitOfWork.Update takes a query specifying which rows to update, and an object or
dictionary specifying the values to update those rows with. If you pass an object, then
LightSpeed uses the names and values of its properties to compose the update. If you
pass a dictionary, then LightSpeed uses the string keys of the dictionary and their
associated values.
The following example uses an anonymous type to update the IsBadPaymentRisk
column.
Performing a bulk update using a change object
DateTime overdueDate = DateTime.Now.AddMonths(-6);
Query overdueAccounts = new Query(typeof(Customer),
Entity.Attribute("Balance") < 0 && Entity.Attribute("LastPaymentDate") < overdueDate);
unitOfWork.Update(overdueAccounts, new { IsBadPaymentRisk = true });
unitOfWork.SaveChanges();
Mindscape LightSpeed User Guide
252
The following example uses a dictionary to perform the same update.
Performing a bulk update using a change dictionary
DateTime overdueDate = DateTime.Now.AddMonths(-6);
Query overdueAccounts = new Query(typeof(Customer),
Entity.Attribute("Balance") < 0 && Entity.Attribute("LastPaymentDate") < overdueDate);
Dictionary<string, object> changes = new Dictionary<string, object>();
changes.Add("IsBadPaymentRisk", true);
unitOfWork.Update(overdueAccounts, changes);
unitOfWork.SaveChanges();
Note that like entity operations, bulk operations are not applied to the database
immediately. You must still call SaveChanges. This allows bulk updates to be carried
out atomically and to be coordinated with entity operations.
Bulk Deletes
IUnitOfWork.Remove takes a query specifying which rows to delete.
Performing a bulk delete
DateTime expiryDate = DateTime.Now.AddMonths(-6);
Query expiredUsers = new Query(typeof(User),
Entity.Attribute("LastActiveDate") < expiryDate);
unitOfWork.Remove(expiredUsers);
unitOfWork.SaveChanges();
As noted under Bulk Updates, you must remember to call SaveChanges to commit the
bulk delete.
To perform this type of query using LINQ you can use the .Remove() extension method
in the Mindscape.LightSpeed.Linq namespace to indicate that the query should be a
remove operation. As with IUnitOfWork.Remove you will need to call SaveChanges to
commit the bulk delete.
Mindscape LightSpeed User Guide
253
Considerations for Bulk Operations
Bulk operations bypass the identity map. After performing a bulk operation, dispose
the unit of work, or at least reset it (by passing true to SaveChanges).
Bulk operations bypass the cache, and do not update the full text search index. Avoid
using bulk updates if your application uses either of these features.
Mindscape LightSpeed User Guide
254
Batching
When you call IUnitOfWork.SaveChanges, LightSpeed flushes all pending changes –
inserts, updates and deletes – to the database. To reduce the number of round-trips to
the database, LightSpeed collects these pending changes into batches for submission
into the database.
Note that LightSpeed always commits changes under the aegis of a transaction.
Therefore, even if there are more SQL statements than will fit in one batch, you are still
guaranteed that the save will be atomic.
Customising the Batch Size
By default, LightSpeed collects pending changes into batches of 10.
You can override the default batch size by setting LightSpeedContext.UpdateBatchSize
in code, or the updateBatchSize attribute in configuration.
Customising the batch size in configuration
<add name="Test"
updateBatchSize="20" />
Customising the batch size in code
_context.UpdateBatchSize = 20;
Increasing the batch size will reduce the number of database round-trips during a large
save, but results in much longer SQL commands. Increasing batch size too far can
therefore reduce performance instead of improving it. If you make a change, be sure to
measure the impact on a system whose configuration (speed, throughput, latency) is as
similar as possible to your production system. Particularly in low latency environments,
reducing round trips may be a poor trade off.
Mindscape LightSpeed User Guide
255
Identity Columns and Batching
The IdentityColumn identity method defeats batching, because LightSpeed has to query
for the database-allocated Id after each insert. Identity columns should be used only
when required for compatibility with existing databases.
Databases and Batching
Some databases do not support batching. On these databases, each SQL statement is
run as a separate command regardless of batching settings.
Mindscape LightSpeed User Guide
256
Caching
LightSpeed provides two mechanisms for caching of entity instances – a first level and
second level cache. Both caches are designed to help in reducing the number of
database queries that need to be executed. The first level cache, which is also known as
the Identity Map, is always in use, will cover all entities and cannot be disabled; second
level caching, however, must be explicitly enabled and configured for use as it is
disabled by default and only extends to caching entities which you have explicitly opted
in through use of the Cached option.
First level caching is scoped to the current unit of work and therefore any cached
objects will be unavailable as cached objects inside a second unit of work. The second
level cache provide application wide caching – beyond the bounds of a unit of work.
An example of how the first and second level caches will be hit
using (var unitOfWork = context.CreateUnitOfWork())
{
var contrib1 = unitOfWork.FindById<Contribution>(52);
// This request will be returned from the level one cache in memory
var contrib2 = unitOfWork.FindById<Contribution>(52);
}
using (var unitOfWork = context.CreateUnitOfWork())
{
// If level two cache is not enabled, this would generate
// a database call. With the level two cache enabled it would
// be fetched from the cache as we fetched it in the previous
// unit of work
var contrib1 = unitOfWork.FindById<Contribution>(52);
}
Enabling Second Level Caching
You do not need to use second level caching at all, but you may wish to enable it to
improve performance of your application. If you’re noticing that your application is
querying the database frequently for the same information (for example, reference
data) then adding caching will provide an improvement in performance immediately.
Mindscape LightSpeed User Guide
257
Web applications also benefit well from the second level cache as generally pages are
constructed relatively quickly within a tightly defined unit of work and therefore the
first level cache is not going to be of use across multiple requests.
LightSpeed supports a pluggable cache provider model and provides implementations
for two cache providers out of the box:


DefaultCache – Uses the System.Web.Caching.Cache provider (which can be
used in non-web apps too). This cache is a simple in-process cache.
MemcachedCache – Uses the popular memcached distributed cache.
Memcached can be distributed over an arbitrary number of servers and is
suitable for high-load scenarios.
Additionally you can implement your own cache provider by implementing the ICache
interface.
To enable second level caching, you can either do this programmatically or through
configuration as shown below:
Enabling second level caching programmatically
context.Cache = new CacheBroker(new DefaultCache());
Enabling Second Level Caching using configuration
<lightSpeedContexts>
<add name="default"
connectionStringName="Dev"
cacheClass="Mindscape.LightSpeed.Caching.DefaultCache, Mindscape.LightSpeed" />
</lightSpeedContexts>
Configuring the Second Level Cache
Caching may be applied declaratively at the model level by applying the
CachedAttribute.
Mindscape LightSpeed User Guide
258
[Cached(ExpiryMinutes = 15)]
public class Instance : Entity<long>
{
}
Alternatively it can also be applied using the LightSpeed designer by selecting an entity
and setting the cache to be enabled. Cache expiry times can also be configured with the
designer if desired.
When LightSpeed loads an entity decorated with the CachedAttribute it will
immediately add it to the second-level cache. Any subsequent request for that entity
by its id will cause a cache hit and the database load will be avoided.
Understanding the Second Level Cache
For the first level cache, LightSpeed will check if it already has an entity loaded for the
given Id when you perform any type of fetch where you specify an ID. For example, if
you use the FindById() method, LightSpeed will check the internal first level cache first
and, if found, return that and save a database call. If an explicit ID is not provided (for
example, a query that would return all entities from a table) then as LightSpeed
receives the result and looks to hydrate an entity it will first check if it already has an
entity in the first level cache and, if found, return that entity. This type of logic applies
when fetching explicitly as well as when lazy loading occurs.
Mindscape LightSpeed User Guide
259
The second level cache, if enabled, will also be checked in all of the same situations
however it is more likely to contain objects as they will live across the unit of work
boundaries.
To summarise when a cache hit would occur:



You fetch an entity by Id
You fetch a collection of entities
You lazy load a collection or entity
Note: If you are dealing with projections then no cache hits will be possible, even if the
projection contains the entity identifier.
Collection Caching
If you wish to cache an entire entity set in memory (e.g. for reference data) you can use
FindAll caching. To apply this to an entity open the LightSpeed Model Explorer (View >
Other Windows > LightSpeed Model) and in the tree view locate the entity that you
want to cache. Right-click it and choose Add New Caching Options. Then expand the
entity, select the Caching Options node, and you will see the option for Cache Find All
Result in the Properties grid.
Caching Considerations
Caching can sound like a wonderful way to easily improve performance of your
application however it is not a silver bullet. Primary concerns to consider is cache item
expiry and if stale data could potentially impact your system. Cache expiry is a complex
topic and worth reading more about if you are seriously considering second level
caching options for a production application.
Consider why you are trying to cache data more aggressively and if LightSpeed caching
is the best solution. For example, with web applications it may be far more efficient to
enable output caching on the web pages. The initial load of a page may cause several
database hits however subsequent requests will not cause any queries to fire and will
not invoke the page creation process with .NET.
Mindscape LightSpeed User Guide
260
Working with the Cache Manually
Second level cache items can be interacted with directly by accessing the
UnitOfWork.Context.Cache object. Through this object cache items can be added,
updated and removed.
Mindscape LightSpeed User Guide
261
Database Hints
When you send a query to a database, the database engine works out how to execute
that query using various heuristics. This usually results in an extremely efficient plan.
However, sometimes you can work out a better plan based on your knowledge of the
database or of the broader application context. For example, you might have found
through testing that the database engine is not using an index which could improve
performance. Or you might know that it doesn’t matter if a particular query reads rows
that are in the process of being written, meaning the database engine can skip the
safety overhead of a lock.
In such situations, some databases allow you to pass hints to the database engine on
how to execute the query. This section shows how to specify these hints on LightSpeed
queries.
In all cases, it’s important to remember that hints are exactly that – hints. The query
planner doesn’t have to obey them. You are providing advice on how to execute the
query: the database engine is free to overrule that advice.
Index Hints
An index hint advises the database to use a particular index. You can pass an index hint
using the WithIndexHint operator, providing the name of the index you want to use.
Providing an index hint
var orders = unitOfWork.Orders
.WithIndexHint("IX_OrderDueDate")
.Where(o => o.DueDate < today)
.ToList();
You can pass multiple index names to WithIndexHint if required.
At the time of writing, index hints are supported on Oracle and SQL Server. Using an
index hint on another database does not cause an error – since hints are advisory
anyway – but is ignored.
Mindscape LightSpeed User Guide
262
Table Hints
A table hint advises the database about the way the query uses the table being
queried. Table hints can be used for a variety of tasks. For example, the SQL Server
NOLOCK table hint indicates that the table need not be locked during the query, trading
improved performance for the risk of dirty reads. You can pass a table hint using the
WithTableHint operator.
Providing a table hint
var orders = unitOfWork.Orders
.WithTableHint("NOLOCK")
.Where(o => o.DueDate < today)
.ToList();
Table hints are database-specific – LightSpeed simply passes the raw hint text to the
database. At the time of writing, table hints are supported only on SQL Server. Using a
table hint on another database does not cause an error – since hints are advisory
anyway – but is ignored.
Database Hints Using Query Objects
If you are using query objects instead of LINQ, you can provide hints through the
Query.Hints object.
Providing hints using query objects
Query query = new Query(Entity.Attribute("DueDate") < today);
query.Hints.Indexes.Add("IX_OrderDueDate");
query.Hints.TableHints.Add("NOLOCK");
var orders = unitOfWork.Find<Order>(query);
Mindscape LightSpeed User Guide
263
Measuring Performance
Although you can use standard .NET tools such as profilers to measure performance in
LightSpeed applications, the LightSpeed logging interface allows you to carry out simple
measurements of query performance. For each database access, you can have
LightSpeed log the SQL sent to the database, and how long it took for the database to
respond. You can view this information through the console or Visual Studio Output
window, or capture or analyse it by processing CommandLog records in a custom
logger. See Logging in the Testing and Debugging chapter for more information.
Mindscape LightSpeed User Guide
264
Intercepting Queries
LightSpeed provides a facility to intercept interactions with the ADO.NET provider and
hooks within the querying pipeline to allow you to either inspect the query and
ADO.NET objects for logging or profiling purposes or to manipulate them to apply
security or filtering concerns.
Intercepting ADO.NET interactions
If you require an awareness of the interactions that LightSpeed takes with ADO.NET
Firstly a generic interceptor class which if set will give you access to the ADO.NET
interactions. To create an interceptor you must derive from the Interceptor base class
in the Mindscape.LightSpeed.Profiling namespace and then implement override
methods as required.
Here is an example of using this approach to couple with the ASP.NET Mini Profiler:
Implementing a custom Interceptor class
public class MiniProfilerInterceptor : Interceptor
{
public override IDbConnection CreateConnection(Func<IDbConnection> baseCreator)
{
var realConnection = baseCreator();
return ProfiledDbConnection.Get((DbConnection)realConnection, MiniProfiler.Current);
// or return new ProfiledDbConnection(...) depending on which version you are using
}
public override IDbCommand CreateCommand(Func<IDbCommand> baseCreator)
{
var realCommand = baseCreator();
return new ProfiledDbCommand((DbCommand)realCommand, null, MiniProfiler.Current);
}
}
To attach an interceptor either assign a new instance to the Interceptor property on
your LightSpeedContext instance, or set it via the context configuration.
Mindscape LightSpeed User Guide
265
Assigning an interceptor by configuration
<lightSpeedContexts>
<add name="Development"
interceptorClass="MyAssembly.MyInterceptor,MyAssembly"
/>
</lightSpeedContexts>
Intercepting LightSpeed queries
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 built 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).
To solve these requirements you can implement query based interception 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, apply the
QueryFilterAttribute. QueryFilterAttribute takes a type, which must implement the
IQueryFilter interface.
Mindscape LightSpeed User Guide
266
[QueryFilter(typeof(IsActiveFilter))]
partial class Member { ... }
public class IsActiveFilter : IQueryFilter { ... }
Filtering by interface
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? A filter must
implement the IQueryFilter interface which is described as:
Mindscape LightSpeed User Guide
267
IQueryFilter interface definition
public interface IQueryFilter
{
QueryExpression Filter { get; }
}
To implement this you must return a LightSpeed QueryExpression object that consults
the appropriate entity property. For example if the database table contains an IsActive
flag which we want to filter on then you might write a query expression such as the
following:
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;
}
}
}
Mindscape LightSpeed User Guide
268
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
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 via an ADO.NET based
interceptor.
Mindscape LightSpeed User Guide
269
Implementing Storage Policies with
LightSpeed
A storage policy refers to a decision about how entities should be stored. LightSpeed
supports a number of storage policies through convention-based mappings.
Mindscape LightSpeed User Guide
270
Entity Tracking
Storing Creation and Update Times
To have LightSpeed automatically store the time when entities of a particular type are
created and updated, select the entity and set Track Create Time and/or Track Update
Time to true.
The Track Create Time option creates a field named CreatedOn. LightSpeed
automatically populates this field when the entity is first saved. You can access this
field normally and use it in queries.
The Track Update Time option creates a field named UpdatedOn. LightSpeed
automatically populates this field whenever the entity is saved. As with CreatedOn,
you can access this field normally and use it in queries.
The database must contain the corresponding backing columns for whichever options
are selected. If you use Update Database or Create Migration in the designer, it will
create these columns for you. If you create these columns manually, they must be nonnullable columns of date-time type.
Storing Creating and Updating Users
To have LightSpeed automatically store the user who created or last updated entities of
a particular type, add CreatedBy and/or UpdatedBy string fields to the entity, and set
the LightSpeedContext.AuditInfoMode.
If AuditInfoMode is set, LightSpeed automatically populates the CreatedBy field when
the entity is first saved, and automatically updates the UpdatedBy field each time the
entity is saved. Like the timestamp fields, you can access these fields normally and use
them in queries. You should normally mark the fields as Load Only so that application
code does not try to modify them.
For compatibility reasons, the CreatedBy and UpdatedBy fields are not used for
automatic storage by default: you must enable the policy through the
AuditInfoMode.
Mindscape LightSpeed User Guide
271
Because different applications identify users in different ways, the user id written into
the CreatedBy and UpdatedBy fields depends on the AuditInfoMode. See below for
further information.
Mindscape LightSpeed User Guide
272
Soft Deletion
By default, when you delete an entity through LightSpeed, it is deleted from the
database – that is, LightSpeed sends a SQL DELETE statement. However, you can
specify that entities of a particular type should be soft deleted – that is, instead of being
deleted, they should be left in the database but marked with a ‘deleted’ flag.
To have LightSpeed use soft deletion for an entity type, select the entity and set its Soft
Delete option to true.
The Soft Delete option creates a field named DeletedOn. The DeletedOn field is a
nullable DateTime, which is null by default, but which LightSpeed automatically
populates when the entity is deleted.
The database must contain a corresponding DeletedOn column. If you use Update
Database or Create Migration in the designer, it will create this column for you. If you
create the column manually, it must be a nullable column of date-time type.
Storing Which User Deleted an Entity
To have LightSpeed automatically store the user who deleted a soft-deleted entity, add
a DeletedBy string fields to the entity, and set the LightSpeedContext.AuditInfoMode.
If AuditInfoMode is set, LightSpeed automatically populates the DeletedBy field when
the entity is deleted. You should normally mark the field as Load Only so that
application code does not try to modify it.
For compatibility reasons, the DeletedBy field is not used for automatic storage by
default: you must enable the policy through the AuditInfoMode.
Because different applications identify users in different ways, the user id written into
the DeletedBy field depends on the AuditInfoMode. See below for further information.
Obviously, DeletedBy is not meaningful for hard-deleted entities.
Mindscape LightSpeed User Guide
273
Loading Soft Deleted Entities
Soft deleted entities remain in the database after deletion, and can be retrieved using
LightSpeed. To include deleted entities in a query, apply the IncludeDeleted operator
to the query:
Loading soft deleted entities
var allOrders = unitOfWork.Orders.IncludeDeleted();
(If using query objects, set Query.IncludeDeleted to true.)
IncludeDeleted does not limit the query to deleted entities – it will return both deleted
and non-deleted entities. You can use the DeletedOn field to distinguish deleted
entities.
LightSpeed does not provide a way to purge or restore soft deleted entities. These are
database administration tasks and should be handled as such.
Mindscape LightSpeed User Guide
274
Timestamps for Entity Tracking and Soft Deletion
By default, the times stored by LightSpeed for the CreatedOn, UpdatedOn and
DeletedOn fields are the current local time of the computer where LightSpeed is
running. You may want to override this. For example, if a database is shared across
multiple time zones, you may want to use UTC or some other standard time zone so
that the sort order is consistent regardless of which site created or updated a record.
Or you may want to force a ‘fake’ time for unit testing purposes.
To do this, set LightSpeedContext.AutoTimestampMode in code, or the
autoTimestampMode attribute in configuration. The available values are defined by
the AutoTimestampMode enumeration.
Setting the timestamp strategy in configuration
<add name="Test"
autoTimestamps="Utc" />
Built-In Timestamp Strategies
There are two built-in timestamp strategies, Local and Utc.


Local: The timestamp is the local time of the computer where LightSpeed is
running.
Utc: The timestamp is UTC/GMT.
In both cases the time comes from the clock of the machine where LightSpeed is
running. If clocks are not well synchronised then the errors will be reflected in the
stored timestamps.
Using a Custom Timestamp Strategy
If you use the Custom mode, you must also implement IAutoTimestampStrategy, and
set LightSpeedContext.CustomAutoTimestampStrategy to an instance of that
implementation.
Mindscape LightSpeed User Guide
275
Implementing IAutoTimestampStrategy for unit testing
public class FixedAutoTimestampStrategy : IAutoTimestampStrategy
{
private DateTime _timestamp;
public FixedAutoTimestampStrategy(DateTime timestamp)
{
_timestamp = timestamp;
}
public AutoTimestampMode Mode
{
// Custom strategies must always return AutoTimestampMode.Custom
get { return AutoTimestampMode.Custom; }
}
public DateTime GetTime()
{
return _timestamp;
}
}
Using the custom strategy
_context.AutoTimestampMode = AutoTimestampMode.Custom;
_context.CustomAutoTimestampStrategy = new FixedAutoTimestampStrategy(Tests.NominalTime);
You can also specify a custom timestamp strategy in configuration using its
assembly-qualified type name. In this case the strategy class cannot take constructor
parameters.
Setting a custom timestamp strategy in configuration
<add name="Test"
autoTimestamps="MyApp.MyTimestampStrategy, MyApp" />
Mindscape LightSpeed User Guide
276
Database-side Timestamping
LightSpeed 5 also includes support for database-side timestamps. With your custom
timestamp strategy implementation, have it also implement the
IAutoTimestampExpressionStrategy interface. Your GetTimeExpression() method
should return the desired SQL expression for the column, or null to use the client-side
timestamp returned from IAutoTimestampStrategy.GetTime(). Here’s one
implementation:
Setting a custom timestamp strategy in configuration
public object GetTimeExpression(Type type, TimestampUsage usage, LightSpeedContext
context) {
if (usage == TimestampUsage.DeletedOn)
return null;
return "DEFAULT"; // or "GETDATE()" or "GETUTCDATE()" etc.
}
LightSpeed will not read back the timestamp generated by the database. After saving
changes, therefore, the entity will contain the timestamp returned from
IAutoTimestampStrategy.GetTime(). If your application needs to know the timestamp
generated by the database, you will need to reload the entity.
If your application needs to be portable across different database engines, you can use
the LightSpeedContext.DataProvider value to determine which database is being
targeted.
Mindscape LightSpeed User Guide
277
User Ids for Entity Tracking and Soft Deletion
Different applications identify users in different ways. There is no default way of
identifying a user. So in order to store user names in the CreatedBy, UpdatedBy and
DeletedBy fields, you must specify to LightSpeed how it should identify users.
To do this, set LightSpeedContext.AuditInfoMode in code, or set the auditInfo attribute
in configuration. The available values are defined by the AuditInfoMode enumeration.
Setting how users are identified in configuration
<add name="Test"
auditInfo="HttpContext" />
Built-In User Identification Strategies
There are two built-in user identification strategies, HttpContext and WindowsIdentity.


HttpContext: The user id is taken from the HttpContext.Current.User.Identity.
The application should ensure that users are authenticated.
WindowsIdentity: The user id is the Windows login of the active user.
Using a Custom Identification Strategy
Some applications have their own means of identifying users, such as a custom user
database or directory. To make LightSpeed use such a custom identification method,
specify the Custom AuditInfoMode, implement IAuditInfoStrategy, and set
LightSpeedContext.CustomAuditInfoStrategy to an instance of that implementation.
Mindscape LightSpeed User Guide
278
Implementing IAuditInfoStrategy for unit testing
public class FixedAuditInfoStrategy : IAuditInfoStrategy
{
private string _user;
public FixedAuditInfoStrategy(string user)
{
_user = user;
}
public AuditInfoMode Mode
{
// Custom strategies must always return AuditInfoMode.Custom
get { return AuditInfoMode.Custom; }
}
public string GetCurrentUser()
{
return _user;
}
}
Using the custom strategy
_context.AuditInfoMode = AuditInfoMode.Custom;
_context.CustomAuditInfoStrategy = new FixedAuditInfoStrategy(Tests.NominalUser);
You can also specify a custom audit information strategy in configuration using its
assembly-qualified type name. In this case the strategy class cannot take constructor
parameters.
Setting a custom audit info strategy in configuration
<add name="Test"
auditInfo="MyApp.MyAuditInfoStrategy, MyApp" />
Mindscape LightSpeed User Guide
279
Concurrent Editing
LightSpeed uses optimistic concurrency: that is, it allows users to edit an entity without
taking a lock, optimistically assuming that it is okay for users to edit the same entity at
the same time. When each user commits their changes, their version of the entity is
persisted. LightSpeed doesn’t check whether there has been a concurrent edit. This is
effectively a ‘last one wins’ policy.
LightSpeed also supports concurrency checking, which results in an exception being
thrown if a user tries to save an entity when that entity has been modified since the
user first loaded it. This is effectively a ‘first one wins’ policy.
To enable concurrency checking for entities of a particular type, select the entity and
set Optimistic Concurrency Checking to true.
The Optimistic Concurrency Checking option creates a field named LockVersion.
LightSpeed automatically increments this field each time the entity is saved, and
verifies that the version of the entity in the database is still the version that was
originally loaded. If not, it means that somebody else has changed the entity in the
meantime, and LightSpeed raises an OptimisticConcurrencyException.
The database must contain the corresponding backing column for the LockVersion
field. If you use Update Database or Create Migration in the designer, it will create this
column for you. If you create the column manually, it must be a non-nullable column
of integer type.
Guidance for Optimistic Concurrency Checking
If a concurrency check fails, LightSpeed raises an OptimisticConcurrencyException.
However, the exception does not indicate which entity the error relates to. You should
therefore normally use optimistic concurrency checking only in scenarios where users
will be editing one entity at a time. If you must allow batch editing, then if an
exception occurs you will need to query the database to figure out which entities have
changed and are causing the error.
LightSpeed does not provide a way to revert entities or merge changes. To resolve an
OptimisticConcurrencyException, you will typically need to load the latest versions of
Mindscape LightSpeed User Guide
280
the entities you want to save into a fresh unit of work, and merge the changes from the
stale unit of work (where the original edits took place) into the fresh unit of work. You
can use entity property change tracking and the metadata API to help with this.
Mindscape LightSpeed User Guide
281
Implementing Policies in Hand-Coded Entities
As indicated above, LightSpeed’s storage policies are implemented using specially
named fields. In fact, storage policies are determined by the presence of these
specially named fields. In hand-coded entities, therefore, you can implement a storage
policy just by declaring a field with the appropriate name.
Implementing time tracking in a hand-coded entity
public class Document : Entity<int>
{
// "Field is never assigned to" - LightSpeed assigns these fields internally
#pragma warning disable 649
private readonly DateTime _createdOn;
private readonly DateTime _updatedOn;
#pragma warning restore 649
public DateTime CreatedOn { get { return _createdOn; } }
public DateTime UpdatedOn { get { return _updatedOn; } }
}
Special fields should normally be marked readonly as they should be modified only by
LightSpeed, not by application code. This will cause a compiler warning because there
is no way to set the fields: you should disable this warning as shown above. ASP.NET
medium trust environments do not allow LightSpeed to modify readonly fields: if you
plan to use your model in such an environment, implement the special fields as
read-write.
Column Names for Policy Fields
The LightSpeed fields for storage policies must have the names listed above. If you
need to use different column names in the database, you can map them using
ColumnAttribute or LightSpeedContext.NamingStrategy. This option is only available
when hand-coding entities.
Mindscape LightSpeed User Guide
282
Change Tracking
It is sometimes useful to be able to get a list of changes between the originally loaded
version of an entity and the in-memory version. For example, this could be used for
confirmation or auditing purposes. LightSpeed supports this through the
Entity.ChangeTracker property.
Change tracking is not enabled by default because it can add a significant overhead to
the size of each entity. To enable change tracking, set ChangeTracker.TrackingMode to
ChangeTrackingMode.ChangesOnly. (Only changes made after you have enabled
change tracking are recorded.)
To get the list of changes, use the ChangeTracker.Changes property.
Saving Only Changed Fields
Enabling change tracking does not change LightSpeed’s default behaviour of saving all
fields. This is because change tracking does not in itself support optimistic concurrency
checking.
To save only changed fields you have two options available. Either set Optimistic
Concurrency Checking to true in the designer (or create a LockVersion field in your
entity), as described under Concurrent Editing above or you can apply the
[UnversionedPartialUpdate] attribute to your entity to indicate that only changed fields
should be persisted if the values in the database match the original values when the
entity was loaded.
Mindscape LightSpeed User Guide
283
Working with Legacy Databases
LightSpeed is primarily designed around a ‘convention over configuration’ approach, in
which your code maps naturally to your database schema. In a greenfield application,
this produces simple, well normalised database schemas that are well aligned with the
domain code, in accordance with the principle of a ‘ubiquitous language’ used across
the system. When you have an existing database, though, you may not be able or
willing to modify the database to follow LightSpeed’s conventions, because of technical
risk, compatibility constraints with other applications or simple lack of time. This
chapter describes some techniques for working with such databases using LightSpeed.
Mindscape LightSpeed User Guide
284
Invoking Stored Procedures
Legacy databases often make extensive use of stored procedures to adapt a complex
table structure or to wrap business logic up in the database. For information about
invoking stored procedures, see the chapter Controlling the Database Mapping.
Mindscape LightSpeed User Guide
285
CRUD Stored Procedures
Some environments mandate that all database access be through stored procedures.
The reasons vary, but usually centre around either performance – the architects believe
that stored procedures are faster than table accesses – or security – denying
application code access to tables prevents SQL injection. The result of such policies is
that every table comes with a set of stored procedures which do nothing more than
perform CRUD actions.
The best way to handle such an environment is to overthrow the ‘stored procedures
only’ policy. In most modern large-scale databases, the query planner is so efficient
that the performance argument no longer holds water. And LightSpeed always uses
parameters to pass values in SQL statements, preventing SQL injection through
unsanitised data. The inconvenience, limitations and inefficiency of CRUD procedures
dwarf whatever marginal benefits they may have.
If it is not possible to get direct table access, you can force LightSpeed to use stored
procedures for its CRUD operations. To do this, select each entity and change its
Access Method to StoredProcedures. The designer displays a new section in the
property grid named Access Procedures: fill this section out with the names of the
CRUD procedures.
Mindscape LightSpeed User Guide
286
In addition, you will need to tell LightSpeed to use stored procedures to load child
collections in associations. To do this, select the association arrow and fill in the Select
Procedure option.
CRUD Procedure Conventions
You can define five access procedures for entity CRUD operations.





Select Procedure: Selects all entities of this type.
Select By Id Procedure: Selects a single entity by Id. The procedure should take
a single parameter, whose name is the Identity Column Name for the entity,
and whose value is the entity Id to select.
Insert Procedure: Inserts a single entity. The procedure should take a
parameter for each column, with the same name as that column.
Update Procedure: Updates a single entity. The procedure should take a
parameter for each column, with the same name as that column.
Delete Procedure: Deletes a single entity. The procedure should take a single
parameter, whose name is the Identity Column Name for the entity, and whose
value is the entity Id to delete.
You can also define an access procedure on an association, for looking up child
collections:

Select Procedure: Selects all entities of the child type whose foreign key is
equal to the parameter value. The procedure should take a single parameter,
whose name is the column name of the foreign key of the association, and
whose value is the Id of the parent.
CRUD procedures must also adhere to any database-specific conventions for
LightSpeed stored procedures. This specifically affects Oracle databases. See Working
with Database Providers for details.
CRUD Procedure Limitations
CRUD procedures prevent you from using many aspects of LightSpeed. For example,
you cannot use ad hoc queries or eager loading with entities that are loaded through
Mindscape LightSpeed User Guide
287
CRUD procedures. You will need to create and invoke specific stored procedures for
any queries you want to use.
Mindscape LightSpeed User Guide
288
Using Natural Keys
LightSpeed adopts the convention that entities are identified by surrogate keys; that is,
opaque identifiers with no business meaning, whose sole purpose is to be unique
within a table. Because surrogate keys aren’t derived from application data, their
actual values are not important, so LightSpeed can assign values using one of the
identity methods discussed under Identity Generation in the chapter Controlling the
Database Mapping.
Many existing databases don’t use surrogate keys. Instead, they identify entities using
a piece of application data which is presumed to be unique, but has business meaning.
Such pieces of application data are called natural keys.
Natural keys can be problematic, because as applications and business environments
change, application data can turn out to be not as unique, or not as immutable, as
originally assumed. So if you have a database which uses natural keys, it’s worth
evaluating whether you could change it over to use surrogate keys. Unfortunately, this
often isn’t possible for reasons of application compatibility or technical risk.
Assuming you have to use natural keys, this means LightSpeed can no longer assign its
own Ids to entities, because the Id has to be derived from the business data in some
entity-specific way. Instead, you must implement your own Id generation logic.
Implementing the GeneratedId Method
When LightSpeed needs to assign an Id to an entity, it calls the entity’s GeneratedId
method. By default, this hands off to the identity method specified in the
LightSpeedContext or on the entity.
When you need to use a natural key, you can override GeneratedId to return an item of
business data.
Mindscape LightSpeed User Guide
289
Implementing GeneratedId
protected override object GeneratedId()
{
return _productCode; // See remarks below
}
Natural Keys and Column Mappings
LightSpeed maps databases columns to fields. Each column is mapped at most once.
Consequently, a natural key column, which is mapped to the implicit Id field, cannot
also be mapped to another persistent field. Therefore, assuming you need to store the
natural key in the entity while waiting for LightSpeed to call GeneratedId, the
temporary field you store it in must be transient.
Implementing GeneratedId
public partial class Product : Entity<string>
{
[Transient]
private string _productCode; // Must be transient
protected override object GeneratedId()
{
return _productCode;
}
}
If the _productCode field were not transient, LightSpeed would try to map it to a
ProductCode column in the database. If there was no ProductCode column, this would
cause an error. If ProductCode was the natural key (Id) column, then that column
would now be mapped twice, once to Id and once to _productCode – also an error. (If
there was a ProductCode column, independent of the natural key, then there would be
no error, but this is unlikely, as it would mean the product key was stored in two
places. However, something like this could happen if the natural key were derived
from other columns, but not identical to those columns.)
Mindscape LightSpeed User Guide
290
When the Natural Key is Assigned
LightSpeed assigns an Id to an entity when that entity first joins a unit of work. Your
GeneratedId implementation will be called at this point. Therefore, you must make
sure that any business data you need for the natural key is ready before the entity joins
a unit of work. Ids are immutable, so you do not get a second chance!
An entity joins a unit of work when you call IUnitOfWork.Add for that entity or any
associated entity, or when you associate it with another entity that is already part of a
unit of work (by using an association setter or by calling EntityCollection<T>.Add). It is
important to remember that entities can be implicitly added to a unit of work through
an association.
It is strongly recommended that you initialise the natural key data immediately after
creating a new entity. This minimises the chance that you might accidentally add the
entity to a unit of work – and thereby trigger a call to GeneratedId – before the natural
key data is available. It is also a good idea to put a check, such as a Debug.Assert, in
GeneratedId to verify that it is not called before the data is available.
Natural Keys and the IdentityColumn Identity Method
A natural key is never a database autoincrement/identity column. You must therefore
never specify the IdentityColumn identity method when using natural keys. Any other
identity method will be ignored, because your GeneratedId override takes precedence,
but using IdentityColumn will result in an error because LightSpeed performs special
handling for it during an INSERT. (Specifically, it prevents LightSpeed sending the Id to
the database in the INSERT.)
Mindscape LightSpeed User Guide
291
Using Composite Keys
Composite keys are similar to natural keys in many ways, except that a composite key
comprises multiple database columns, and must therefore be mapped as a value
object.
Composite Key Types
Every entity in LightSpeed has an Id property. In the case of a composite key, the Id
property has to contain multiple values. This happens by making the identity type a
composite type – a struct (.NET value type) with a field for each column of the
composite key.
The columns of the composite key will be mapped to the fields of this identity type. To
refer to composite key columns, therefore, you must refer to entity.Id.field_name; the
columns are members of the composite Id, not distinct properties on the entity.
Composite Keys in the Designer
When you drag a table with a composite key onto the designer, LightSpeed infers the
key columns from the database primary key, and displays identity fields for the key
columns.
The identity type is automatically generated. Its name is the entity name followed by
Id. For example, an entity named InternationalProduct will have an identity type
named InternationalProductId.
Mindscape LightSpeed User Guide
292
You can create composite keys in a model-first approach, by using the LightSpeed
Model Explorer to add identity properties to an entity. You should not normally do
this: if you are defining new entities rather than mapping existing tables, you should
use a surrogate key instead.
Composite Keys in Hand-Coded Entities
If you are hand-coding an entity with a composite key, you must provide an identity
type, either by locating an existing type that fits the composite key columns or (more
usually) by coding a specific identity type. The identity type must be a struct (value
type) and should be immutable (all fields marked readonly).
A hand-coded composite key type
public struct InternationalProductId
{
private readonly int _productId;
private readonly string _languageId;
public InternationalProductId(int productId, string languageId)
{
_productId = productId;
_languageId = languageId;
}
public int ProductId
{
get { return _productId; }
}
public string LanguageId
{
get { return _languageId; }
}
// Overrides of Equals and GetHashCode omitted for clarity.
// You should always provide these methods as they can markedly
// affect efficiency.
}
Mindscape LightSpeed User Guide
293
You can then specify this type as the TId type parameter when deriving from
Entity<TId>:
Declaring a composite keyed entity
public class InternationalProduct : Entity<InternationalProductId>
Assigning Composite Keys
Composite keys are assigned in the same way as other natural keys: by overriding the
GeneratedId method. Similar considerations apply in terms of storing Id generation
data in transient fields and ensuring the Id generation data is available before the
entity joins a unit of work. The GeneratedId override must return an instance of the
composite key type.
Generating the value for a composite key
[Transient]
private InternationalProductId _pendingId;
public InternationalProduct(int productId, string languageId)
{
_pendingId = new InternationalProductId(productId, languageId);
}
protected override object GeneratedId()
{
return _pendingId;
}
Composite Foreign Keys
When another table has a foreign key to a composite-keyed table, the foreign key field
must be of the composite key type, the foreign key columns must be mapped to the
fields of the composite key type, and the foreign key field must be marked as a value
object.
Mindscape LightSpeed User Guide
294
An association with a composite foreign key
public class Advert : Entity<int>
{
[ValueObject]
[ValueObjectColumn("ProductId", "PRD_ID")]
[ValueObjectColumn("LanguageId", "LANG_ID")]
private InternationalProductId _productId;
public InternationalProductId ProductId
{
get { return _productId; }
set { Set(ref _productId, value, "ProductId"); }
}
private readonly EntityHolder<InternationalProduct> _product =
new EntityHolder<InternationalProduct>();
public InternationalProduct Product
{
get { return Get(_product); }
set { Set(_product, value); }
}
}
If you are using the designer, it can generate this code for you, but it may not be able to
infer the column mappings from the database. You may therefore need to manually
set up the association and map columns.
Mindscape LightSpeed User Guide
295
Foreign Keys That Are Part of a Composite Key
In some cases, one of the columns of a composite key may also be the foreign key for
an association. For example, in the InternationalProduct example,
InternationalProduct.Id.ProductId might be the foreign key for an association to a
Product entity. This runs counter to the LightSpeed convention that an association’s
foreign key is represented by a field with the name association_nameId.
To handle this case, select the association arrow and enter the path to the key field in
the Key Property Reference box. (In code, apply ForeignKeyFieldAttribute to the
EntityHolder.) In the example above, the path would be Id.ProductId.
Composite Foreign Keys That Overlap the Primary Key
The Key Property Reference technique works when you have a simple foreign key
which happens to be a column in the composite primary key. In some cases, you will
have a composite foreign key, some of whose fields are columns in the primary key. In
this case, you cannot use Key Property Reference, because there is no single field which
acts as the foreign key; nor can you provide a composite foreign key field, because that
would require the overlapping columns to be mapped twice. Instead, you must provide
a custom association resolver. This is a type which implements IAssociationResolver
and returns a QueryExpression describing how to look up an associated entity or
collection.
Using a custom resolver brings certain limitations particularly around eager loading and
cascade delete. It is strongly recommended that you try to modify your database
schema to avoid the use of overlapping foreign and primary composite keys. If using a
custom resolver is unavoidable, contact Mindscape technical support for a sample.
Many-to-Many Associations Represented As Composite Keys
A common idiom for many-to-many associations, even in databases that otherwise use
surrogate keys, is to omit the surrogate key on the through table. That is, the through
table contains foreign keys to the tables being joined, and its primary key is a
composite key on these two columns rather than a separate Id column. For
Mindscape LightSpeed User Guide
296
information about implementing this in LightSpeed, see the online article Many-tomany associations and composite keys in LightSpeed.
Mindscape LightSpeed User Guide
297
Mapping Database Types to Domain Types
Many legacy databases have their own ways of storing data – some systematic
conventions, some ad hoc. Often, these storage formats aren’t the way you want to
represent the data in the domain model.
For example, many databases without a Boolean type tend to store Boolean values
using single-character strings, “Y” for true and “N” for false. You would not want to
surface these columns as strings in the domain model, because that would be confusing
to users of the domain model, and could lead to errors if users tried to enter arbitrary
strings. Rather, you would prefer to surface these columns as Boolean fields, and
translate between the database storage format and the domain type within the entity.
There are two ways of doing this in LightSpeed: custom wrappers, and field converters.
Custom Wrappers
As previously noted, LightSpeed maps .NET fields to database columns, and ignores
.NET properties. This means you are free to define wrapper properties or methods
with different types from the underlying field. These wrappers can convert between
the domain type, which they present in the public interface of the entity, and the
database representation, which is encapsulated in the private fields.
To implement this, select the field you want to wrap and set its Generation option to
FieldOnly. Then open or create a partial class file for the entity, and implement the
wrapper property or methods yourself.
Mindscape LightSpeed User Guide
298
Surfacing a database string as a domain Boolean property
partial class Member
{
// _isModerator string field is in designer generated code
public bool IsModerator
{
get { return _isModerator == "Y"; }
set
{
string dbIsModerator = value ? "Y" : "N";
Set(ref _isModerator, dbIsModerator, "IsModerator");
}
}
}
Remember to use the Entity.Set method to set the value of persistent fields.
A crucial pitfall of this approach is that the translation logic is not applied to queries.
Users must write queries in terms of the underlying database format, not the domain
format. This is confusing when using the query objects syntax, and is not possible at all
in LINQ:
Entity.Attribute("IsModerator") == "Y"
Entity.Attribute("IsModerator") == true
// works
// fails
from m uow.Members
where m.IsModerator == "Y"
select m
// compiler error, can’t compare bool and string
Therefore, if you use a custom wrapper, and anticipate users wanting to query on the
wrapped field, be sure to encapsulate all querying logic behind a repository API which
can perform the necessary translations.
Field Converters
A field converter encapsulates the translation between database and domain as a
reusable object. This is useful for two reasons: first, it allows that translation to be
applied to queries; and second, it allows the same translation to be applied to many
Mindscape LightSpeed User Guide
299
different fields without needing to write a custom wrapper for each. There are also
specialised cases which are problematic for custom mapping, such as LightSpeed
3-style data transfer objects, or TimeSpan fields where LightSpeed’s built-in handling
may clash with a desired database-specific mapping.
To implement a field converter, implement the IFieldConverter interface, or derive
from the FieldConverter<TDatabase, TModel> base class.
Converting a database string to a Boolean value
public class YesNoConverter : FieldConverter<string, bool>
{
protected override bool ConvertFromDatabase(string databaseValue)
{
return "Y".Equals(databaseValue, StringComparison.OrdinalIgnoreCase);
}
protected override string ConvertToDatabase(bool value)
{
return value ? "Y" : "N";
}
}
The LightSpeed designer represents columns requiring conversion using a user-defined
type. For example, you might represent the Y/N Boolean idiom as a YesNo type. This
saves you applying the converter separately to each field that needs it – instead, the
user-defined type bundles up the domain type, the database type and the mapping
between them. Note that the user-defined type will still surface in the domain model
as (in this case) a Boolean.
To create a user-defined type that represents a custom format, open the LightSpeed
Model Explorer, right-click the root model node, choose Add New User Defined Type,
and apply the following settings:


Name: The name to appear in the Data Type drop-down, e.g. YesNo.
CLR Type Name: How this type should be surfaced in the domain model, e.g.
System.Boolean. This should be a namespace-qualified CLR type name (see
Enums and Other User-Defined Types in the chapter Working with Models in
the Visual Designer for more information).
Mindscape LightSpeed User Guide
300




Is Value Type: Set this to True or False depending on whether the CLR type is a
struct or a class.
Data Type: How this type is represented in the database, e.g. String.
Converter Type: The name of the class which converts between the database
representation and the CLR type, e.g. MyProject.YesNoConverter.
Is Standard Data Type: You can usually leave this as True. However, if the
storage format uses a database-specific type such as the MySQL time type, set
it to False, and enter the database type name in the Database Type Name box.
Mindscape LightSpeed User Guide
301
Once you have defined a user-defined type to represent a custom storage format, you
can use it just like any other data type, by selecting it from the Data Type drop down:
LightSpeed will now generate a boolean property, but map it to a string column.
Furthermore, now that the user-defined type has been set up, you can apply it to as
many properties as you want, which makes it very convenient for mapping patterns
that are used widely across a database.
Field Converters for Hand-Coded Entities
You can also use field converters with hand-coded entities. This is done in a slightly
different way from the designer. Instead of creating a user-defined type, you simply
define each field as being of the desired domain type, then apply ColumnAttribute to
the field, specifying the ConverterType property.
Mindscape LightSpeed User Guide
302
Using a field converter in a hand-coded entity
[Column(ConverterType = typeof(YesNoConverter))]
private bool _isFullOfGreeks;
// domain type
public bool IsFullOfGreeks
{
get { return _isFullOfGreeks; }
set { Set(ref _isFullOfGreeks, value, "IsFullOfGreeks"); }
}
Field Converters and Querying
When you use a field that has a field converter in a query, the field converter is used to
translate the comparand to its database format. For example, consider the following
query:
var woes = from h in uow.Horses
where h.IsFullOfGreeks == true
select h;
The YesNoConverter is applied to the comparand true, resulting in SQL such as:
SELECT ...
FROM Horse
WHERE IsFullOfGreeks = 'Y'
// parameter substituted for clarity
This enables users of the entity to write queries in domain terms, and to use LINQ
without suffering compiler type errors.
Querying Considerations for Field Converter Design
Some care is required if the conversion is not one-to-one. For example, consider a
database convention which represents Booleans as integers, with 0 representing false
Mindscape LightSpeed User Guide
303
and any non-zero value representing true. When converting Booleans back to integers,
the converter has to pick a value to represent true, say 1.
A converter which is not one-to-one
public class IntBoolConverter : FieldConverter<int, bool>
{
protected override bool ConvertFromDatabase(int databaseValue)
{
return databaseValue != 0;
}
protected override string ConvertToDatabase(bool value)
{
return value ? 1 : 0; // danger!
}
}
Suppose now you wrote the LINQ query where o.SomeIntBoolField == true. The converter
translates the comparand true to the database value 1. So LightSpeed would emit the
SQL clause WHERE SomeIntBoolField == 1. This would not match values such as 2 or -1,
even though these are meant to be considered true. You would therefore need to be
careful to write the query where o.SomeIntBoolField != false, as this would translate to
WHERE SomeIntBoolField <> 0. This is only an issue if your mapping is not one-to-one.
Mindscape LightSpeed User Guide
304
Working with Database Providers
Although LightSpeed tries to abstract you away from the underlying database engine,
different databases do have different capabilities and characteristics and it’s important
to be aware of these when using LightSpeed, whether to avoid using unsupported
features or to ensure that you adhere to database-specific conventions. Furthermore,
in some cases LightSpeed provides special features for certain databases.
This chapter lists specific considerations for each database supported by LightSpeed.
Mindscape LightSpeed User Guide
305
DB2
LINQ Support
The ToString() method is not supported on DB2.
The coalesce (??) operator cannot be used with strings on DB2.
Some grouping operations fail on DB2.
Tools Support
LightSpeed offers only limited support for migrations on DB2.
Mindscape LightSpeed User Guide
306
MySQL
Designer Support
In order to drag tables and views onto the designer, you will need MySQL
Connector/Net. This is a free download from the MySQL Web site. LightSpeed has not
been tested with third-party Visual Studio/MySQL integration add-ins – please contact
us if you encounter problems when using such add-ins.
Although MySQL Connector/Net is capable of displaying stored procedures, at the time
of writing it does not support dragging stored procedures from Server Explorer. This is a
limitation of MySQL Connector/Net, not of LightSpeed. You can still add stored
procedures to your model using the Toolbox, or invoke them by constructing a
ProcedureQuery object in code.
Creating Tables from the Designer
If you use the LightSpeed designer’s Update Database feature to create MySQL tables,
these tables will use the default storage engine for the database. The default storage
engine can be set at the database level using MySQL administration tools. For Linux
MySQL installations, the default setting is MyISAM; it is normally recommended that
you change this to the transactional InnoDB engine.
You can override the default storage engine when creating tables by adding a MySQL
Roundtripping Policy to your model. To do this, open the LightSpeed Model Explorer
(View > Other Windows > LightSpeed Model), right click the model in the tree view, and
choose Add New MySQL Roundtripping Policy. You can then set the engine to be
whichever engine you would prefer LightSpeed to use for new tables.
Mindscape LightSpeed User Guide
307
Oracle
Using Oracle Stored Procedures with LightSpeed
Oracle stored procedures return row set results through parameters. In order to locate
the row set, LightSpeed adopts the convention that the results should be returned
through an out parameter of type ref cursor named “results”.
ODP.NET Versioning Considerations
Depending on your installed version(s) of the Oracle client, when using the Oracle9Odp
provider you may receive assembly binding errors for the Oracle.DataAccess DLL of the
form:
Could not load file or assembly ‘Oracle.DataAccess, Version=10.2.0.100,
Culture=neutral, PublicKeyToken=89b483f429c47342’ or one of its dependencies. The
system cannot find the file specified.
This can be solved using a binding redirect in the application configuration file, of the
form:
Binding redirect for Oracle ODP.NET
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Oracle.DataAccess" publicKeyToken="89b483f429c47342"
culture="neutral"/>
<!-- oldVersion should be the version that was not found -->
<!-- newVersion should be the version you have installed -->
<bindingRedirect oldVersion="10.2.0.100" newVersion="2.102.2.20" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Mindscape LightSpeed User Guide
308
Designer Support
Some third-party add-ins which integrate Oracle into Visual Studio (such as the Devart
DotConnect add-in) use a connection string format which is not understood by
LightSpeed. If you encounter problems when using such an add-in, create a new Server
Explorer connection using the .NET Framework Data Provider for Oracle or the Oracle
Data Provider for .NET.
Mindscape LightSpeed User Guide
309
PostgreSQL
Designer Support
At the time of writing, the free PostgreSQL driver, Npgsql, does not provide Visual
Studio integration to enable drag and drop onto the LightSpeed designer. If you want to
import PostgreSQL tables into the designer, there are a number of options:



Mindscape has created a free add-in which allows you to display PostgreSQL
databases in Server Explorer and drag them onto the designer. Please post in
the support forum if you would like to use this add-in.
Some commercial PostgreSQL libraries include Visual Studio integration. These
have not been tested with LightSpeed but some at least are known to work.
Please post in the support forum if you need advice on using a specific
commercial library with the LightSpeed designer.
You can use the lsgen.exe command line tool with the /l:lsmodel option to
generate a .lsmodel file from your database.
Mindscape LightSpeed User Guide
310
SimpleDB
Connection String Format
When using Amazon SimpleDB, LightSpeed expects a connection string in the following
format:
Access Key=your_access_key;Secret Access Key=your_secret_access_key
You can also specify the following additional connection string options:



Url: By default, LightSpeed will connect to Amazon’s default SimpleDB
endpoint (US East). Provide a Url element in the connection string to connect
to another SimpleDB endpoint, such as US West, Europe or Singapore, or to a
compatible store such as a local M/DB instance.
Consistent Read: By default, LightSpeed uses standard (eventually-consistent)
reads. To perform consistent reads, add the option Consistent Read=true to the
connection string.
Enable Batch Insert: By default, LightSpeed performs inserts one at a time. To
enable batching of inserts, add the option Enable Batch Insert=true to the
connection string.
Limitations
Amazon SimpleDB supports only a limited subset of the features of traditional
relational databases. Consequently, the following limitations apply when using
LightSpeed on SimpleDB:



No eager loading. Eager loads are silently translated into lazy loads and no
warning is given.
No cross-table queries. A query of the form
Entity.Attribute(“Contributor.UserName”) == “jd” will not work.
No batching of updates and deletes. Each operation is a separate request to
SimpleDB. At present, LightSpeed makes no attempt to perform these
operations in parallel, so a large number of updates may take a long time.
Insert batching must be turned on explicitly in the connection string.
Mindscape LightSpeed User Guide
311

No queries involving server-side operations, e.g. SQL functions such as SUM or
LOWER or LINQ equivalents such as Sum() or ToLower().
Eventual Consistency and Consistent Reads
SimpleDB has an ‘eventually consistent’ storage model. That is, when you make a
change, it is guaranteed to propagate through SimpleDB eventually, but it may not be
immediately visible on the next read.
If you need to ensure that a query retrieves the latest saved data, you can request a
consistent read. This trades off performance for currency.
By default, LightSpeed uses standard reads. To request consistent reads with
LightSpeed, include the Consistent Read=true element in the connection string. This
forces consistent reads on all queries.
To override the consistent-read setting on a per-query basis, use the
WithProviderOptions method and pass a suitable AmazonSimpleDBProviderOptions
object. (If you are using query objects, set Query.ProviderOptions.) Set
ConsistentRead to true to force a consistent read, to false to force a standard read, and
to null to use the default behaviour (standard or consistent according to the connection
string).
Overriding the consistent read setting for a single query
var forceConsistent = new AmazonSimpleDBProviderOptions
{
ConsistentRead = true
}
var products = unitOfWork.Products
.WithProviderOptions(forceConsistent);
Data Storage
SimpleDB has no concept of data types: everything is treated as a string. The most
important impact of this is on sorting: SimpleDB always performs lexical comparisons.
For example, ‘45’ sorts before ‘123’. This is usually not desirable for numeric values.
Mindscape LightSpeed User Guide
312
Therefore, when LightSpeed stores data in SimpleDB, it always stores it in a sortable
format. Numeric values are padded with leading zeroes to the maximum length of the
numeric type, e.g. 0000012345. DateTime values are stored using the ISO 8601 sortable
format, e.g. 2001-01-01T01:02:03.004.
For maximum efficiency you should therefore use the smallest suitable numeric type
(e.g. store a year as a short rather than an int). We also strongly advising using floats or
decimals instead of doubles, as a double takes 300+ characters to store!
Because negative numbers cannot be range-compared using lexical ordering, you
should not use signed types in comparisons unless you are confident that neither the
value used in the query, nor any value in SimpleDB, is actually negative.
Tools Support
In order to drag domains onto the designer, you will need Mindscape SimpleDB
Management Tools. You can use the free edition if you do not require SimpleDB
management capability.
Because SimpleDB treats all data as strings, the designer always infers the string type.
Schema round-tripping is not currently available for SimpleDB.
Migrations are not supported on SimpleDB.
LightSpeed 5 Changes
FindById() now has the option to return null for non-existent IDs - this is instead of
returning the spurious item SimpleDB usually returns. This option is available by setting
the MaterialiseZeroAttributeEntities property on the SimpleDBDataProviderAdapter.
Mindscape LightSpeed User Guide
313
SQLite
There are no known special considerations for SQLite.
Mindscape LightSpeed User Guide
314
SQL Server
SQL Server 2008
Spatial Data Types
To enable spatial data types at runtime, choose the SqlServer2008 or SqlServer2012
DataProvider in your LightSpeedContext configuration. If you choose this provider, you
must distribute Microsoft.SqlServer.Types.dll with you application, even if you do not
use the spatial data types.
In order to use spatial functions in LINQ queries, you will need to map the appropriate
SqlGeography and SqlGeometry methods to the equivalent SQL Server geography and
geometry member functions. For information about doing this, see ‘Mapping Member
Functions’ in the chapter Advanced Querying Techniques.
Time Data Type
LightSpeed 5 now has direct support for time data type translation. This is both in the
core and in the designer.
Stored Procedures
Table value parameters can be used in SQL Server 2008 stored procedures.
HierarchyId
HierarchyId is supported as data type when using the SQL Server 2008 or SQL Server
2012 providers.
SQL Server 2012
If you are using SQL Server 2012, you must be using Visual Studio 2010 or Visual Studio
2012 to connect using the Server Explorer.
This provider supports using the new Sequence identity type added in SQL Server 2012.
Mindscape LightSpeed User Guide
315
SQL Server 2000 Limitations
LightSpeed provides only limited support for SQL Server 2000. In particular paging
operations (including LINQ operations such as Skip that require paging) are not
supported.
SQL Server 2000 support will be deprecated in a future version of LightSpeed.
SQL Server 2000 Designer Support
The LightSpeed designer defaults to using SQL Server 2005 APIs for extracting the
database schema. Therefore, if you drag tables from SQL Server 2000 onto the
designer, you will get an error. To work around this, click on the model background and
set Database Provider to SqlServer2000 before dragging the tables on.
If you are using Visual Studio 2010, the only way to connect to SQL Server 2000 through
Server Explorer is to use the OLE DB provider. You can still drag tables from an OLE DB
connection onto the designer. This is a special workaround for the VS2010 SQL Server
2000 issue: you cannot drag tables from OLE DB for any other database. Note that at
runtime you must still use the normal SqlConnection connection string format, not the
OLE DB connection string format.
SQL Server 2000 support will be deprecated in a future version of LightSpeed.
Mindscape LightSpeed User Guide
316
SQL Server Compact
Versions
SQL Server Compact 3.5 is supported in the main LightSpeed DLL. SQL Server
Compact 4 is supported via the supplementary DLL
Mindscape.LightSpeed.Providers.SqlServerCe4.dll. If you are using SQL Server
Compact 4, you must deploy this to the same directory as the main LightSpeed DLL.
Paging and Ordering
SQL Server Compact 3.5 does not support paging. Although SQL Server Compact 4
provides some support for paging, this is not comprehensive. Some paging and
ordering operations may still fail.
LINQ Support
Some string operations (notably concatenations with ToString()) fail on SQL Server
Compact.
The coalesce (??) operator cannot be used with strings on SQL Server Compact.
There are several engine limitations which can affect complex LINQ queries, specifically
around joining, grouping, composition (e.g. Intersect), subselects and the Distinct
operator.
Tools Support
Migrations have only limited support on SQL Server Compact.
Mindscape LightSpeed User Guide
317
VistaDB 4
Paging and Ordering
VistaDB does not support paging, except for the specific case of limiting. (That is, you
can use the Take operator, but not the Skip operator.) In addition, VistaDB does not
support expressions in an OrderBy clause: you can order only by primitive properties,
not computed expressions or properties of associated entities.
LINQ Support
There are several engine limitations which can affect complex LINQ queries, specifically
around joining, grouping, composition (e.g. Intersect), subselects and the Distinct
operator.
Tools Support
The designer does not currently support dragging views from VistaDB.
Migrations have only limited support on VistaDB.
Mindscape LightSpeed User Guide
318
Low Level Database Access
It is sometimes useful to send SQL commands directly to a database, or for other
reasons to drop down to ADO.NET. LightSpeed supports this through the
IDataProviderObjectFactory interface and IUnitOfWork.PrepareCommand and
FindBySql methods.
Creating ADO.NET Objects with LightSpeed
The LightSpeedContext.DataProviderObjectFactory property returns an
IDataProviderObjectFactory for the context’s data provider. You can use this to create
ADO.NET connection, command and command parameter objects suitable to the
database in use.
Note that connection objects are not automatically initialised with the context
connection string.
Using ADO.NET Objects with LightSpeed
To associate an ADO.NET command with the connection and transaction of a unit of
work, call IUnitOfWork.PrepareCommand. This does not execute the command – you
must still call ExecuteReader, ExecuteScalar or ExecuteNonQuery – but it prepares the
command to run over the unit of work’s connection, and enrols it in the unit of work’s
transaction if one is in progress.
If you want to materialise the results of a SQL command as entities, call
IUnitOfWork.FindBySql. In this case, the results of the command must include an Id
column.
Mindscape LightSpeed User Guide
319
Windows Azure Table Service
The Windows Azure Table Storage Service is supported in LightSpeed 5, with the Azure
Table Service data provider.
To get this working, you must reference the provider DLL explicitly, as it isn’t baked into
the LightSpeed DLL like most providers. The connection string format is:
“Account Name=acct_name;Key=kev_base64_string;Use HTTPS=false”;
You must also use the Guid identity method.
There is no current drag-and-drop designer support from the Server Explorer. You can
still create your models in the designer, however you need to use the toolbox instead
of the aforementioned explorer.
After you have created the tables, the first additions to the table service will create any
schema needed.
Mindscape LightSpeed User Guide
320
Database Migrations
Migrations are a solution to the problem of managing database versions. A migration
captures the set of actions required to update a database from one version to the
next—which might include schema changes such as widening or adding columns and/or
data changes such as computing defaults for new columns—and to roll that same
database back to its original state.
Capturing migrations as artifacts makes it easier and more reliable to apply the changes
to multiple physical databases (development, test, staging and production) with high
confidence that the same set of changes is being applied each time—because it worked
in staging, it will work in production.
Being able to roll migrations backwards as well as forwards is important for acceptance
testing and staging environments. For example, if staging fails, you will want to revert it
to its previous status. But you may not want to rebuild the entire environment from
scratch. Running migrations in reverse enables this.
LightSpeed’s migrations framework provides you with:



A way to specify migrations in code using a database-agnostic API and the full
flexibility of the .NET Framework.
A tool that creates migrations for you as you make changes to your LightSpeed
model.
Command-line and GUI tools for running migrations, or for generating SQL
scripts that can be run later by a DBA.
Mindscape LightSpeed User Guide
321
Creating Migrations
Writing Migration Code
A migration is implemented as a class which derives from Migration. The actions to
take are specified in the class’ overrides of the Migration.Up and Migration.Down
methods.
The Migration class provides a number of methods that you can call to specify the
actions to take in the migration. This section lists a few of the key methods; see the
Migration class documentation for specific overloads, more details and other methods.
Method
Action
Typical SQL Equivalent
AddTable
Adds a new table to the database
CREATE TABLE
DropTable
Drops a table from the database
DROP TABLE
RenameTable
Changes the name of an existing
table
(varies)
AddColumn
Adds a column to a table
ALTER TABLE ADD
AddForeignKeyColumn
Adds a column with a foreign key
constraint to a table
ALTER TABLE ADD
DropColumn
Drops a column from a table
ALTER TABLE DROP
ChangeColumn
Change the data type or nullability ALTER TABLE ALTER
of a column
RenameColumn
Changes the name of a column
(varies)
ExecuteNativeCommand
Executes arbitrary SQL. You can
use this to make changes that are
not supported through the
migrations API, but it may make
your migration code non-portable
across database providers.
(as specified)
(SQL equivalents are approximate; the exact SQL syntax varies from database to
database.)
Mindscape LightSpeed User Guide
322
Creating Migrations from a Model
The LightSpeed designer can create migrations for you. Migrations are created as
source code which you can edit to reflect additional actions, conditional logic and
overridden behaviour. Your model will give rise to a sequence of migrations as it
evolves over time. Migrations are collected together in a migrations project separate
from your application and model code.
Because migrations are created as C# or Visual Basic source code, they are stored in a
Visual Studio project. You need to specify on your model which project it should store
its migrations in.
To do this:




Open your model, select the Migrations menu and choose Set Migrations
Project.
LightSpeed lists the existing projects in your solution.
Choose a project and click OK, or click the New Project button.
If you create a new project, LightSpeed prompts for the project file. Create a
new folder and enter the desired project file name.
Don’t use the same migrations project for multiple models.
To create a migration in the migration project, select the Migrations menu and choose
Create Migration.



LightSpeed displays the actions required in the Up method to migrate the
database from the last migration to the current model design, and the actions
required in the Down method to reverse the process. You can de-select any
actions that you don’t want generated for you.
Enter a migration name and optionally a description. The migration name will
be the name of the generated class and must be a valid class name in the
migration project language.
Click OK. LightSpeed generates and displays the migration class. You can now
edit this class if required.
LightSpeed stores snapshots of your model in the migrations project, and uses these to
work out what has changed. Do not move, delete or rename these snapshots!
Mindscape LightSpeed User Guide
323
Create Migrations from Scratch
Introduced in LightSpeed 5, empty migrations can be created for use with custom
migration code.
Data Types
You will normally specify data types using the ModelDataType class. This abstracts your
code from the database representation. For example, you would write
ModelDataType.String rather than VARCHAR, NVARCHAR or VARCHAR2.
When you require fine control over data types, you can instead specify a literal string.
For example, if you want a string to be represented as a fixed-size field, you could
specify “CHAR” instead of ModelDataType.String. Such data types are database
specific.
Identity Generation
The Migration class also provides APIs to create the database resources used by the
LightSpeed identity generation methods. See the AddKeyTable API.
Initial Value When Adding a Table
Introduced in LightSpeed 5, the AddTable method now includes an overload to create a
key table with an initial value. This will be used by default when generating migrations
from the designer.
Migrating Entity Framework EDMX Model Files
LightSpeed now includes support for migrating EDMX V2 files, from Entity Framework,
to the LightSpeed Model format. This allows you to import domain models created in
the Entity Designer into LightSpeed.
Injecting Custom User Code
In LightSpeed 5, Before and After hooks after available, allowing custom user code to
be injected into the workflow when deriving.
Mindscape LightSpeed User Guide
324
Running Migrations
Running Migrations from Visual Studio
To run migrations from Visual Studio:






Open your model.
From the Migrations menu, choose Run Migrations. The Migrations window is
displayed.
The Migrations window shows the available migrations and the current
database version. (If you see a “migrations project does not build” message,
open the Errors window and fix the problems, then click Refresh to try again.)
Check that the Method is set to Apply to connected database and that the
database and connection string are correct. If not, choose Change to change
your settings. (See below for more info. You will always need to do this the
first time you run a given migration project.)
Select the target version that you’d like to migrate the database to.
Select Apply Now.
LightSpeed will figure out whether to run the Up or Down migrations depending on the
current database version.
Before you first run a migration from Visual Studio, you need to choose the target
database. To do this:





Choose the Change button next to the database connection information.
LightSpeed displays the Migration Settings wizard.
Choose Apply migrations directly to a database and click Next.
Either select a Server Explorer connection, or choose a database type and enter
a connection string.
Click Finish.
If you subsequently change the migration target, to run the migrations against a
different database, choose the Change button again and re-run the wizard.
Mindscape LightSpeed User Guide
325
Running Migrations from the Command Line
To run migrations from the command line, run the lsmigrate.exe program. You can find
this in the installation folder, under the Migrations subfolder. lsmigrate compiles
migration source code files from the current directory. (You do not need to have a
migrations project and you do not need to build the migrations yourself.)
The syntax for lsmigrate is:
lsmigrate provider_name connection_string [to_version]



provider_name: The type of database. See the Appendices for permitted
values.
connection_string: The database connection string.
to_version: The version to which to migrate the database. If omitted, the latest
version will be selected.
LightSpeed will figure out whether to run the Up or Down migrations depending on the
current database version.
Running Migrations from Your Application
To run migrations from your application, call the Migrator.Migrate method. You will
normally call the overload that takes an IMigrationLoader, passing an
AssemblyMigrationLoader for the DLL containing your mgirations.
Running migrations from code
var source = new AssemblyMigrationLoader(typeof(MyMigration).Assembly);
Migrator.Migrate(source, ProviderType.MySql5, connectionString, null);
If you want to monitor migration progress, for example to provide an interactive
display or to log SQL for diagnostic purposes, use Migrator.CreateMigrator to create a
Migrator instance, then call Migrator.Execute to run the migrations. You can set the
MigrationLogger property to log migrations, and handle the MigrationExecuting and
MigrationExecuted events to provide interactivity.
Mindscape LightSpeed User Guide
326
Note that the migrations assembly has several dependencies which are not part of the
normal LightSpeed redistributable.
Mindscape LightSpeed User Guide
327
Creating SQL Scripts from Migrations
To create SQL scripts from Visual Studio:







Open your model.
From the Migrations menu, choose Run Migrations. The Migrations window is
displayed.
Check that the Method is set to Generate SQL script and that the database is
correct. If you want LightSpeed to work out the starting version for the script
by connecting to the database, check that the connection string is correct; if
you want to specify the starting version for the SQL script yourself, check that
the connection string shows Not connected. If you need to change any of these
settings, choose the Change button. (See below for more info.)
The Migrations window shows the available migrations and the current
database version. (If you see a “migrations project does not build” message,
open the Errors window and fix the problems, then click Refresh to try again.)
If you’re connected to a database, select the target version which you’d like to
generate a SQL script for migrating to.
If you’re not connected to a database, select the start and end versions for
which you’d like to generate a SQL script for migrating between.
Select Generate SQL.
LightSpeed generates the SQL script and displays it in Visual Studio. You can save this
SQL script to the folder of your choice.
Choosing the Generation Settings
Before you first generate a SQL script, you need to choose the target database type;
optionally, you can also choose a database instance from which LightSpeed will work
out the current version. To do this:




Choose the Change button next to the database connection information.
LightSpeed displays the Migration Settings wizard.
Choose Generate SQL that I can later apply to a database and click Next.
If you’d like LightSpeed to work out the current version automatically each
time you generate a script, choose I’m connected to the database from this
Mindscape LightSpeed User Guide
328



computer. Otherwise, choose I’m not connected to the database from this
computer. Click Next.
If you chose connected operation, either select a Server Explorer connection,
or choose a database type and enter a connection string.
If you chose disconnected operation, choose a database type.
Click Finish.
If you subsequently need to change the migration settings, choose the Change button
again and re-run the wizard.
Mindscape LightSpeed User Guide
329
Database Support
Migrations are not supported on Amazon SimpleDB.
Some databases do not support all migration actions. For example, SQLite does not
support adding a foreign key column to an existing table. In addition, some features are
not currently implemented on DB2, SQL Server Compact Edition and VistaDB.
Mindscape LightSpeed User Guide
330
Appendices
Mindscape LightSpeed User Guide
331
Configuration Reference
The following configuration settings are available through the LightSpeedContext class
and the configuration file.
Property
(Configuration Attribute)
Description
More
Information
AuditInfoMode
(auditInfo)
How to generate user names for CreatedBy, Implementing
UpdatedBy, etc.
Storage Policies
in LightSpeed
AutoTimestampMode
(autoTimestamps)
How to generate timestamps for
CreatedOn, UpdatedOn, etc.
Implementing
Storage Policies
in LightSpeed
Cache
(cacheClass)
Cache implementation for cached entities
Performance and
Tuning
CascadeDeletes
(cascadeDeletes)
Whether delete operations cascade by
default
Basic Operations
CommandTimeout
(commandTimeout)
How long LightSpeed should allow
commands to run before failing them.
(Config entry is in seconds.)
ConnectionString
(connectionStringName)
The database connection string. (Config
entry refers to the <connectionStrings>
section.)
Basic Operations
CustomAuditInfoStrategy
(auditInfo)
IAuditInfoStrategy for user names for
CreatedBy, UpdatedBy, etc.
Implementing
Storage Policies
in LightSpeed
CustomAutoTimestampStrategy IAutoTimestampStrategy for timestamps
(autoTimestamps)
for CreatedOn, UpdatedOn, etc.
Implementing
Storage Policies
in LightSpeed
DataProvider
(dataProvider)
The type of database.
Basic Operations
DetectPropertyNames
(detectPropertyNames)
Enables the Entity.Set overload which does
not take a property name. True by default.
Mindscape LightSpeed User Guide
332
Property
(Configuration Attribute)
Description
More
Information
DisplayNamingStrategy
(displayNamingStrategyClass)
IDisplayNamingStrategy for localising
property names for display in validation
messages.
Building
Applications with
LightSpeed
EntityFactory
IEntityFactory to be used when
materialising entities.
IdentityBlockSize
(identityBlockSize)
When using the KeyTable identity method,
the number of Ids to reserve per database
query. When using the Sequence or
MultiSequence method, the increment
amount of the sequence.
Controlling the
Database
Mapping
IdentityMethod
(identityMethod)
How LightSpeed assigns Ids to entities.
Controlling the
Database
Mapping
IdentityMethodOptions
Additional configuration for the
IdentityMethod
Controlling the
Database
Mapping
Logger
(loggerClass)
ILogger for SQL and debug logging
Testing and
Debugging
NamingStrategy
(namingStrategyClass)
INamingStrategy for database mapping.
Controlling the
Database
Mapping
PluralizeTableNames
(pluralizeTableNames)
Whether table names in the database use
the plural or singular form of the entity
class name (e.g. Person table or People
table).
Controlling the
Database
Mapping
QuoteIdentifiers
(quoteIdentifiers)
Whether to quote identifiers (e.g. table
names) in generated SQL. Avoids conflicts
with SQL reserved words, but can cause
case sensitivity issues on some databases.
Basic Operations
Schema
(schema)
The default database schema (can be
overridden on a per-entity basis).
SearchEngine
(searchEngineClass)
The type of full-text search engine, if any.
Mindscape LightSpeed User Guide
Advanced
Querying
Techniques
333
Property
(Configuration Attribute)
Description
More
Information
SearchEngineFileLocation
(searchEngineFileLocation)
Location of full text search index file(s).
Advanced
Querying
Techniques
UpdateBatchSize
(updateBatchSize)
Maximum number of update statements
per command batch.
Performance and
Tuning
UseMediumTrustCompatibility
Runs LightSpeed in a mode that is
compatible with ASP.NET medium trust, at
the expense of some performance.
Building Web
Applications with
LightSpeed
VerboseLogging
(verboseLogging)
Shows additional detail in log displays.
Testing and
Debugging
Mindscape LightSpeed User Guide
334
Tips, Tricks and Troubleshooting
Use a Short-Running Unit of Work
Long-running units of work introduce issues of concurrency and stale data. A
particularly common pattern in Web applications is “unit of work per (HTTP) request” –
never keep a unit of work across requests (this isn’t just a matter of concurrency or
stale data, but storing a unit of work across requests is unreliable: for example a unit of
work can’t be serialised to session state).
See the chapters Building Web Applications with LightSpeed and Building WPF and
Windows Forms Applications with LightSpeed for more discussion.
Use a Single LightSpeedContext
The LightSpeedContext contains essentially static configuration information. There’s
usually no need to have more than one unless you’re talking to multiple databases.
Furthermore, because ID allocation in sequence or key table configurations is handled
at a context level, using multiple contexts can lead to increased database load and ID
fragmentation. See Building Applications with LightSpeed for more discussion.
Use Configuration Files Where Possible
Prefer configuration files to code-based configuration. You can set up the LightSpeed
context in code by setting properties, or in configuration via the web.config or
app.exe.config file. Using a configuration file makes it easier for operations staff or
other users to configure the application to different environments.
Partial Classes
Use partial classes to add behaviour to generated entity classes. You can also add
properties in partial classes, but if those properties introduce additional state (as
opposed to being wrapper or adapter properties around the existing LightSpeed fields),
be sure to mark the backing fields with the TransientAttribute so LightSpeed doesn’t try
to persist them.
Mindscape LightSpeed User Guide
335
Never use automatic properties in a LightSpeed entity class – the C# compiler
generates a backing field which doesn’t map to a database column, and you can’t get at
the field to mark it transient.
Use Eager Loading and Named Aggregates to Tune Loading
Performance
Eager loading and named aggregates allow you to tune loading performance for
difference scenarios. Eager loading means that LightSpeed queries the database for an
entity and its associated entities in a single database round-trip. This can massively
improve performance, avoiding the so-called “N+1” problem. If you need finer control,
to be able to choose whether to eager-load an association or not on a per-query basis,
you can use a named aggregate: this allows you to say “I want to load this Customer
with all their Orders” or “I want to load just the Customer” depending on the task at
hand. You can also apply this to fields, for example eager-loading a large binary only if
the “with high-resolution picture” aggregate is specified in the query.
Avoid Needless Change Tracking
Don’t turn on change tracking unless you need it. Change tracking has a memory cost,
which can grow significant if you are making many changes to lots of entities. Don’t
incur that cost unless you have a reason for doing so.
Measure the Performance Impact of Changing UpdateBatchSize
Tweaking UpdateBatchSize can improve save performance – or worsen it. When
LightSpeed saves a unit of work, it sends the SQL statements to the database in
batches. The number of statements per batch is controlled by
LightSpeedConfiguration.UpdateBatchSize. The default value is 10. Increasing this
figure reduces the number of round-trips to the database, which can improve
performance. However, it also means that LightSpeed has to build, and the database
has to parse, much larger blocks of SQL – which can worsen performance. (Also, some
databases limit the number of parameters in a single SQL batch, so very large batch
sizes may cause database errors.) Don’t increase UpdateBatchSize too far, and always
measure the performance impact rather than just assuming that bigger is better!
Mindscape LightSpeed User Guide
336
Consider Changing IdentityBlockSize
Increasing IdentityBlockSize can improve performance – at a cost. When using a key
table identity method, LightSpeed has to query the database to get the next “block” of
IDs to allocate to entities. LightSpeedContext.IdentityBlockSize determines how many
IDs it blocks out on each query. Increasing the IdentityBlockSize from its default of 10
therefore means LightSpeed has to query the key table less often, improving
performance. For example, if you’re doing a bulk insert of tens of thousands of items,
then increasing IdentityBlockSize from 10 to 1000 saves thousands of allocation calls
and can make the application run measurably faster. But a large block size can result in
a lot of unused IDs where the block has not been exhausted, so don’t increase
IdentityBlockSize beyond the point of diminishing returns.
As of LightSpeed 5, the identity block case can now be set on a per-table basis.
Keep Sequences in Sync with IdentityBlockSize
When using sequence identity methods, IdentityBlockSize must equal the sequence
increment amount. In a sequence identity method, the size of the ID block is
determined by the database, not by LightSpeed. In this case, IdentityBlockSize must be
the block size specified in the sequence definition, i.e. the INCREMENT BY amount. An
incorrect IdentityBlockSize can lead to duplicate IDs being allocated, which will result in
database constraint violations when you come to save.
ObjectDisposedException in SystemTransactionCompletedEvent
Some customers have reported experiencing ObjectDisposedException when disposing
a system TransactionScope than encapsulated two nested units of work. This appears
to be a bug in the .NET Framework running on Windows XP. The solution is to use a
single unit of work, or to dispose the first unit of work before creating the second unit
of work.
Mindscape LightSpeed User Guide
337
Command Line Tools Reference
LightSpeed includes the following command line tools.
lsgen – Create Entity Classes from Database
The lsgen tool creates entity classes from a database. It can also be used to create a
.lsmodel file which can then be loaded into the designer for further development.
Syntax: lsgen /p:provider /c:connection_string /l:language /n:namespace
/o:output_dest





provider: The type of database. See below.
connection_string: The database connection string.
language: The language to generate the entity classes in.
namespace: The namespace into which to generate the classes.
output_dest: Where to save the generated files. When generating C# or Visual
Basic files, this must be the name of a folder. The folder is created if it does not
already exist. Existing files at this location are overwritten.
lsgen also supports the following optional switches:








/linq: Generate a strong-typed LINQ unit of work class.
/contracts: Generate WCF data contracts.
/m:model_name : Specifies the model name for LINQ or WCF class names
/include:tables : Include only the specified tables in the generated model.
/exclude:tables : Exclude the specified tables from the generated model.
/stp:prefix : The prefix to be stripped from table names if present (e.g. tbl).
/src:prefix : The prefix to be stripped from column names if present (e.g. col).
/template:template_file : Use a custom code generation template.
The tables argument to the /include and /exclude switches is either a commaseparated list of table names, or the @ character followed by the name of a file
containing the list of tables, one per line. E.g. /include:Cars,Engines or
/exclude:@CarTables.txt. Associations are generated regardless of whether the
Mindscape LightSpeed User Guide
338
association target type is generated, so care is required to avoid “dangling”
associations.
Individual columns can be excluded using table.column notation, e.g.
MyTable.UnwantedColumn, or *.column to exclude columns of that name no matter
which table they are in.
To create a designer model using lsgen, specify /l:lsmodel. In this case, the output
destination (/o) must be a file, not a folder. lsgen does not generate any layout for the
generated model: it is up to you to make the diagram look good by loading it into Visual
Studio and dragging the entities into a suitable layout.
You can also generate C# or Visual Basic files from a LightSpeed designer model using
lsgen. This is not normally necessary because Visual Studio automatically generates the
appropriate code, but it may be useful in certain automation scenarios. To do this,
specify /p:lsmodel, and pass the model (.lsmodel) filename as the /c (connection
string) option.
lsmigrate – Apply Migrations
The lsmigrate tool applies migrations, supplied as source code, to a database.
Syntax: lsmigrate provider connection_string [to_version]



provider: The type of database. See below.
connection_string: The database connection string.
to_version: The version to which to migrate the database. If omitted, the latest
version will be selected.
Database Providers in Command Line Tools
The ‘provider’ argument for command line tools can take the following values:
Value
Database
Abbreviation
MySql5
MySQL
mysql
Oracle9
Oracle
oracle
PostgreSql8
PostgreSQL
pg
Mindscape LightSpeed User Guide
339
Value
Database
Abbreviation
Sqlite3
SQLite
sqlite
SqlServer2000
Microsoft SQL Server 2000
sql2000
SqlServer2005
Microsoft SQL Server 2005
sql2005
SqlServer2008
Microsoft SQL Server 2008
sql2008
SqlServer2012
Microsoft SQL Server 2012
sql2012
SqlServerCE
Microsoft SQL Server Compact 3.5
sqlce
SqlServerCE4
Microsoft SQL Server Compact 4
sqlce4
VistaDB4
VistaDB
vdb4
Languages in Command Line Tools
The ‘language’ argument for command line tools can take the following values:
Value
Language
Abbreviation
CSharp
C#
cs
VisualBasic
Visual Basic
vb
Mindscape LightSpeed User Guide
340
LINQ Support Limitations
The LINQ API was designed by Microsoft to represent a wide range of set-oriented
operations, not all of which are meaningful or practical in the context of a relational
database or the SQL query language. Some LINQ queries that are syntactically valid are
therefore not supported in LightSpeed, and there are some limitations on the terms
that can be used in a LINQ query to LightSpeed. This section summarises these
limitations.
Unsupported LINQ Operators
The following operations are not supported by the LightSpeed LINQ Query Provider,
and no support is planned.



SequenceEqual: Compares two sequences for equality
SkipWhile: Skips elements while a condition remains true
TakeWhile: Takes elements while a condition remains true
If you need to use one of these operators, you must materialise the query results using
AsEnumerable or ToList. You can then process it using LINQ to Objects, which does
support the operators mentioned above. For example:
Using LINQ to Objects operators with a LightSpeed query
// Runtime error: can't translate TakeWhile to SQL
var millionaires = unitOfWork.Employees
.OrderByDescending(e => e.Salary)
.TakeWhile(e => e.Salary >= 1000000);
// Success: translates first two lines to SQL, then runs TakeWhile on the results
var millionaires = unitOfWork.Employees
.OrderByDescending(e => e.Salary)
.AsEnumerable()
// subsequent operators run client-side
.TakeWhile(e => e.Salary >= 1000000);
Mindscape LightSpeed User Guide
341
Comparisons to an Associated Entity
In a LINQ query, you can compare values from one entity with values from an
associated entity. However, you must write the query so that the associated entity
appears on the left of the comparison operator.
Comparing attributes of associated entities in a LINQ query
// Runtime error: association must appear on left
where order.Price > order.Customer.CreditLimit
// Corrected version
where order.Customer.CreditLimit <= order.Price
CLR Methods in a LINQ Query
The translation of CLR methods to SQL is dependent on the database provider. As a
guideline:






String members which correspond to the SQL LIKE operator such as Contains
and StartsWith are generally supported on all databases. (Note, however, that
the comparand must be a string known at query time; it cannot be another
property of the range variable. For example, you can write where
m.Name.Contains(filterBox.Text), but not where m.Name.Contains(m.Email). This is
a restriction of the SQL LIKE operator.)
Queries involving string concatenation may not be correctly handled on nonSQL Server databases.
The extended patterns of the Visual Basic Like operator are supported only on
SQL Server.
Math.Min and Math.Max are supported on all databases.
Other Math members such as Abs, Ceiling and Log are generally supported on
all databases except SQLite.
DateTime members relating to date or time parts (Year, Month, Day, Hour,
Minute, Second) are generally supported on all databases. The DateTime.Date
property is supported only on SQL Server 2008, Oracle and PostgreSQL. The
DateTime.Time property is not supported on any database.
Mindscape LightSpeed User Guide
342
Joining, Grouping and Combining
Some complex joining and grouping scenarios are not supported. Support for some
scenarios will be added in future builds; please contact Mindscape if there is a
particular pattern you would like us to support.
Unsupported scenarios include:





Binary expressions used as grouping keys
Group joins over more than two tables
Queries that self-join in their criteria
Combining a grouping with joins
Some grouping with ordering or paging queries
Set operations
There is only partial support for the Concat, Intersect, Union and Except operators.
These can be used to combine entity sets of the same type, but support for other
scenarios (such as projections from multiple tables) is very limited. Many databases do
not support the Intersect operator at all.
Database Specific Limitations
Some databases lack support for certain functions or operations that are supported
elsewhere. See Working with Database Providers for database-specific information.
Mindscape LightSpeed User Guide
343
Further Reading
The following resources are not specific to LightSpeed, but provide background and
more detail on the patterns and techniques that influenced the design of LightSpeed.
Eric Evans, Domain Driven Design
Martin Fowler, Patterns of Enterprise Application Architecture
Mindscape LightSpeed User Guide
344
Index
Add method, 60
adding entities, 60
ADO.NET, 307
transaction, 64
aliasing, 214
application, 88
creating and upgrading the database, 314
ASP.NET, 99
Dynamic Data, 113
medium trust, 112
ASP.NET MVC, 106
sample, 111
association
and database first development, 172
creating in code, 30
creating in the designer, 23
custom resolver, 284
eager loading, 232
Get method, 31
mapping to foreign key, 68
metadata for, 226
one-way, 167
properties, 24
reverse, 30
Set method, 31
attributes
and fields, 30
auditing, 259
auto through entity, 25
automatic properties, 28
shunned, 324
batch size, 243
batching, 243
performance, 324
BeginTransaction method, 64
binding. See data binding
block allocation of Ids, 80
bulk operations, 240
cache, 245
bulk operations and, 242
manual access, 249
cascade delete, 62
change tracking, 271
class table inheritance, 158
ClickOnce, 122
Mindscape LightSpeed User Guide
code first development, 28
code generation, 184
T4 templates, 188
templates, 184
using designer extensions, 186
column
loading computed, 74
mapping, 70
mapping to association, 68
mapping to field, 67
mapping to Id, 67
value object mapping, 164
Column Name option, 70
ColumnAttribute, 71
command line tools, 326
composite key, 280
many-to-many association, 284
concrete table inheritance, 159
concurrency, 268
configuration, 45, 46, 89, 320
file, 46, 89
lightSpeedContexts section, 46
connection string, 48
SimpleDB, 299
ConnectionStrategy class, 95
controller, 106
convention over configuration, 66
Convert to Manual Implementation command,
197
creation time, 259
timestamp, 263
cross join, 216
cross-cutting keys, 284
CRUD, 43
stored procedures for CRUD operations, 274
custom association resolver, 284
custom attributes
in the designer, 194
custom mapping
data type, 175
custom property setter, 197
data binding
ASP.NET MVC, 107
interfaces, 121
live data feed, 119
345
Web Forms, 103
data contract, 135
formatter for EntityCollections, 136
data format, 286
data provider, 47
data transfer object, 138
LightSpeed 3 compatibility, 141
sample, 142
data type
conversion, 287
converting in property code, 286
custom mapping, 175
database-specific, 174
designer, 174
spatial, 303
database, 47, 293
converting data, 286
creating in the designer, 18
hints, 250
index, 250
legacy, 272
mapping, 66
migrations, 309
querying, 50
saving changes, 60, 62
synchronising to model, 170
view, 82
database connection
and unit of work, 95
custom strategy, 95
database first development, 21, 170
and inheritance, 172
one-to-one association, 172
reorganising domain model, 172
value object, 172
database synchronisation
configuring, 173
database-defined type, 174
DB2, 294
debugging
logging, 149
delete
bulk delete, 240
deleting entities, 62
cascade, 62
deployment
ClickOnce, 122
designer, 13, 168
code generation, 184
creating migrations, 311
Mindscape LightSpeed User Guide
defaults for creating entities, 183
enum, 174
extensibility, 186
filtering the view, 178
keyboard shortcut, 196
Model Explorer window, 169
PostgreSQL, 298
referencing an external class, 196
tips, 194
toolbox, 14
user-defined type, 174
detecting concurrent edits, 268
discriminator, 156
distinct query, 212
distributed applications, 128
client configuration, 133
samples, 136
service hosting, 130
DistributedUnitOfWork, 129
documentation
in the designer, 27
Documentation command, 27
domain model, 10
capturing an image of, 195
creating from database, 21, 36
creating in code, 28
creating with the designer, 13
linked files, 181
synchronising to database, 170
viewing large models, 178
domain service, 124
DTO. See data transfer object
Dynamic Data, 113
eager loading, 232
viewing load graph in designer, 179
entity, 44
adding new, 60
bulk update or delete, 240
classes, 18
composite key, 280
creating from database, 36
creating in code, 28
creating in the designer, 14
defaults for new entity types, 183
deleting, 62
distributed, 129
field loading, 236
Get method, 31
IsValid property, 37
loading strategy, 231
346
loading through stored procedure, 87
mapping to table, 67
mapping to view, 83
metadata for, 225
natural key, 277
OnValidate method, 41
renaming, 177, 197
saving creation and update details, 259
Set method, 29, 31
soft delete, 261
updating, 61
Validate method, 37
Entity.Attribute method, 56
EntityCollection, 30
WCF serialisation, 136
EntityDataBinder, 103
EntityHolder, 30
enum, 174
extension properties, 186
external class reference, 196
fake objects, 146
features, 7
field
excluding from save, 74
lazy loading, 236
load only, 74
mapping to column, 67
metadata for, 226
Set method, 29
transient, 74
vs. property, 28, 68
field converter, 175, 287
queries and, 291
Find method, 56
flush changes to database, 60, 62
foreign key
composite, 282
composite overlapping primary key, 284
mapping, 70
mapping to association, 68
naming convention, 68
part of primary key, 284
full text search, 200
bulk operations and, 242
function
invoking SQL functions, 204
GeneratedId method, 277
Get method, 31
Get Started command, 195
getter
Mindscape LightSpeed User Guide
custom code in property, 197
custom property getter, 286
grouping
using query objects, 217
GUID identity generation, 79
performance tradeoffs, 79
GuidComb, 79
hint, 250
IAssociationResolver interface, 284
Id, 76
assigning, 76
composite key, 280
identity type, 15
mapping to column, 67
natural key, 277
IDataErrorInfo, 121
IdColumnName, 71
identity column, 80
batching and, 244
Identity Column Name option, 70
identity generation, 76
block allocation, 80
identity map, 245
identity method, 76
per entity, 77
setting, 76
IdentityBlockSize, 325
for key table, 78
for sequences, 78
sequences and, 325
IdentityMethod enumeration, 76
IDisplayNamingStrategy interface, 93
IEditableObject, 121
IFieldConverter interface, 175, 287
ILogger interface, 150
import data transfer object, 140
INamingStrategy, 71
index hint, 250
inheritance, 156
choosing a mapping, 159
class table, 158
concrete table, 159
discriminator, 156
external base type, 196
hiding inheritance arrows, 196
moving properties between classes, 172
single table, 157
inner join, 215
InnoDB, 295
INotifyPropertyChanged, 121
347
inserting entities, 60
intersection, 220
IsValid property, 37
iterative development, 170
IUnitOfWork interface, 44, 56
IUnitOfWork.Add method, 60
IUnitOfWork.Project method, 211
IUnitOfWork.Remove method, 62
IUnitOfWork.SaveChanges method, 60, 62
join
using query objects, 214
keyboard shortcut, 196
KeyTable identity generation, 77
block allocation, 80
naming convention, 68
keyword
escaping, 73
large models, 178
large objects, 236
lazy loading, 236
legacy database, 272
LightSpeed, 4
LightSpeed Model Explorer, 169
LightSpeedContext, 45, 46, 90, 323
scope, 90
lightSpeedContexts section, 89
LightSpeedEntityModelBinder, 107
linked model, 181
LINQ, 50
invoking custom functions, 204
supported features, 329
loading
controlling with named aggregates, 233
default, 231
eager, 232
localisation, 42, 93
logging, 149
custom logger, 150
logical delete. See soft delete
long running unit of work, 119
lsgen, 36, 326
lsmigrate, 327
Lucene, 200
many to many association, 25
composite key, 284
creating in code, 33
designer, 191
hiding through entity, 172
mapping, 66
column, 70
Mindscape LightSpeed User Guide
default, 67
non-default, 70
table, 70
value object, 164
medium trust, 112
metadata, 223
and field values, 228
Microsoft SQL Server. See SQL Server
Migration class, 310
migrations, 309
creating SQL scripts, 316
running, 313
mock objects, 147
Model Explorer window, 169
model first development, 18, 170
MyISAM, 295
MySQL, 295
round-tripping policy, 173
N+1 problem, 232
named aggregate, 233, 239
filtering designer view by, 179
lazy loading fields using, 236
visualising, 239
naming convention
column, 67
defining your own, 71
foreign key, 68
key table, 68
Oracle stored procedure results, 296
primary key, 67
table, 67
natural key, 277
NoReverseAssociationAttribute, 167
ObjectDisposedException, 325
ODP.NET
could not load file or assembly, 296
one to many association, 23
one to one association, 25
and database first development, 172
OnValidate method, 41
optimistic concurrency, 268
OptimisticConcurrencyException
responding to, 268
Oracle, 296
outer join, 216
paging
using LINQ, 53
using query objects, 57
partial class
creating, 177
348
performance, 230
measuring, 252
PerRequestUnitOfWorkScope, 100
persistence, 74
load-only fields, 74
transient fields, 74
pluralization, 67
polymorphism, 159
PostgreSQL, 298
primary key
composite key, 280
mapping, 70
naming convention, 67
natural key, 277
profiling, 152
Project method, 211
projections
using query objects, 211
property
creating in the designer, 16
custom getter and setter, 197, 286
localising property name, 93
metadata, 226
renaming, 177, 197
Set method, 29
vs. field, 28, 68
query expression, 56
query object, 56, 211
subexpressions, 221
vs. LINQ, 59
QueryExpression class, 56
querying, 50, 199
query expression, 56
using query objects, 56
quoting identifiers, 73
rapid application development, 170
refactoring, 177
reference data, 167
release notes, 6
reminder note, 195
Removemethod, 62
removing entities, 62
Rename command, 197
renaming entities and fields, 177
reserved word, 73
reverse association, 30
RIA Services, 123
rich client, 116
sample
ASP.NET MVC, 111
Mindscape LightSpeed User Guide
data transfer objects, 142
distributed applications, 136
Web Forms, 105
SaveChanges method, 60, 62
saving changes, 60, 62
transactional, 64
schema, 92
migrating database schema, 309
scoping unit of work
ASP.NET MVC, 106
Web Forms, 100
WPF and Windows Forms, 117
search
full text, 200
second level cache, 245
sequence identity generation, 78
block allocation, 80
options, 80
sequence per table, 79
Set method, 29, 31
setter
custom code in property, 197
custom property setter, 286
Silverlight, 123
SimpleDB, 299
single table inheritance, 157
soft delete, 261
sorting
using LINQ, 53
using query objects, 57
spatial data
SQL Server 2008, 303
SQL
creating migration scripts, 316
invoking SQL functions, 204
logging, 149
timing, 152
SQL keyword
escaping, 73
SQL Server, 303
SQL Server 2000, 304
SQL Server Compact, 305
SQLite, 302
stored procedure, 85
CRUD procedures, 274
Oracle, 296
subexpression
using query objects, 221
surrogate key, 277
T4 templates, 188
349
table
dragging into model, 21
mapping, 70
mapping to entity, 67
table hint, 251
Table Name option, 70
table per concrete class, 159
table per hierarchy, 157
table per subclass, 158
TableAttribute, 71
tag
filtering designer view by, 178
testing, 144
through association, 25
auto through entity, 25
creating in code, 33
hiding through entity, 172
timestamp, 263
tips and tricks, 323
tracing, 149
track create time, 259
track update time, 259
tracking changes, 271
transaction, 64
ADO.NET, 64
and unit testing, 144
TransactionScope, 64
union, 220
unit of work, 44
adding entities, 60
bulk operations and, 242
class, 50
database connection, 95
deleting entities, 62
distributed, 129
long running, 119
saving changes, 60, 62
scoping in ASP.NET MVC, 106
scoping in Web Forms, 100
scoping in WPF and Windows Forms, 117
short running vs. long running, 323
unit testing, 144
update
bulk update, 240
update batching, 243
Update Database command, 18, 170
Update From Source command, 22, 170
update time, 259
timestamp, 263
UpdateBatchSize, 243, 324
Mindscape LightSpeed User Guide
updating entities, 61
user
identifying, 266
track creating or updating, 259
track deleting user, 261
user-defined type
designer, 174
Validate method, 37
validation, 37
advanced designer options, 38
ASP.NET Dynamic Data, 114
ASP.NET MVC, 109
customising automatic, 40
declarative in code, 40
designer, 37
localising messages, 42, 93
procedural, 41
Web Forms, 101
whole object, 41
validation attributes
and fields, 30
value object, 161
and database first development, 162, 172
editing values, 163
mapping, 164
view
database, 82, 213
filtering designer view, 178
mapping to entity, 83
querying through, 82
saving designer filter, 179
VistaDB, 306
Visual Basic, 35
visual designer. See designer
Visual Studio
designer, 168
mapping keyboard to designer, 196
migrations project, 311
running migrations from, 313
WCF, 128
custom entity service, 136
RIA Services, 123
samples, 136
service hosting, 130
Web, 99
Web Forms, 100
sample, 105
Web service
hosting in WCF, 130
loose coupling, 138
350
Web services, 128
Windows Forms, 116
WithAggregate operator, 234, 239
Mindscape LightSpeed User Guide
WPF, 116
XML documentation
in the designer, 27
351