0% found this document useful (0 votes)
2K views

Light Speed User Guide

This document provides a user guide for Mindscape LightSpeed, an object-relational mapping tool. It discusses key concepts like creating domain models using either a visual designer or code, validating entity properties, performing basic database operations through LINQ queries or query objects, and controlling the database mapping. The guide covers topics such as creating entities and associations, working with the unit of work, configuring LightSpeed, and using transactions.

Uploaded by

targzz
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2K views

Light Speed User Guide

This document provides a user guide for Mindscape LightSpeed, an object-relational mapping tool. It discusses key concepts like creating domain models using either a visual designer or code, validating entity properties, performing basic database operations through LINQ queries or query objects, and controlling the database mapping. The guide covers topics such as creating entities and associations, working with the unit of work, configuring LightSpeed, and using transactions.

Uploaded by

targzz
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 258

Mindscape LightSpeed User Guide

Contents
Introduction................................................................................................. 12
What is LightSpeed?.......................................................................................................................... 13 New to LightSpeed? .......................................................................................................................... 14 Old Hand? ......................................................................................................................................... 15 Key Features...................................................................................................................................... 16 About This Book ................................................................................................................................ 17

Creating Domain Models ............................................................................. 18


Objects and Databases...................................................................................................................... 19
LightSpeed and Object-Relational Mapping ................................................................................................ 19

Creating Models with the Visual Designer ........................................................................................ 21


Creating Entities in the Designer ................................................................................................................. 21 Using Entity Classes ..................................................................................................................................... 25 Creating Database Tables from Entities ....................................................................................................... 25 Creating Entities from Database Tables ....................................................................................................... 27 Creating Associations Between Entities ....................................................................................................... 29 Many-to-Many Associations ........................................................................................................................ 30 XML Documentation .................................................................................................................................... 32

Creating Models in Code ................................................................................................................... 33


Creating Entity Classes ................................................................................................................................. 33 Creating Associations Between Entities ....................................................................................................... 34 Many-to-Many Associations ........................................................................................................................ 37 Special Considerations for Visual Basic ........................................................................................................ 38 Creating Code Models from Database Tables .............................................................................................. 39

Validation .......................................................................................................................................... 40
Specifying Validation Criteria ....................................................................................................................... 40 Advanced Validation Options ...................................................................................................................... 41 Automatic Validations.................................................................................................................................. 42 Declarative Validation in Code ..................................................................................................................... 43 Overriding OnValidate ................................................................................................................................. 43 Validation Considerations ............................................................................................................................ 44 Localising Validation Messages .................................................................................................................... 44

Basic Operations .......................................................................................... 45


Working with Entities........................................................................................................................ 46
The Unit of Work.......................................................................................................................................... 46 The LightSpeedContext Object .................................................................................................................... 46

LightSpeed Configuration Basics ....................................................................................................... 48


LightSpeed Configuration Section ................................................................................................................ 48 Loading a LightSpeedContext from Configuration ....................................................................................... 48 Specifying the Data Provider........................................................................................................................ 49 Specifying the Connection String ................................................................................................................. 49 Other Configuration Options ....................................................................................................................... 50

Mindscape LightSpeed User Guide

Querying the Database Using LINQ .................................................................................................. 51


Creating a Strong-Typed Unit of Work......................................................................................................... 51 Writing LINQ Queries Using C# Syntax......................................................................................................... 51 Writing LINQ Queries Using the Standard Query Operators ....................................................................... 52 Writing LINQ Queries Against Hand-Coded Entities .................................................................................... 52 Common LINQ Techniques .......................................................................................................................... 53 LINQ Expressions.......................................................................................................................................... 54

Querying the Database Using Query Objects ................................................................................... 55


Creating a Unit of Work ............................................................................................................................... 55 Query Expressions........................................................................................................................................ 55 Sorting and Paging ....................................................................................................................................... 56 Query Objects .............................................................................................................................................. 56 Single Entity Queries .................................................................................................................................... 56 Count Queries .............................................................................................................................................. 57 Choosing Between LINQ and Query Objects ............................................................................................... 57

Creating, Modifying and Deleting Entities ........................................................................................ 58


How Changes are Saved ............................................................................................................................... 58 Adding a New Entity..................................................................................................................................... 58 Updating an Existing Entity .......................................................................................................................... 59 Deleting an Existing Entity ........................................................................................................................... 59 Saving the Unit of Work ............................................................................................................................... 59

Transactions ...................................................................................................................................... 61
Using TransactionScope ............................................................................................................................... 61 Using ADO.NET Transactions ....................................................................................................................... 61 Automatic Transactions ............................................................................................................................... 62

Controlling the Database Mapping............................................................... 63


Understanding the Default Mapping ................................................................................................ 64
Entity Classes Map to Tables ....................................................................................................................... 64 Entity Id Maps to the Id Column .................................................................................................................. 64 Fields Map to Columns ................................................................................................................................ 64 Associations Map to Foreign Key Columns .................................................................................................. 65 Id Values Come From KeyTable ................................................................................................................... 65 Default Mapping Example............................................................................................................................ 65

Overriding the Default Mapping ....................................................................................................... 66


Mapping Individual Tables and Columns ..................................................................................................... 66 Mapping Individual Tables and Columns in the Designer ............................................................................ 66 Mapping Individual Tables and Columns in Code ........................................................................................ 66 Defining Your Own Mapping Convention .................................................................................................... 67 Reserved Words ........................................................................................................................................... 68

Overriding Persistence Behaviour..................................................................................................... 69


Excluding a Field From Persistence .............................................................................................................. 69 Excluding a Field From Being Saved ............................................................................................................. 69 Examples ...................................................................................................................................................... 69

Identity Generation........................................................................................................................... 70
Identity Methods in LightSpeed ................................................................................................................... 70 Setting the Identity Method Globally .......................................................................................................... 70 Overriding the Identity Method on a Per-Entity Basis ................................................................................. 71 KeyTable Identity Generation ...................................................................................................................... 71 Sequence and MultiSequence Identity Generation ..................................................................................... 71

Mindscape LightSpeed User Guide

Guid and GuidComb Identity Generation .................................................................................................... 72 IdentityColumn Identity Generation ............................................................................................................ 73 Identity Generation Options ........................................................................................................................ 73 How Block Allocation Methods Work .......................................................................................................... 73

Working with Database Views .......................................................................................................... 75


Loading Entities Through a View.................................................................................................................. 75 Creating an Entity Class to Map a View ....................................................................................................... 76

Invoking Stored Procedures .............................................................................................................. 77


Invoking a Stored Procedure Using LINQ ..................................................................................................... 77 Invoking a Stored Procedure Using Query Objects ...................................................................................... 77 Database Considerations for Stored Procedures ......................................................................................... 78 Additional Support for Stored Procedures .................................................................................................. 78

Building Applications with LightSpeed ......................................................... 79


Configuration .................................................................................................................................... 80
How to Configure LightSpeed ...................................................................................................................... 80 Loading the Configuration ........................................................................................................................... 80 Setting Up a LightSpeedContext in Code ..................................................................................................... 81 Context Per Application, Not Context Per Request ..................................................................................... 81 Terminology ................................................................................................................................................. 81 Configuration Settings ................................................................................................................................. 82

Localizing LightSpeed Messages ....................................................................................................... 83


Localizing Property Names........................................................................................................................... 83

Customising How LightSpeed Connects to the Database ................................................................. 85


Implementing a Custom Connection Strategy ............................................................................................. 85 Using a Custom Connection Strategy........................................................................................................... 86

Building Web Applications ........................................................................... 88


Building ASP.NET Web Forms Applications ...................................................................................... 89
Unit of Work Scoping ................................................................................................................................... 89 Validation ..................................................................................................................................................... 90 Data Binding using EntityDataBinder ........................................................................................................... 92 Samples ........................................................................................................................................................ 94

Building ASP.NET MVC Applications ................................................................................................. 95


Unit of Work Scoping ................................................................................................................................... 95 Model Binding for LightSpeed Entities......................................................................................................... 96 Validation ..................................................................................................................................................... 97 Samples ........................................................................................................................................................ 98

Running LightSpeed in Medium Trust............................................................................................... 99 ASP.NET Dynamic Data ................................................................................................................... 100


Handling Associations ................................................................................................................................ 101 Validation ................................................................................................................................................... 101

Building WPF and Windows Forms Applications ........................................ 103


Unit Of Work Scoping...................................................................................................................... 104 Entity Support for Rich Client Frameworks..................................................................................... 105

Mindscape LightSpeed User Guide

Building Silverlight Applications ................................................................. 106


Using RIA Services with LightSpeed ................................................................................................ 107 Validation in RIA Services................................................................................................................ 108

Building Distributed Applications ............................................................... 109


Distributed Entity Programming ..................................................................................................... 110
The Distributed Unit of Work..................................................................................................................... 110 The DistributedUnitOfWorkService ........................................................................................................... 111 Manually Hosting the Service .................................................................................................................... 111 Supported Bindings .................................................................................................................................... 113 Configuring your client for a DistributedUnitOfWork ................................................................................ 113 Executing operations at the client ............................................................................................................. 114 Data Contracts ........................................................................................................................................... 114 Building WCF Services using Entities ......................................................................................................... 115 Samples ...................................................................................................................................................... 115

Building WCF Services using Data Transfer Objects ....................................................................... 116


Providing custom mapping for IUnitOfWork.Import ................................................................................. 118 Compatibility with LightSpeed 3 code generated DTOs ............................................................................ 118 Samples ...................................................................................................................................................... 119

Testing and Debugging ............................................................................... 120


Unit Testing ..................................................................................................................................... 121 Logging ............................................................................................................................................ 122
Built-In Loggers .......................................................................................................................................... 122 Enabling Logging ........................................................................................................................................ 122 Disabling Logging ....................................................................................................................................... 123 Building a Custom Logger .......................................................................................................................... 123

Profiling ........................................................................................................................................... 124


Query Patterns ........................................................................................................................................... 124 Timing Queries ........................................................................................................................................... 124 Using a Custom Logger for Profiling........................................................................................................... 124

Using the Debugger Visualizer ........................................................................................................ 125

Domain Modelling Techniques ................................................................... 126


Inheritance ...................................................................................................................................... 127
Discriminators ............................................................................................................................................ 127 Single Table Inheritance ............................................................................................................................. 127 Class Table Inheritance .............................................................................................................................. 128 Implementing Common Services in a Base Class ....................................................................................... 129 Which Should I Choose? ............................................................................................................................ 129

Value Objects .................................................................................................................................. 130


Defining a Value Object Type in the Designer ........................................................................................... 130 Creating a Value Object Member in the Designer ..................................................................................... 130 Value Objects in Database-First Development .......................................................................................... 130 Defining a Value Object Type in Code ........................................................................................................ 131 Creating a Value Object Member in Code ................................................................................................. 131 Setting Value Object Properties ................................................................................................................. 132 Value Object Database Mappings .............................................................................................................. 132

Mindscape LightSpeed User Guide

Reference Data and Lookups .......................................................................................................... 134

Working with Models in the Visual Designer .............................................. 135


LightSpeed Model Explorer ............................................................................................................. 136 Workflows for Rapid Application Development ............................................................................. 137
Driving the Database from the Domain Model .......................................................................................... 137 Modelling Using Your Database Tools ....................................................................................................... 137 Which Should I Choose? ............................................................................................................................ 138 Reorganising Model Elements in a Database First Workflow .................................................................... 138 Configuring Database Synchronisation ...................................................................................................... 139

Enums and Other User-Defined Types............................................................................................ 140


Adding an Enum Type to the Designer ...................................................................................................... 140 Adding a Database-Defined Type to the Designer ..................................................................................... 140 Adding a User-Defined Type with Custom Mapping .................................................................................. 141 Using a User-Defined Type ........................................................................................................................ 141

Refactoring in the Designer ............................................................................................................ 142 Custom Views.................................................................................................................................. 143


Filtering ...................................................................................................................................................... 143 QuickViews................................................................................................................................................. 144

Linked Models ................................................................................................................................. 145


Code Generation ........................................................................................................................................ 145 Creating Associations Across Model File Boundaries ................................................................................ 145 Creating and Maintaining Entity Links ....................................................................................................... 146

Changing the Designer Defaults...................................................................................................... 147 Customising the Generated Code ................................................................................................... 148
Using Your Own Code Generation Templates ........................................................................................... 148 Extending the Designer Metamodel .......................................................................................................... 149 Using T4 Templates with the LightSpeed Designer.................................................................................... 151

Many-to-Many Associations ........................................................................................................... 153


Using an Auto Through Entity .................................................................................................................... 153 Using an Explicit Through Entity ................................................................................................................ 153 How Do Auto and Explicit Through Entities Differ? ................................................................................... 154

Designer Shortcuts and Tips ........................................................................................................... 155


Speeding Up Property Entry ...................................................................................................................... 155 Custom Attributes ...................................................................................................................................... 155 Grabbing an Image of Your Model ............................................................................................................. 155 Using Reminder Notes ............................................................................................................................... 156 Use Get Started for Configuration File Entries ........................................................................................... 156 Rearranging Properties .............................................................................................................................. 156 Assigning Keyboard Shortcuts to LightSpeed Commands ......................................................................... 156 Using a Custom Base Class for Your Entities .............................................................................................. 156 Changing Property or Entity Names When Other Code Already Uses Them ............................................. 157 Writing Custom Property Getter or Setter Code ....................................................................................... 157 Setting Validation Options Which Arent In the Properties Window......................................................... 157

Advanced Querying Techniques ................................................................. 158


Full Text Search ............................................................................................................................... 159

Mindscape LightSpeed User Guide

Invoking SQL Functions ................................................................................................................... 160


Mapping SQL Functions in LINQ................................................................................................................. 160 Mapping to a Custom Function.................................................................................................................. 161 Mapping Argument Order ......................................................................................................................... 161 Mapping Member Functions ..................................................................................................................... 162 Invoking SQL Functions Using Query Objects ............................................................................................ 162

Exploring the Query Object............................................................................................................. 163


Basic Querying Operations ........................................................................................................................ 163 Controlling Entity Load .............................................................................................................................. 163 Projections ................................................................................................................................................. 163 Views .......................................................................................................................................................... 165 Controlling Aliasing .................................................................................................................................... 165 Joins ........................................................................................................................................................... 165 Grouping .................................................................................................................................................... 167 Subexpressions .......................................................................................................................................... 169 Unions and Intersections ........................................................................................................................... 169 Hints ........................................................................................................................................................... 169 Other Querying Properties ........................................................................................................................ 169

Subexpressions ............................................................................................................................... 170

Working with Metadata ............................................................................. 172


The LightSpeed Metamodel ............................................................................................................ 173
Referencing the Metadata Assembly ......................................................................................................... 173

Getting Class Information ............................................................................................................... 174


Getting Class Settings ................................................................................................................................ 174 Getting Field and Association Information ................................................................................................ 174 Fields and Properties ................................................................................................................................. 175

Getting and Setting Fields Through Metadata................................................................................ 176


Getting Field Values ................................................................................................................................... 176 Getting Association Values ........................................................................................................................ 176 Setting Field Values .................................................................................................................................... 176

Performance and Tuning ............................................................................ 178


Controlling How Entities Load......................................................................................................... 179
Eager Loading............................................................................................................................................. 179 Fine Grained Control Using Named Aggregates ........................................................................................ 180

Controlling How Entity Data Loads ................................................................................................. 183 Understanding Named Aggregates ................................................................................................. 185
Visualising Aggregates ............................................................................................................................... 185

Bulk Updates and Deletes ............................................................................................................... 186


Bulk Updates .............................................................................................................................................. 186 Bulk Deletes ............................................................................................................................................... 187 Considerations for Bulk Operations ........................................................................................................... 187

Batching .......................................................................................................................................... 188


Customising the Batch Size ........................................................................................................................ 188 Identity Columns and Batching .................................................................................................................. 188 Databases and Batching ............................................................................................................................. 188

Mindscape LightSpeed User Guide

Caching ............................................................................................................................................ 189


Enabling Second Level Caching .................................................................................................................. 189 Configuring the Second Level Cache .......................................................................................................... 190 Understanding the Second Level Cache..................................................................................................... 191 Caching Considerations.............................................................................................................................. 191 Working with the Cache manually ............................................................................................................. 191

Database Hints ................................................................................................................................ 192


Index Hints ................................................................................................................................................. 192 Table Hints ................................................................................................................................................. 192 Database Hints Using Query Objects ......................................................................................................... 193

Measuring Performance ................................................................................................................. 194

Implementing Storage Policies with LightSpeed ......................................... 195


Entity Tracking ................................................................................................................................ 196
Storing Creation and Update Times ........................................................................................................... 196 Storing Creating and Updating Users ......................................................................................................... 196

Soft Deletion ................................................................................................................................... 197


Storing Which User Deleted an Entity ....................................................................................................... 197 Loading Soft Deleted Entities ..................................................................................................................... 197

Timestamps for Entity Tracking and Soft Deletion ......................................................................... 199


Built-In Timestamp Strategies .................................................................................................................... 199 Using a Custom Timestamp Strategy ......................................................................................................... 199

User Ids for Entity Tracking and Soft Deletion ................................................................................ 201
Built-In User Identification Strategies ........................................................................................................ 201 Using a Custom Identification Strategy ..................................................................................................... 201

Concurrent Editing .......................................................................................................................... 203


Guidance for Optimistic Concurrency Checking ........................................................................................ 203

Implementing Policies in Hand-Coded Entities ............................................................................... 204


Column Names for Policy Fields................................................................................................................. 204

Working with Legacy Databases................................................................. 205


Invoking Stored Procedures ............................................................................................................ 206 CRUD Stored Procedures ................................................................................................................ 207
CRUD Procedure Conventions ................................................................................................................... 207 CRUD Procedure Limitations ...................................................................................................................... 208

Using Natural Keys .......................................................................................................................... 209


Implementing the GeneratedId Method ................................................................................................... 209 Natural Keys and Column Mappings .......................................................................................................... 209 When the Natural Key is Assigned ............................................................................................................. 210 Natural Keys and the IdentityColumn Identity Method ............................................................................ 210

Using Composite Keys ..................................................................................................................... 211


Composite Key Types ................................................................................................................................. 211 Composite Keys in the Designer ................................................................................................................ 211 Composite Keys in Hand-Coded Entities .................................................................................................... 211 Assigning Composite Keys.......................................................................................................................... 212 Composite Foreign Keys............................................................................................................................. 213

Mindscape LightSpeed User Guide

Foreign Keys That Are Part of a Composite Key ......................................................................................... 214 Composite Foreign Keys That Overlap the Primary Key ............................................................................ 214 Many-to-Many Associations Represented As Composite Keys ................................................................. 215

Mapping Database Types to Domain Types.................................................................................... 216


Custom Wrappers ...................................................................................................................................... 216 Field Converters ......................................................................................................................................... 217 Field Converters for Hand-Coded Entities ................................................................................................. 219 Field Converters and Querying .................................................................................................................. 219 Querying Considerations for Field Converter Design................................................................................. 220

Working with Database Providers .............................................................. 221


DB2 .................................................................................................................................................. 222
LINQ Support.............................................................................................................................................. 222 Tools Support ............................................................................................................................................. 222

Firebird ............................................................................................................................................ 223


General ...................................................................................................................................................... 223 Tools Support ............................................................................................................................................. 223

MySQL ............................................................................................................................................. 224


Designer Support ....................................................................................................................................... 224 Creating Tables from the Designer ............................................................................................................ 224

Oracle .............................................................................................................................................. 225


Using Oracle Stored Procedures with LightSpeed ..................................................................................... 225 ODP.NET Versioning Considerations .......................................................................................................... 225 Designer Support ....................................................................................................................................... 225

PostgreSQL ...................................................................................................................................... 226


Designer Support ....................................................................................................................................... 226

SimpleDB ......................................................................................................................................... 227


Connection String Format .......................................................................................................................... 227 Limitations ................................................................................................................................................. 227 Eventual Consistency and Consistent Reads .............................................................................................. 227 Data Storage .............................................................................................................................................. 228 Tools Support ............................................................................................................................................. 228

SQLite .............................................................................................................................................. 229 SQL Server ....................................................................................................................................... 230


SQL Server 2008 Spatial Data Types .......................................................................................................... 230 SQL Server 2000 Limitations ...................................................................................................................... 230 SQL Server 2000 Designer Support ............................................................................................................ 230

SQL Server Compact........................................................................................................................ 231


Versions ..................................................................................................................................................... 231 Paging and Ordering .................................................................................................................................. 231 LINQ Support.............................................................................................................................................. 231 Tools Support ............................................................................................................................................. 231

VistaDB ............................................................................................................................................ 232


Versions ..................................................................................................................................................... 232 Paging and Ordering .................................................................................................................................. 232 LINQ Support.............................................................................................................................................. 232 Tools Support ............................................................................................................................................. 232

Mindscape LightSpeed User Guide

Low Level Database Access ............................................................................................................. 233


Creating ADO.NET Objects with LightSpeed .............................................................................................. 233 Using ADO.NET Objects with LightSpeed .................................................................................................. 233

Database Migrations .................................................................................. 234


Creating Migrations ........................................................................................................................ 235
Writing Migration Code ............................................................................................................................. 235 Creating Migrations from a Model ............................................................................................................ 235 Data Types ................................................................................................................................................. 236 Identity Generation.................................................................................................................................... 236

Running Migrations ......................................................................................................................... 237


Running Migrations from Visual Studio ..................................................................................................... 237 Running Migrations from the Command Line............................................................................................ 237 Running Migrations from Your Application ............................................................................................... 238

Creating SQL Scripts from Migrations ............................................................................................. 239


Choosing the Generation Settings ............................................................................................................. 239

Database Support ........................................................................................................................... 240

Appendices ................................................................................................ 241


Configuration Reference ................................................................................................................. 242 Tips, Tricks and Troubleshooting .................................................................................................... 244
Use a Short-Running Unit of Work............................................................................................................. 244 Use a Single LightSpeedContext................................................................................................................. 244 Use Configuration Files Where Possible .................................................................................................... 244 Partial Classes ............................................................................................................................................ 244 Use Eager Loading and Named Aggregates to Tune Loading Performance ............................................... 244 Avoid Needless Change Tracking ............................................................................................................... 245 Measure the Performance Impact of Changing UpdateBatchSize ............................................................ 245 Consider Changing IdentityBlockSize ......................................................................................................... 245 Keep Sequences in Sync with IdentityBlockSize ........................................................................................ 245 ObjectDisposedException in SystemTransactionCompletedEvent ............................................................ 245

Command Line Tools Reference ..................................................................................................... 247


lsgen Create Entity Classes from Database ............................................................................................. 247 lsmigrate Apply Migrations ..................................................................................................................... 248 Database Providers in Command Line Tools .............................................................................................. 248 Languages in Command Line Tools ............................................................................................................ 248

Database Driver Versioning ............................................................................................................ 249 LINQ Support Limitations ................................................................................................................ 250
Unsupported LINQ Operators .................................................................................................................... 250 Comparisons to an Associated Entity ........................................................................................................ 250 CLR Methods in a LINQ Query.................................................................................................................... 251 Joining, Grouping and Combining .............................................................................................................. 251 Database Specific Limitations .................................................................................................................... 252

Further Reading .............................................................................................................................. 253

Index ......................................................................................................... 254

Mindscape LightSpeed User Guide

10

Mindscape LightSpeed User Guide

11

Introduction

Mindscape LightSpeed User Guide

12

What is LightSpeed?
At its worst business logic can be very complex. Rules and logic describe many different cases and slants of behavior, and its 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

13

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

14

Old Hand?
If youre already experienced with LightSpeed, you can find out whats new and different in this version of LightSpeed from the Mindscape > LightSpeed > Whats New and Release Notes link on the Start menu.

Mindscape LightSpeed User Guide

15

Key Features
LightSpeeds 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 isnt 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 speed-bumps 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 entitys 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. 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

16

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

17

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 youre working on, and the relationships between them. This chapter describes how to build and evolve domain models using LightSpeed.

Mindscape LightSpeed User Guide

18

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 convention over configuration. The immediate practical benefit of this is that we dont 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.

Mindscape LightSpeed User Guide

19

Once weve 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

20

Creating Models with the Visual Designer


The LightSpeed visual designer integrates into Visual Studio 2008 and Visual Studio 2010. 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.

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.

Mindscape LightSpeed User Guide

21

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:

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 youll only need to think about as you start building up your model. For example, youll 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.

Mindscape LightSpeed User Guide

22

One option thats 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, youll 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.)

To add properties to the entity, right-click it and choose Add New Entity Property.

Mindscape LightSpeed User Guide

23

A shortcut for this is to hit the Insert key when the entity (or an existing property of the entity) is selected. You can edit the name of the new property by typing:

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

24

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

25

(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 doesnt already exist, LightSpeed may be able to create it for you. This depends on the database provider. For providers where LightSpeed cant create databases, youll need to use a suitable database administration tool to create a blank database.)

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.

Mindscape LightSpeed User Guide

26

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.

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 isnt 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 dont 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

27

(In this case, you dont 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 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 dont want these cluttering up the model.

Mindscape LightSpeed User Guide

28

Like Update Database, Update From Source is non-destructive. For example, if youve 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.

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

Mindscape LightSpeed User Guide

29

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 cant 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). 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

Mindscape LightSpeed User Guide

30

to get finer control over the through entity, see the chapter Working with Models in the Visual Designer.)

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, youll 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 dont need to use them. ### TODO: We probably need to discuss more about when you would, and how. ### When you use Update Database to create or update database tables, LightSpeed creates any required through tables, with suitable foreign keys.

Mindscape LightSpeed User Guide

31

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 fieldstypically 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 4.0 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

32

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. Youll 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 dont specify a constructor, the C# compiler supplies a public default constructor, but if you specify a non-default constructor, youll 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. Its 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 its essential to understand it. For example, it means you mustnt use C# automatic properties, 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.)

Mindscape LightSpeed User Guide

33

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

You dont have to provide a wrapper property and LightSpeed wont care if you dont. 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 cant find the reverse for an association. Furthermore, each

Mindscape LightSpeed User Guide

34

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. 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 doesnt do anything. Youll 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. The conventional property wrappers for a one-to-many association therefore look like this in code:

Mindscape LightSpeed User Guide

35

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

Mindscape LightSpeed User Guide

36

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: 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-to-many: each Contribution can have any number of ContributionTags and each Tag can also have any number of ContributionTags. Heres 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

37

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: 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.

Mindscape LightSpeed User Guide

38

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

39

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.

The following validation options are available: Validation Validate Email Validate Format Validate Length Description Ensures that the property contains a valid email address For strings, ensures that the string conforms to the supplied regular expression 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. 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. Ensures that the property value is unique ideal for email addresses, user

Validate Presence

Validate Unique

Mindscape LightSpeed User Guide

40

names, etc. See Validation Considerations below. Validate URI Validate Value Ensures that the property contains a valid URI 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). 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 right-clicking 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

41

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.

Mindscape LightSpeed User Guide

42

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 ValidateAttribute ValidateComparisonAttribute ValidateEmailAddressAttribute ValidateFormatAttribute ValidateLengthAttribute ValidatePresenceAttribute Description Used to specify a custom validation rule Compares the target field to another value, e.g. less than 100 Ensures that the property contains a valid email address For strings, ensures that the field conforms to the supplied regular expression For strings, ensures that the string length falls between the specified bounds 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. For numeric values, validates that the value is within the specified range Ensures that the property value is unique ideal for email addresses, user names, etc. See Validation Considerations below. Ensures that the property contains a valid URI

ValidateRangeAttribute ValidateUniqueAttribute ValidateUriAttribute

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

43

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.

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

44

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

45

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 youre 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. 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 wont change over the course of the

Mindscape LightSpeed User Guide

46

application, though they may of course be different for different instances of the application. Youll therefore normally create your LightSpeedContext object as a static or singleton object theres no benefit, and some disadvantages, to creating a new LightSpeedContext every time you need one.

Mindscape LightSpeed User Guide

47

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, well 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: Loading a LightSpeedContext from configuration
private static readonly LightSpeedContext _context = new LightSpeedContext("Test");

Mindscape LightSpeed User Guide

48

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 SqlServer2000 SqlServer2005 SqlServer2008 MySql5 PostgreSql8 SQLite3 Oracle9 Oracle9Odp VistaDB3 VistaDB4 SqlServerCE SqlServerCE4 DB2 AmazonSimpleDB Description Microsoft SQL Server 2000 using System.Data provider. Microsoft SQL Server 2005 using System.Data provider. Microsoft SQL Server 2008 using System.Data provider. MySQL 5 database using MySql.Data provider. PostgreSQL 8 database through Npgsql provider. SQLite 3 database through System.Data.SQLite provider. Oracle 9 (or higher) database through System.Data.OracleClient provider. Oracle 9 (or higher) database through Oracle.DataAccess provider. VistaDB 3 database through VistaDB.NET20 provider. This provider has been deprecated and is no longer officially supported. VistaDB 4 database through VistaDB.4 provider. SQL Server Compact 3.5 through System.Data provider. SQL Server Compact 4 through System.Data provider (requires additional supporting assembly see Working with Database Providers chapter). DB2 9.5 through IBM.DB2 provider. 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:

Mindscape LightSpeed User Guide

49

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

See the chapter Controlling the Database Mapping for more information on these options.

Mindscape LightSpeed User Guide

50

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 wont normally show the LightSpeedContext. Weve shown it here because, when you use LINQ, its important to remember to use the strong-typed generic version of LightSpeedContext.)

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.)

Mindscape LightSpeed User Guide

51

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. 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:

Mindscape LightSpeed User Guide

52

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

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 dont 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.

Mindscape LightSpeed User Guide

53

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

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

54

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 dont need a strong-typed unit of work: you just use the IUnitOfWork base interface. Consequently, you dont 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. 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 ||:

Mindscape LightSpeed User Guide

55

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 cant 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. 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).Limit(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:

Mindscape LightSpeed User Guide

56

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 works 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.

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 doesnt 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 LINQs 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

57

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 round-trips.

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. 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 the 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.

Mindscape LightSpeed User Guide

58

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.

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:

Mindscape LightSpeed User Guide

59

Saving the unit of work


unitOfWork.SaveChanges();

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

60

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.) 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.

Mindscape LightSpeed User Guide

61

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

62

Controlling the Database Mapping


When youre working with LightSpeed, youre 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 wouldnt need to know how or where that persistence happened. In reality, of course, this doesnt 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

63

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. Its 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.

Mindscape LightSpeed User Guide

64

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


### TODO: Graphic of a pair of entities with a couple of fields and an association, and the corresponding database mapping. ###

Mindscape LightSpeed User Guide

65

Overriding the Default Mapping


In greenfield development, its 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 isnt 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 dont 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 youre 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. 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 attributes IdColumnName. To specify the name of the foreign key column for an association, apply ColumnAttribute to the field containing the foreign key.

Mindscape LightSpeed User Guide

66

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 LightSpeeds, 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 tables primary key would be EmployeeId while the Customer tables 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: 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" />

Mindscape LightSpeed User Guide

67

Specifying the Hungarian convention in code


_context.NamingStrategy = new HungarianNamingStrategy();

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 SQLs ORDER BY keywords. To prevent errors in this case, set the quoteIdentifiers configuration attribute, or set LightSpeedContext.QuoteIdentifiers in code.

Mindscape LightSpeed User Guide

68

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).

Examples
Overriding persistence behaviour
[Transient] private EditStatus _editStatus; private readonly decimal _discountedPrice;

// neither loaded nor saved // loaded, but not saved

Mindscape LightSpeed User Guide

69

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 Sequence MultiSequence Guid GuidComb Identity Column Id Type Numeric Numeric Numeric GUID GUID 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 databases 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 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" />

Mindscape LightSpeed User Guide

70

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). 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

Mindscape LightSpeed User Guide

71

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. 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 Nilssons article The Cost of GUIDs as Primary Keys, to trade randomness for database INSERT performance. The COMB method generates GUIDs in a way that reduces the indexing cost when a new primary key

Mindscape LightSpeed User Guide

72

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 auto-increment 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 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

Mindscape LightSpeed User Guide

73

unit of work per Web request, even though the units of work shared the same LightSpeedContext object.

Mindscape LightSpeed User Guide

74

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 business-meaningful 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 propertys 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

75

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

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 dont 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 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 cant 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

76

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.

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.

Mindscape LightSpeed User Guide

77

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.

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

78

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, well 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

79

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. 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:

Mindscape LightSpeed User Guide

80

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 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).

Terminology
If youre 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

81

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 connectionStringName Usage 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. The type of database engine. See the Basic Operations chapter or the DataProvider enumeration for a list. 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. 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. The database schema in which the entity tables are found. Specifies how LightSpeed assigns Ids to new entities. See Identity Generation in the Controlling the Database Mapping chapter. 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.

dataProvider pluralizeTableNames

quoteIdentifiers

schema identityMethod loggerClass

All of these (and the other) configuration settings have equivalent properties on the LightSpeedContext class.

Mindscape LightSpeed User Guide

82

Localizing LightSpeed Messages


All of LightSpeeds 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. 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" />

Mindscape LightSpeed User Guide

83

Specifying a display naming strategy in code


_context.DisplayNamingStrategy = new ResourceLookupStrategy();

Mindscape LightSpeed User Guide

84

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

85

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

Using a Custom Connection Strategy


To use a custom connection strategy, set the UnitOfWork.ConnectionStrategy property to an instance of your custom strategy.

Mindscape LightSpeed User Guide

86

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

87

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

88

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

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.

Mindscape LightSpeed User Guide

89

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_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(); } }

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. 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.

Mindscape LightSpeed User Guide

90

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

91

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 { if (Member.IsValid) { return null; } return 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 describe the binding behaviour between form fields and data accessible to your page instance.

Mindscape LightSpeed User Guide

92

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

93

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
If you want to review some sample code, have a look at the Aptitude Test sample which is part of the Visual Studio 2008 samples solution.

Mindscape LightSpeed User Guide

94

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 would recommend that you leverage the LightSpeedControllerBase<TUnitOfWork> class which is provided as part of the Mindscape.LightSpeed.Web assembly as the base for your controllers as this will provide a per request scoped UnitOfWork which uses PerRequestUnitOfWorkScope<TUnitOfWork> and which will handle 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; } } }

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.

Mindscape LightSpeed User Guide

95

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. Included in the Mindscape.LightSpeed.Web assembly is a custom model binder called LightSpeedEntityModelBinder which can be used with you ASP.NET MVC projects for binding LightSpeed entities. To assist with the registration of this model binder to just handle the specific entity types in the solution you can register it by calling the 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); }

Mindscape LightSpeed User Guide

96

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. 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 describe the appropriate Data Annotation attributes needed to allow automatic validation to be applied by ASP.NET MVC. 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.

Mindscape LightSpeed User Guide

97

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. 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
If you want to review some sample code, have a look at the TV Guide MVC 2 sample which is part of the Visual Studio 2008 samples solution or the Deals MVC 3 sample which is part of the Visual Studio 2010 samples solution.

Mindscape LightSpeed User Guide

98

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. 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

99

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: 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.

Mindscape LightSpeed User Guide

100

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. 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

101

Mindscape LightSpeed User Guide

102

Building WPF and Windows Forms Applications


x

Mindscape LightSpeed User Guide

103

Unit Of Work Scoping


X

Mindscape LightSpeed User Guide

104

Entity Support for Rich Client Frameworks


X

Mindscape LightSpeed User Guide

105

Building Silverlight Applications


x

Mindscape LightSpeed User Guide

106

Using RIA Services with LightSpeed


X

Mindscape LightSpeed User Guide

107

Validation in RIA Services


X

Mindscape LightSpeed User Guide

108

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 3 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

109

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. Instantiating a DistributedUnitOfWork instance
// DistributedModelUnitOfWork is a strongly typed DistributedUnitOfWork instance var context = new LightSpeedContext<DistributedModelUnitOfWork>() { // Setting the UnitOfWorkFactory is required to generate DistributedUnitOfWork instances // The factory controls 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.

Mindscape LightSpeed User Guide

110

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 initiailize 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. 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
using (var unitOfWork = new LightSpeedContext<FilmFestival.Model.ModelUnitOfWork>("default") .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

111

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 with a 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. 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>

Mindscape LightSpeed User Guide

112

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. 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>

Mindscape LightSpeed User Guide

113

Example of creating a DistributedUnitOfWorkFactory with runtime configuration about the endpoint


var context = new LightSpeedContext<DistributedModelUnitOfWork>() { UnitOfWorkFactory = new DistributedUnitOfWorkFactory<DistributedModelUnitOfWork>(binding, address) };

Example of creating a DistributedUnitOfWorkFactory with a typed model


public interface IMyTypedUnitOfWork { System.Linq.IQueryable<Member> Members{ get; } } public class MyDistributedUnitOfWork : DistributedUnitOfWork, IMyTypedUnitOfWork { 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. 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.

Mindscape LightSpeed User Guide

114

Building WCF Services using Entities


If you dont 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 which is part of the Visual Studio 2008 samples solution. 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 which is part of the Visual Studio 2010 samples solution. The Website uses the DistributedUnitOfWork in a per request fashion as part of a stateless ASP.NET MVC web application. For a sample which demonstrates building a hand crafted WCF service which exposes entities then you should review the ATM sample which is part of the Visual Studio 2008 samples solution. 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

115

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.

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.

Mindscape LightSpeed User Guide

116

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();

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.

Mindscape LightSpeed User Guide

117

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. 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 code generated DTOs


As part of LightSpeed 3 we offered code generation templates which would generate you some 1:1 style DTOs and associated mapping helper methods. These have been deprecated as part of LightSpeed 4 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

118

Samples
If you wish to review a practical sample which uses Data Transfer Objects with LightSpeed then you should review the ATM sample which is part of the Visual Studio 2008 samples solution. 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

119

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

120

Unit Testing
### JD to do this bit ###

Mindscape LightSpeed User Guide

121

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" />

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.

Mindscape LightSpeed User Guide

122

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 applications 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 performance-oriented 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. A custom logger which uses the applications 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().

Mindscape LightSpeed User Guide

123

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 poorly-performing 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. 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 * } }

Mindscape LightSpeed User Guide

124

Using the Debugger Visualizer


LightSpeed includes a Visual Studio debugger visualizer which you can use to view the SQL statements generated by a LightSpeed query object. To install the visualizer for Visual Studio 2008: Copy Mindscape.LightSpeed.DebuggerVisualizer.dll to My Documents\Visual Studio 2008\Visualizers or (Program Files)\Microsoft Visual Studio 9.0\Common7\Packages\Debugger\Visualizers.

To install the visualizer for Visual Studio 2010: Copy Mindscape.LightSpeed.DebuggerVisualizer.dll to My Documents\Visual Studio 2010\Visualizers or (Program Files)\Microsoft Visual Studio 10.0\Common7\Packages\Debugger\Visualizers.

To use the visualizer: Start a debug session. Examine a LightSpeed Query object in a DataTip, a debugger variables window or the QuickWatch dialog. You will see a magnifying glass icon next to the text. Click on the magnifying glass icon to display the query SQL.

The debugger visualizer supports only query objects, not LINQ queries.

Mindscape LightSpeed User Guide

125

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

126

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 doesnt match any of the derived class discriminator values, it treats it as an instance of the root class. 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

Mindscape LightSpeed User Guide

127

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. ### TODO: picture of hierarchy and database mapping ### You dont 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: Designer: For each inheritance arrow in the hierarchy, set Inheritance Type to ClassTableInheritance.

Mindscape LightSpeed User Guide

128

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 reentering 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 dont need to load objects polymorphically in a single query.

Which Should I Choose?


If you need polymorphism but your derived classes dont 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 inheritance. Therefore, you should prefer single table inheritance over class table inheritance unless the derived classes add a lot of new columns. If you dont need polymorphism, you should use concrete table inheritance because it provides the best performance and most natural database mapping.

Mindscape LightSpeed User Guide

129

Value Objects
Many attributes of an entity can be represented by primitive values such as integers or strings. However, its 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.

Value Objects in Database-First Development


Relational databases dont have a concept of value objects, so when you drag a table onto the designer, LightSpeed cant 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

Mindscape LightSpeed User Guide

130

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. A simple value object type
public class Money { private readonly decimal _amount; public Money(decimal amount) { _amount = amount; } public decimal Amount { get { return _amount; } } }

// Fields should be read-only

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

Mindscape LightSpeed User Guide

131

Setting Value Object Properties


Value objects are immutable. This means you cannot modify the properties of the value object directly.
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 dont have identity.) Even if you created a mutable value object, you could not set properties this way, because value objects dont 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. 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.

Mindscape LightSpeed User Guide

132

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. Position.X No modifier Column Latitude Site.Location No modifier LocationX LocationLatitude Prefix Coordinates CoordinatesX CoordinatesLatitude

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 youd 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 hand-coded members. 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

133

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 dont change it. A transactional entity that is, one which is taking part in a unit of work may refer to reference data, and its 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 its 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 dont 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

134

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

135

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

136

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. 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 youve 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

Mindscape LightSpeed User Guide

137

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 dont have to! LightSpeed doesnt 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, its easier to stay in the database tool to make those changes, rather than jumping back to LightSpeed and breaking your flow. LightSpeed doesnt 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.

Reorganising Model Elements in a Database First Workflow


The relational database model doesnt 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 rightclicking 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

Mindscape LightSpeed User Guide

138

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 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 Many-toMany 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

139

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 doesnt 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: 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.

Mindscape LightSpeed User Guide

140

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 doesnt 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.) 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 its 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

141

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 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.

Create Partial Class Create Partial Classes Convert to Manual Implementation

Entity Model Property

Extract Interface

Entity

Mindscape LightSpeed User Guide

142

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 youre 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) 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. 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.)

Tag

Aggregate

You can optionally show additional entities which, although they dont match the filter themselves, are related to entities that do. This help you to see the filtered entities in context. Show Option Show selected Effect Shows only entities which match the filter.

Mindscape LightSpeed User Guide

143

Show associated

Show load graph

Show inheritance

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. 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

144

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. Dont 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 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 entitys 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.

Mindscape LightSpeed User Guide

145

Set the entitys 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 links 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

146

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

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

147

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, its recommended that you dont 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 youre 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 project-specific 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), youll see an extra entry here called LightSpeed Template File. Edit this to point to your copy of the main template (typically Base.vm):

Mindscape LightSpeed User Guide

148

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 wont automatically regenerate code when you change the template. Changes to the templates will only take effect next time you edit your model. If you dont 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, youll want to fold them into your custom templates. Its 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 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. Its sometimes useful to be able to specify other options which arent 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.

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.

Mindscape LightSpeed User Guide

149

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 returns an enum value, you will usually want to emit it qualified with the enum type name.
[DisplayName(Full Name)] [DisplayName("Full Name")] [Priority(High)] [Priority(Priority.High)] // would be an error // correct // would be an error // correct

Mindscape LightSpeed User Guide

150

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 Studios 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. 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 projects 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;

Mindscape LightSpeed User Guide

151

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.
<# 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

152

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. ### TODO: a picture would probably help here ### 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 theres no additional data associated with the relationship, and you dont 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:

If you have an explicit through entity and its 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 cant store that information on the Contribution or the Tag. The best place to store it is therefore on the through

Mindscape LightSpeed User Guide

153

entity which represents the contributiontag 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-to-many associations between the main entities and the through entity. This allows you to control details such as foreign key column mapping. 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 dont. 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

154

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 cant use custom attributes to apply LightSpeed attributes its usually more convenient to use the 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 youre 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.

Mindscape LightSpeed User Guide

155

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 youve 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.

Rearranging Properties
To rearrange entries in an entitys 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 youve 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.

Mindscape LightSpeed User Guide

156

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 entitys 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.

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 dont 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 Arent In the Properties Window


To set validation options that arent 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.

Mindscape LightSpeed User Guide

157

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, and how to exploit database-specific or user-defined 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

158

Full Text Search


### To be trasked ###

Mindscape LightSpeed User Guide

159

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 dont 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. 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 Servers (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:

Mindscape LightSpeed User Guide

160

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

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

Mindscape LightSpeed User Guide

161

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 functions 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 2008s 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 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

162

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 screens 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. 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:

Mindscape LightSpeed User Guide

163

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. 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 dont 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 } }

Mindscape LightSpeed User Guide

164

Views
Specify the ViewName property to run the query against a view instead of against the entitys normal backing table. See Working with Database Views in the chapter Controlling the Database Mapping for details.

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 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.

Mindscape LightSpeed User Guide

165

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");

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>();

Mindscape LightSpeed User Guide

166

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. 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

167

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

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

Mindscape LightSpeed User Guide

168

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.

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.

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

169

Subexpressions
When youre 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

170

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 clientsPayingLotsOfFreightForLittleValue = _unitOfWork.Project<FreightSummary>(query);

In LINQ, you can create sub expressions using the let keyword; no special APIs are required.

Mindscape LightSpeed User Guide

171

Working with Metadata


Most of the time when youre working with LightSpeed, youre 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

172

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.

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

173

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; var classInfo = order.EntityInfo(); // bring extension method into scope

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.)

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.)

Mindscape LightSpeed User Guide

174

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 PropertyName: OrderReference HumanName: Order Reference

Mindscape LightSpeed User Guide

175

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 wont 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.

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:

Mindscape LightSpeed User Guide

176

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

177

Performance and Tuning


LightSpeed makes it easy to work with your persistent data as .NET objects, abstracting away many details of your applications use of the database. In practice, however, no application layer can completely abstract away the database. To get good performance, its 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

178

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 its loaded and doesnt 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 doesnt do any work it doesnt 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 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.

Mindscape LightSpeed User Guide

179

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.) 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> _customer = 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 doesnt need to display the individual order lines, so it doesnt want to eager load

Mindscape LightSpeed User Guide

180

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. 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();

To eager load a particular aggregate using query objects, specify it in using the Query.AggregateName property:

Mindscape LightSpeed User Guide

181

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 customers 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

182

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 dont 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, youll 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 cant 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. 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

Mindscape LightSpeed User Guide

183

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 WithSecurityBlobs attribute will load all the employees on that site, including their SecurityPhoto fields.

Mindscape LightSpeed User Guide

184

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 dont 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 dont 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 dont 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 doesnt affect fields or associations that arent 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

185

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();

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();

Mindscape LightSpeed User Guide

186

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.

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

187

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.

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

188

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; However the second level caching 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 attribute. 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 however you may wish to enable it to improve performance of your application. If youre 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. 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 nonweb apps too). This cache is a simple in-process cache.

Mindscape LightSpeed User Guide

189

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 programatically
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.
[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.

Mindscape LightSpeed User Guide

190

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. 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.

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.

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

191

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 doesnt 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, its important to remember that hints are exactly that hints. The query planner doesnt 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.

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.

Mindscape LightSpeed User Guide

192

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

193

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

194

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

195

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 non-nullable 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. 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

196

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.

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.)

Mindscape LightSpeed User Guide

197

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

198

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

199

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

Mindscape LightSpeed User Guide

200

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

201

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

Mindscape LightSpeed User Guide

202

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 doesnt 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 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

203

Implementing Policies in Hand-Coded Entities


As indicated above, LightSpeeds 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

204

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 LightSpeeds 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

205

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

206

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.

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.

Mindscape LightSpeed User Guide

207

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 CRUD procedures. You will need to create and invoke specific stored procedures for any queries you want to use.

Mindscape LightSpeed User Guide

208

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 arent 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 dont 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, its worth evaluating whether you could change it over to use surrogate keys. Unfortunately, this often isnt 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 entitys 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. 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.

Mindscape LightSpeed User Guide

209

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.)

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

210

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. 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).

Mindscape LightSpeed User Guide

211

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

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.

Mindscape LightSpeed User Guide

212

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. 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

213

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 associations 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.

Mindscape LightSpeed User Guide

214

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 information about implementing this in LightSpeed, see the online article Many-to-many associations and composite keys in LightSpeed.

Mindscape LightSpeed User Guide

215

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 arent 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. 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:

Mindscape LightSpeed User Guide

216

Entity.Attribute("IsModerator") == "Y" Entity.Attribute("IsModerator") == true from m uow.Members where m.IsModerator == "Y" select m

// works // fails

// compiler error, cant 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 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 LightSpeeds 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:

Mindscape LightSpeed User Guide

217

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). 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

218

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. 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:

Mindscape LightSpeed User Guide

219

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

220

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 its 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

221

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

222

Firebird
General
Although Firebird capability still exists in LightSpeed 4, it is no longer actively maintained. New features are not tested with Firebird, and Firebird support may be removed in a future version of LightSpeed. In general, features beyond those described in the Basic Operations chapter, such as joins and groups, may not be implemented for Firebird.

Tools Support
The designer does not currently support dragging tables or views from Firebird. Migrations are not supported on Firebird.

Mindscape LightSpeed User Guide

223

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 designers 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

224

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>

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

225

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

226

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 Amazons 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. 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.

Mindscape LightSpeed User Guide

227

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. 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-0101T01: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.

Mindscape LightSpeed User Guide

228

SQLite
There are no known special considerations for SQLite.

Mindscape LightSpeed User Guide

229

SQL Server
SQL Server 2008 Spatial Data Types
To enable spatial data types at runtime, choose the SqlServer2008 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. You do not need to do anything to enable spatial data types at design time. The designer does not distinguish between SQL Server 2005 and SQL Server 2008. It will show SQL Server 2005 regardless of which version you are using, but you will still be able to use spatial data types.

### TODO: talk about how to map spatial data functions for use in queries ###

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 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. As noted above, the designer does not have separate providers for SQL Server 2005 and SQL Server 2008. Use the SQL Server 2005 option for SQL Server 2008.

Mindscape LightSpeed User Guide

230

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

231

VistaDB
Versions
Although VistaDB 3 capability still exists in LightSpeed 4, it is deprecated, and may be removed in a future release. We advise customers using VistaDB 3 to upgrade to VistaDB 4 if possible.

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

232

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 contexts 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 works connection, and enrols it in the unit of works 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

233

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 nextwhich might include schema changes such as widening or adding columns and/or data changes such as computing defaults for new columnsand 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 timebecause 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. LightSpeeds 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

234

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 AddTable DropTable RenameTable AddColumn AddForeignKeyColumn DropColumn ChangeColumn RenameColumn ExecuteNativeCommand Action Adds a new table to the database Drops a table from the database Changes the name of an existing table Adds a column to a table Adds a column with a foreign key constraint to a table Drops a column from a table Change the data type or nullability of a column Changes the name of a column Typical SQL Equivalent CREATE TABLE DROP TABLE (varies) ALTER TABLE ADD ALTER TABLE ADD ALTER TABLE DROP ALTER TABLE ALTER (varies)

Executes arbitrary SQL. You can use this to (as specified) make changes that are not supported through the migrations API, but it may make your migration code non-portable across database providers.

(SQL equivalents are approximate; the exact SQL syntax varies from database to database.)

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.

Mindscape LightSpeed User Guide

235

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.

Dont 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 dont 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!

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.

Mindscape LightSpeed User Guide

236

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 youd 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.

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.

Mindscape LightSpeed User Guide

237

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. Note that the migrations assembly has several dependencies which are not part of the normal LightSpeed redistributable.

Mindscape LightSpeed User Guide

238

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 youre connected to a database, select the target version which youd like to generate a SQL script for migrating to. If youre not connected to a database, select the start and end versions for which youd 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 youd like LightSpeed to work out the current version automatically each time you generate a script, choose Im connected to the database from this computer. Otherwise, choose Im 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 rerun the wizard.

Mindscape LightSpeed User Guide

239

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

240

Appendices

Mindscape LightSpeed User Guide

241

Configuration Reference
The following configuration settings are available through the LightSpeedContext class and the configuration file. Property (Configuration Attribute)
AuditInfoMode (auditInfo) AutoTimestampMode (autoTimestamps) Cache (cacheClass) CascadeDeletes (cascadeDeletes) CommandTimeout (commandTimeout) ConnectionString (connectionStringName) CustomAuditInfoStrategy CustomAutoTimestampStrategy DataProvider (dataProvider) DetectPropertyNames (detectPropertyNames) DisplayNamingStrategy

Description
How to generate user names for CreatedBy, UpdatedBy, etc. How to generate timestamps for CreatedOn, UpdatedOn, etc. Cache implementation for cached entities Whether delete operations cascade by default How long LightSpeed should allow commands to run before failing them. (Config entry is in seconds.) The database connection string. (Config entry refers to the <connectionStrings> section.) IAuditInfoStrategy for user names for CreatedBy, UpdatedBy, etc. IAutoTimestampStrategy for timestamps for CreatedOn, UpdatedOn, etc. The type of database. Enables the Entity.Set overload which does not take a property name. True by default. IDisplayNamingStrategy for localising property names for display in validation messages. IEntityFactory to be used when materialising entities. 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. How LightSpeed assigns Ids to entities. Additional configuration for the IdentityMethod

More Information
Implementing Storage Policies in LightSpeed Implementing Storage Policies in LightSpeed Performance and Tuning Basic Operations

Basic Operations

Implementing Storage Policies in LightSpeed Implementing Storage Policies in LightSpeed Basic Operations

Building Applications with LightSpeed

EntityFactory IdentityBlockSize (identityBlockSize)

Controlling the Database Mapping

IdentityMethod (identityMethod) IdentityMethodOptions

Controlling the Database Mapping Controlling the Database Mapping

Mindscape LightSpeed User Guide

242

Property (Configuration Attribute)


Logger (loggerClass) NamingStrategy (namingStrategyClass) PluralizeTableNames (pluralizeTableNames) QuoteIdentifiers (quoteIdentifiers)

Description
ILogger for SQL and debug logging INamingStrategy for database mapping.

More Information
Testing and Debugging Controlling the Database Mapping

Whether table names in the database use the Controlling the Database plural or singular form of the entity class Mapping name (e.g. Person table or People table). Whether to quote identifiers (e.g. table Basic Operations names) in generated SQL. Avoids conflicts with SQL reserved words, but can cause case sensitivity issues on some databases. The default database schema (can be overridden on a per-entity basis). The type of full-text search engine, if any. Location of full text search index file(s). Maximum number of update statements per command batch. Advanced Querying Techniques Advanced Querying Techniques Performance and Tuning

Schema (schema) SearchEngine (searchEngineClass) SearchEngineFileLocation (searchEngineFileLocation) UpdateBatchSize (updateBatchSize) UseMediumTrustCompatibility

Runs LightSpeed in a mode that is compatible Building Web Applications with ASP.NET medium trust, at the expense with LightSpeed of some performance. Shows additional detail in log displays. Testing and Debugging

VerboseLogging

Mindscape LightSpeed User Guide

243

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 isnt just a matter of concurrency or stale data, but storing a unit of work across requests is unreliable: for example a unit of work cant 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. Theres usually no need to have more than one unless youre 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 doesnt try to persist them. Never use automatic properties in a LightSpeed entity class the C# compiler generates a backing field which doesnt map to a database column, and you cant 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 socalled 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

Mindscape LightSpeed User Guide

244

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


Dont 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. Dont 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.) Dont increase UpdateBatchSize too far, and always measure the performance impact rather than just assuming that bigger is better!

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 youre 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 dont increase IdentityBlockSize beyond the point of diminishing returns.

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

Mindscape LightSpeed User Guide

245

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

246

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 comma-separated 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 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

Mindscape LightSpeed User Guide

247

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 MySql5 Oracle9 PostgreSql8 Sqlite3 SqlServer2000 SqlServer2005 SqlServerCE SqlServerCE4 VistaDB4 Database MySQL Oracle PostgreSQL SQLite Microsoft SQL Server 2000 Microsoft SQL Server 2005 or 2008 Microsoft SQL Server Compact 3.5 Microsoft SQL Server Compact 4 VistaDB Abbreviation mysql oracle pg sqlite sql2000 sql sqlce sqlce4 vdb4

Languages in Command Line Tools


The language argument for command line tools can take the following values: Value CSharp VisualBasic Language C# Visual Basic Abbreviation Cs vb

Mindscape LightSpeed User Guide

248

Database Driver Versioning


### TODO! ###

Mindscape LightSpeed User Guide

249

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

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.

Mindscape LightSpeed User Guide

250

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.) Other String members such as Length, Replace and Substring are generally supported on all databases except Firebird. Queries involving string concatenation may not be correctly handled on non-SQL 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 Firebird and 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.

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 Some grouping with ordering or paging queries on SQL Server and Oracle.

Mindscape LightSpeed User Guide

251

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

252

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

253

Index
Add method, 49 adding entities, 49 ADO.NET, 224 ADO.NET transaction, 52 aliasing, 156 application, 70 creating and upgrading the database, 229 association and database first development, 129 creating in code, 25 creating in the designer, 20 custom resolver, 205 eager loading, 170 Get method, 26 mapping to foreign key, 56 metadata for, 165 one-way, 125 properties, 20 reverse, 25 Set method, 26 attributes and fields, 25 auditing, 187 auto through entity, 21 automatic properties, 24 shunned, 235 batch size, 179 batching, 179 performance, 236 BeginTransaction method, 52 block allocation of Ids, 64 bulk operations, 177 cache, 180 bulk operations and, 178 manual access, 182 cascade delete, 50 class table inheritance, 119 code first development, 24 code generation, 139 T4 templates, 142 templates, 139 using designer extensions, 140 column loading computed, 60 mapping, 57 mapping to association, 56 mapping to field, 55 mapping to Id, 55 value object mapping, 123 Column Name option, 57 ColumnAttribute, 57 command line tools, 238 composite key, 202 many-to-many association, 206 concrete table inheritance, 120 concurrency, 194 configuration, 37, 39, 71, 233 file, 39, 71 lightSpeedContexts section, 39 connection string, 40 SimpleDB, 218 ConnectionStrategy class, 76 convention over configuration, 54 Convert to Manual Implementation command, 148 creation time, 187 timestamp, 190 cross join, 157 cross-cutting keys, 205 CRUD, 36 stored procedures for CRUD operations, 198 custom association resolver, 205 custom attributes in the designer, 146 custom mapping data type, 132 custom property setter, 148 data format, 207 data provider, 40 data type conversion, 208 converting in property code, 207 custom mapping, 132 database-specific, 131 designer, 131 spatial, 221 database, 40, 212 converting data, 207 creating in the designer, 16 hints, 183 index, 183 legacy, 196 mapping, 54 migrations, 225 querying, 42 saving changes, 49, 50 synchronising to model, 128 view, 66 database connection and unit of work, 76 custom strategy, 76 database first development, 18, 128 and inheritance, 129 one-to-one association, 129 reorganising domain model, 129

Mindscape LightSpeed User Guide

254

value object, 129 database synchronisation configuring, 130 database-defined type, 131 DB2, 213 debugger visualizer, 116 debugging logging, 113 delete bulk delete, 177 deleting entities, 50 cascade, 50 designer, 12, 126 code generation, 139 creating migrations, 226 defaults for creating entities, 138 enum, 131 extensibility, 140 filtering the view, 134 keyboard shortcut, 147 Model Explorer window, 127 PostgreSQL, 217 referencing an external class, 148 tips, 146 toolbox, 12 user-defined type, 131 detecting concurrent edits, 194 discriminator, 118 distinct query, 154 documentation in the designer, 23 Documentation command, 23 domain model, 9 capturing an image of, 146 creating from database, 18, 30 creating in code, 24 creating with the designer, 12 linked files, 136 synchronising to database, 128 viewing large models, 134 eager load viewing load graph in designer, 135 eager loading, 170 entity, 37 adding new, 49 bulk update or delete, 177 classes, 16 composite key, 202 creating from database, 30 creating in code, 24 creating in the designer, 12 defaults for new entity types, 138 deleting, 50 field loading, 174 Get method, 26 IsValid property, 31 loading strategy, 170

loading through stored procedure, 69 mapping to table, 55 mapping to view, 67 metadata for, 165 natural key, 200 OnValidate method, 34 renaming, 133, 148 saving creation and update details, 187 Set method, 25, 26 soft delete, 188 updating, 50 Validate method, 31 Entity.Attribute method, 46 EntityCollection, 25 EntityHolder, 25 enum, 131 extension properties, 140 external class reference, 148 features, 7 field excluding from save, 60 lazy loading, 174 load only, 60 mapping to column, 55 metadata for, 165 Set method, 25 transient, 60 vs. property, 24, 55 field converter, 132, 208 queries and, 210 Find method, 46 Firebird, 214 flush changes to database, 49, 50 foreign key composite, 204 composite overlapping primary key, 205 mapping, 57 mapping to association, 56 naming convention, 56 part of primary key, 205 full text search bulk operations and, 178 function invoking SQL functions, 151 GeneratedId method, 200 Get method, 26 Get Started command, 147 getter custom code in property, 148 custom property getter, 207 grouping using query objects, 158 GUID identity generation, 63 performance tradeoffs, 63 GuidComb, 63 hint, 183 IAssociationResolver interface, 205

Mindscape LightSpeed User Guide

255

id identity type, 14 Id, 61 assigning, 61 composite key, 202 mapping to column, 55 natural key, 200 IdColumnName, 57 identity column, 64 batching and, 179 Identity Column Name option, 57 identity generation, 61 block allocation, 64 identity map, 180 identity method, 61 per entity, 62 setting, 61 IdentityBlockSize, 236 for key table, 62 for sequences, 63 sequences and, 236 IdentityMethod enumeration, 61 IDisplayNamingStrategy interface, 74 IFieldConverter interface, 132, 208 ILogger interface, 114 INamingStrategy, 58 index hint, 183 inheritance, 118 choosing a mapping, 120 class table, 119 concrete table, 120 discriminator, 118 external base type, 148 hiding inheritance arrows, 147 moving properties between classes, 129 single table, 118 inner join, 157 InnoDB, 215 inserting entities, 49 intersection, 160 IsValid property, 31 iterative development, 128 IUnitOfWork, 37 IUnitOfWork interface, 46 IUnitOfWork.Add method, 49 IUnitOfWork.Project method, 154 IUnitOfWork.Remove method, 50 IUnitOfWork.SaveChanges method, 49, 50 join using query objects, 156 key table naming convention, 56 keyboard shortcut, 147 KeyTable identity generation, 62 block allocation, 64 keyword escaping, 59

large models, 134 large objects, 174 lazy loading, 174 legacy database, 196 LightSpeed, 4 LightSpeed Model Explorer, 127 LightSpeedContext, 37, 39, 71, 235 scope, 72 lightSpeedContexts section, 71 linked model, 136 LINQ, 42 invoking custom functions, 151 supported features, 241 loading controlling with named aggregates, 171 default, 170 eager, 170 localisation, 35, 74 logging, 113 custom logger, 114 logical delete. See soft delete lsgen, 30, 238 lsmigrate, 239 many to many association, 21 creating in code, 28 many-to-many association composite key, 206 hiding through entity, 130 many-to-many associations designer, 144 mapping, 54 column, 57 default, 55 non-default, 57 table, 57 value object, 123 metadata, 163 and field values, 167 Microsoft SQL Server. See SQL Server Migration class, 226 migrations, 225 creating SQL scripts, 230 running, 228 Model Explorer window, 127 model first development, 16, 128 MyISAM, 215 MySQL, 215 round-tripping policy, 130 N+1 problem, 170 named aggregate, 171 filtering designer view by, 134 named aggregates, 176 lazy loading fields using, 174 visualising, 176 naming convention column, 55 defining your own, 58

Mindscape LightSpeed User Guide

256

foreign key, 56 key table, 56 Oracle stored procedure results, 216 primary key, 55 table, 55 natural key, 200 NoReverseAssociationAttribute, 125 ObjectDisposedException, 236 ODP.NET could not load file or assembly, 216 one to many association, 20 one to one association, 21 one-to-one association and database first development, 129 OnValidate method, 34 optimistic concurrency, 194 OptimisticConcurrencyException responding to, 194 Oracle, 216 outer join, 157 paging using LINQ, 44 using query objects, 47 partial class creating, 133 performance, 169 measuring, 185 persistence, 60 load-only fields, 60 transient fields, 60 pluralization, 55 polymorphism, 120 PostgreSQL, 217 primary key composite key, 202 mapping, 57 naming convention, 55 natural key, 200 profiling, 115 Project method, 154 projections using query objects, 154 properties and metadata, 166 property creating in the designer, 14 custom getter and setter, 148, 207 localising property name, 74 renaming, 133, 148 Set method, 25 vs. field, 24, 55 query expression, 46 query object, 46, 154 subexpressions, 161 query objects vs. LINQ, 48 QueryExpression class, 46

querying, 42, 149 query expression, 46 using query objects, 46 quoting identifiers, 59 rapid application development, 128 refactoring, 133 reference data, 125 release notes, 6 reminder note, 147 Removemethod, 50 removing entities, 50 Rename command, 148 renaming entities and fields, 133 reserved word, 59 reverse association, 25 SaveChanges method, 49, 50 saving changes, 49, 50 transactional, 52 schema, 73 migrating database schema, 225 second level cache, 180 sequence identity generation, 62 block allocation, 64 options, 64 sequence per table, 63 Set method, 25, 26 setter custom code in property, 148 custom property setter, 207 SimpleDB, 218 single table inheritance, 118 soft delete, 188 sorting using LINQ, 44 using query objects, 47 spatial data SQL Server 2008, 221 SQL creating migration scripts, 230 invoking SQL functions, 151 logging, 113 timing, 115 SQL keyword escaping, 59 SQL Server, 221 SQL Server 2000, 221 SQL Server Compact, 222 SQLite, 220 stored procedure, 68 CRUD procedures, 198 Oracle, 216 subexpression using query objects, 161 surrogate key, 200 T4 templates, 142 table dragging into model, 18

Mindscape LightSpeed User Guide

257

mapping, 57 mapping to entity, 55 table hint, 183 Table Name option, 57 table per concrete class, 120 table per hierarchy, 118 table per subclass, 119 TableAttribute, 57 tag filtering designer view by, 134 through association, 21 auto through entity, 21 creating in code, 28 hiding through entity, 130 timestamp, 190 tips and tricks, 235 tracing, 113 track create time, 187 track update time, 187 transaction, 52 ADO.NET, 52 TransactionScope, 52 union, 160 unit of work, 37 adding entities, 49 bulk operations and, 178 class, 42 database connection, 76 deleting entities, 50 saving changes, 49, 50 short running vs. long running, 235 update bulk update, 177 update batching, 179 Update Database command, 16, 128 Update From Source command, 19, 128 update time, 187 timestamp, 190 UpdateBatchSize, 179, 236 updating entities, 50

user identifying, 192 track creating or updating, 187 track deleting user, 188 user-defined type designer, 131 Validate method, 31 validation, 31 advanced designer options, 32 customising automatic, 33 declarative in code, 34 designer, 31 localising messages, 35, 74 procedural, 34 whole object, 34 validation attributes and fields, 25 value object, 121 and database first development, 121, 129 editing values, 123 mapping, 123 view database, 66, 156 filtering designer view, 134 mapping to entity, 67 querying through, 66 saving designer filter, 135 VistaDB, 223 Visual Basic, 29 visual designer. See designer Visual Studio debugger visualizer, 116 designer, 126 mapping keyboard to designer, 147 migrations project, 226 running migrations from, 228 WithAggregate operator, 172, 176 XML documentation in the designer, 23

Mindscape LightSpeed User Guide

258

You might also like