Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Extensible Storage Engine wikipedia , lookup
Open Database Connectivity wikipedia , lookup
Microsoft SQL Server wikipedia , lookup
Microsoft Jet Database Engine wikipedia , lookup
Entity–attribute–value model wikipedia , lookup
Clusterpoint wikipedia , lookup
Relational model wikipedia , lookup
Mindscape LightSpeed User Guide 1 Contents Introduction........................................................................... 15 What is LightSpeed?............................................................................................ 16 New to LightSpeed? ............................................................................................ 17 Old Hand? ........................................................................................................... 18 Key Features........................................................................................................ 19 About This Book .................................................................................................. 21 Creating Domain Models ....................................................... 22 Objects and Databases........................................................................................ 23 LightSpeed and Object-Relational Mapping ............................................................... 23 Creating Models with the Visual Designer .......................................................... 25 Creating Entities in the Designer ................................................................................ 26 Using Entity Classes .................................................................................................... 30 Creating Database Tables from Entities ...................................................................... 30 Creating Entities from Database Tables ...................................................................... 33 Creating Associations Between Entities ...................................................................... 35 Many-to-Many Associations ....................................................................................... 37 XML Documentation ................................................................................................... 39 Creating Models in Code ..................................................................................... 40 Creating Entity Classes ................................................................................................ 40 Creating Associations Between Entities ...................................................................... 42 Many-to-Many Associations ....................................................................................... 45 Special Considerations for Visual Basic ....................................................................... 47 Creating Code Models from Database Tables ............................................................. 48 Validation ............................................................................................................ 49 Specifying Validation Criteria ...................................................................................... 49 Advanced Validation Options ..................................................................................... 50 Automatic Validations................................................................................................. 52 Declarative Validation in Code .................................................................................... 52 Overriding OnValidate ................................................................................................ 53 Validation Considerations ........................................................................................... 54 Localising Validation Messages ................................................................................... 54 Working with Entities.......................................................................................... 56 Mindscape LightSpeed User Guide 2 The Unit of Work......................................................................................................... 56 The LightSpeedContext Object ................................................................................... 57 LightSpeed Configuration Basics ......................................................................... 58 LightSpeed Configuration Section ............................................................................... 58 Loading a LightSpeedContext from Configuration ...................................................... 58 Specifying the Data Provider....................................................................................... 59 Specifying the Connection String ................................................................................ 60 Other Configuration Options ...................................................................................... 60 Querying the Database Using LINQ .................................................................... 62 Creating a Strong-Typed Unit of Work........................................................................ 62 Writing LINQ Queries Using C# Syntax........................................................................ 63 Writing LINQ Queries Using the Standard Query Operators ...................................... 63 Writing LINQ Queries Against Hand-Coded Entities ................................................... 64 Common LINQ Techniques ......................................................................................... 65 LINQ Expressions......................................................................................................... 67 Querying the Database Using Query Objects ..................................................... 68 Creating a Unit of Work .............................................................................................. 68 Query Expressions....................................................................................................... 68 Sorting and Paging ...................................................................................................... 69 Query Objects ............................................................................................................. 70 Single Entity Queries ................................................................................................... 70 Count Queries ............................................................................................................. 71 Choosing Between LINQ and Query Objects .............................................................. 71 Creating, Modifying and Deleting Entities .......................................................... 72 How Changes are Saved .............................................................................................. 72 Adding a New Entity.................................................................................................... 72 Updating an Existing Entity ......................................................................................... 73 Deleting an Existing Entity .......................................................................................... 74 Saving the Unit of Work .............................................................................................. 74 Transactions ........................................................................................................ 76 Using TransactionScope .............................................................................................. 76 Using ADO.NET Transactions ...................................................................................... 76 Automatic Transactions .............................................................................................. 77 Controlling the Database Mapping......................................... 78 Understanding the Default Mapping .................................................................. 79 Entity Classes Map to Tables ...................................................................................... 79 Entity Id Maps to the Id Column ................................................................................. 79 Fields Map to Columns ............................................................................................... 79 Mindscape LightSpeed User Guide 3 Associations Map to Foreign Key Columns ................................................................. 80 Id Values Come From KeyTable .................................................................................. 80 Default Mapping Example........................................................................................... 80 Overriding the Default Mapping ......................................................................... 82 Mapping Individual Tables and Columns .................................................................... 82 Mapping Individual Tables and Columns in the Designer ........................................... 82 Mapping Individual Tables and Columns in Code ....................................................... 83 Defining Your Own Mapping Convention ................................................................... 83 Reserved Words .......................................................................................................... 85 Overriding Persistence Behaviour....................................................................... 86 Excluding a Field From Persistence ............................................................................. 86 Excluding a Field From Being Saved ............................................................................ 86 Examples ..................................................................................................................... 87 Identity Generation............................................................................................. 88 Identity Methods in LightSpeed .................................................................................. 88 Setting the Identity Method Globally ......................................................................... 88 Overriding the Identity Method on a Per-Entity Basis ................................................ 89 KeyTable Identity Generation ..................................................................................... 89 Sequence and MultiSequence Identity Generation .................................................... 90 Guid and GuidComb Identity Generation ................................................................... 91 IdentityColumn Identity Generation ........................................................................... 92 Identity Generation Options ....................................................................................... 92 How Block Allocation Methods Work ......................................................................... 92 Working with Database Views ............................................................................ 94 Loading Entities Through a View................................................................................. 94 Creating an Entity Class to Map a View ...................................................................... 95 Invoking Stored Procedures ................................................................................ 97 Invoking a Stored Procedure Using LINQ .................................................................... 97 Invoking a Stored Procedure Using Query Objects ..................................................... 98 Database Considerations for Stored Procedures ........................................................ 99 Additional Support for Stored Procedures ................................................................. 99 Building Applications with LightSpeed ................................. 100 Configuration .................................................................................................... 101 How to Configure LightSpeed ................................................................................... 101 Loading the Configuration ........................................................................................ 102 Setting Up a LightSpeedContext in Code .................................................................. 102 Context Per Application, Not Context Per Request .................................................. 102 Terminology .............................................................................................................. 103 Mindscape LightSpeed User Guide 4 Configuration Settings .............................................................................................. 104 Localizing LightSpeed Messages ....................................................................... 105 Localizing Property Names........................................................................................ 105 Customising How LightSpeed Connects to the Database ................................. 107 Implementing a Custom Connection Strategy .......................................................... 107 Using a Custom Connection Strategy........................................................................ 109 Building Web Applications ................................................... 111 Building ASP.NET Web Forms Applications ...................................................... 112 Unit of Work Scoping ................................................................................................ 112 Validation .................................................................................................................. 113 Data Binding using EntityDataBinder ........................................................................ 115 Samples ..................................................................................................................... 117 Building ASP.NET MVC Applications ................................................................. 118 Unit of Work Scoping ................................................................................................ 118 Model Binding for LightSpeed Entities...................................................................... 119 Validation .................................................................................................................. 121 Samples ..................................................................................................................... 123 Running LightSpeed in Medium Trust............................................................... 124 ASP.NET Dynamic Data ..................................................................................... 125 Handling Associations ............................................................................................... 126 Validation .................................................................................................................. 126 Building WPF and Windows Forms Applications .................. 128 Unit Of Work Scoping........................................................................................ 129 Build with Design Patterns ........................................................................................ 129 Working with Dialogs ................................................................................................ 129 Concurrency .............................................................................................................. 130 Using Refresh Buttons .............................................................................................. 131 Long Running Units of Work ..................................................................................... 131 Entity Support for Rich Client Frameworks....................................................... 133 ClickOnce Deployment ...................................................................................... 134 Building Silverlight Applications ........................................... 135 Using WCF RIA Services with LightSpeed .......................................................... 136 Domain Service Basics............................................................................................... 137 Mindscape LightSpeed User Guide 5 Surfacing the Domain Entities................................................................................... 137 Generating and Using the Silverlight Data Source .................................................... 138 Building Distributed Applications ......................................... 140 Distributed Entity Programming ....................................................................... 141 The Distributed Unit of Work.................................................................................... 141 The DistributedUnitOfWorkService .......................................................................... 142 Manually Hosting the Service ................................................................................... 143 Supported Bindings ................................................................................................... 145 Configuring Your Client for a DistributedUnitOfWork .............................................. 145 Executing Operations at the Client ........................................................................... 147 Data Contracts .......................................................................................................... 147 Building WCF Services using Entities ........................................................................ 148 Samples ..................................................................................................................... 148 Building WCF Services using Data Transfer Objects ......................................... 150 Providing Custom Mapping for IUnitOfWork.Import ............................................... 152 Compatibility with LightSpeed 3 Generated DTOs ................................................... 153 Samples ..................................................................................................................... 154 Testing and Debugging ......................................................... 155 Unit Testing ....................................................................................................... 156 Using a Real Database ............................................................................................... 156 Using Fakes ............................................................................................................... 158 Using Mocks .............................................................................................................. 159 Logging .............................................................................................................. 161 Built-In Loggers ......................................................................................................... 161 Enabling Logging ....................................................................................................... 161 Disabling Logging ...................................................................................................... 162 Building a Custom Logger ......................................................................................... 162 Profiling ............................................................................................................. 164 Query Patterns .......................................................................................................... 164 Timing Queries .......................................................................................................... 164 Using a Custom Logger for Profiling.......................................................................... 164 Domain Modelling Techniques ............................................. 167 Inheritance ........................................................................................................ 168 Discriminators ........................................................................................................... 168 Single Table Inheritance ............................................................................................ 169 Class Table Inheritance ............................................................................................. 170 Mindscape LightSpeed User Guide 6 Implementing Common Services in a Base Class ...................................................... 171 Which Should I Choose? ........................................................................................... 171 Value Objects .................................................................................................... 173 Defining a Value Object Type in the Designer .......................................................... 173 Creating a Value Object Member in the Designer .................................................... 173 Value Objects in Database-First Development ......................................................... 174 Defining a Value Object Type in Code ....................................................................... 174 Creating a Value Object Member in Code ................................................................ 175 Setting Value Object Properties ................................................................................ 175 Value Object Database Mappings ............................................................................. 176 Reference Data and Lookups ............................................................................ 179 Working with Models in the Visual Designer ........................ 180 LightSpeed Model Explorer ............................................................................... 181 Workflows for Rapid Application Development ............................................... 182 Driving the Database from the Domain Model ......................................................... 182 Modelling Using Your Database Tools ...................................................................... 182 Which Should I Choose? ........................................................................................... 183 Reorganising Model Elements in a Database First Workflow ................................... 184 Configuring Database Synchronisation ..................................................................... 185 Enums and Other User-Defined Types.............................................................. 186 Adding an Enum Type to the Designer ..................................................................... 186 Adding a Database-Defined Type to the Designer .................................................... 186 Adding a User-Defined Type with Custom Mapping ................................................. 187 Using a User-Defined Type ....................................................................................... 188 Refactoring in the Designer .............................................................................. 189 Custom Views.................................................................................................... 190 Filtering ..................................................................................................................... 190 QuickViews................................................................................................................ 191 Linked Models ................................................................................................... 193 Code Generation ....................................................................................................... 193 Creating Associations Across Model File Boundaries ............................................... 193 Creating and Maintaining Entity Links ...................................................................... 194 Changing the Designer Defaults........................................................................ 195 Customising the Generated Code ..................................................................... 196 Using Your Own Code Generation Templates .......................................................... 196 Extending the Designer Metamodel ......................................................................... 198 Mindscape LightSpeed User Guide 7 Using T4 Templates with the LightSpeed Designer................................................... 200 Many-to-Many Associations ............................................................................. 203 Using an Auto Through Entity ................................................................................... 203 Using an Explicit Through Entity ............................................................................... 204 How Do Auto and Explicit Through Entities Differ? .................................................. 205 Designer Shortcuts and Tips ............................................................................. 206 Speeding Up Property Entry ..................................................................................... 206 Custom Attributes ..................................................................................................... 206 Grabbing an Image of Your Model ............................................................................ 207 Using Reminder Notes .............................................................................................. 207 Use Get Started for Configuration File Entries .......................................................... 207 Rearranging Properties ............................................................................................. 208 Assigning Keyboard Shortcuts to LightSpeed Commands ........................................ 208 Using a Custom Base Class for Your Entities ............................................................. 208 Changing Property or Entity Names When Other Code Already Uses Them ............ 209 Writing Custom Property Getter or Setter Code ...................................................... 209 Setting Validation Options Which Aren’t In the Properties Window ........................ 209 Show Data Command ............................................................................................... 210 Advanced Querying Techniques ........................................... 211 Full Text Search ................................................................................................. 212 Indexing Data ............................................................................................................ 212 Building the Search Index ......................................................................................... 213 Performing Searches ................................................................................................. 214 Advanced Operator Support ..................................................................................... 215 Further Search Reading............................................................................................. 215 Invoking SQL Functions ..................................................................................... 216 Mapping SQL Functions in LINQ................................................................................ 216 Mapping to a Custom Function................................................................................. 218 Mapping Argument Order ........................................................................................ 218 Mapping Member Functions .................................................................................... 218 Invoking SQL Functions Using Query Objects ........................................................... 219 Compiled Queries.............................................................................................. 220 Compiling a LINQ based query .................................................................................. 220 Compiling a query object based query ..................................................................... 220 Executing a compiled query ...................................................................................... 221 Parameterising compiled queries ............................................................................. 221 Exploring the Query Object............................................................................... 223 Basic Querying Operations ....................................................................................... 223 Mindscape LightSpeed User Guide 8 Controlling Entity Load ............................................................................................. 223 Projections ................................................................................................................ 223 Views ......................................................................................................................... 225 Controlling Aliasing ................................................................................................... 226 Joins .......................................................................................................................... 226 Grouping ................................................................................................................... 229 Subexpressions ......................................................................................................... 231 Unions and Intersections .......................................................................................... 232 Hints .......................................................................................................................... 232 Using direct SQL statements ..................................................................................... 232 Other Querying Properties ....................................................................................... 232 Subexpressions ................................................................................................. 233 Working with Metadata ....................................................... 235 The LightSpeed Metamodel .............................................................................. 236 Referencing the Metadata Assembly ........................................................................ 236 Getting Class Information ................................................................................. 237 Getting Class Settings ............................................................................................... 237 Getting Field and Association Information ............................................................... 238 Fields and Properties ................................................................................................ 238 Getting Validation Information ................................................................................. 239 Getting and Setting Fields through Metadata .................................................. 240 Getting Field Values .................................................................................................. 240 Getting Association Values ....................................................................................... 241 Setting Field Values ................................................................................................... 241 Performance and Tuning ...................................................... 242 Controlling How Entities Load........................................................................... 243 Eager Loading............................................................................................................ 244 Fine Grained Control Using Named Aggregates ....................................................... 245 Controlling How Entity Data Loads ................................................................... 248 Understanding Named Aggregates ................................................................... 251 Visualising Aggregates .............................................................................................. 251 Bulk Updates and Deletes ................................................................................. 252 Bulk Updates ............................................................................................................. 252 Bulk Deletes .............................................................................................................. 253 Considerations for Bulk Operations .......................................................................... 254 Mindscape LightSpeed User Guide 9 Batching ............................................................................................................ 255 Customising the Batch Size ....................................................................................... 255 Identity Columns and Batching ................................................................................. 256 Databases and Batching ............................................................................................ 256 Caching .............................................................................................................. 257 Enabling Second Level Caching ................................................................................. 257 Configuring the Second Level Cache ......................................................................... 258 Understanding the Second Level Cache.................................................................... 259 Collection Caching..................................................................................................... 260 Caching Considerations............................................................................................. 260 Working with the Cache Manually ............................................................................ 261 Database Hints .................................................................................................. 262 Index Hints ................................................................................................................ 262 Table Hints ................................................................................................................ 263 Database Hints Using Query Objects ........................................................................ 263 Measuring Performance ................................................................................... 264 Intercepting Queries ......................................................................................... 265 Intercepting ADO.NET interactions ........................................................................... 265 Intercepting LightSpeed queries ............................................................................... 266 Declaring how an entity type should be filtered....................................................... 266 Filtering by interface ................................................................................................. 267 Implementing a query filter ...................................................................................... 267 Injecting values into implicit filters ........................................................................... 269 Turning off filtering ................................................................................................... 269 Implementing Storage Policies with LightSpeed ................... 270 Entity Tracking .................................................................................................. 271 Storing Creation and Update Times .......................................................................... 271 Storing Creating and Updating Users ........................................................................ 271 Soft Deletion ..................................................................................................... 273 Storing Which User Deleted an Entity ...................................................................... 273 Loading Soft Deleted Entities .................................................................................... 274 Timestamps for Entity Tracking and Soft Deletion ........................................... 275 Built-In Timestamp Strategies ................................................................................... 275 Using a Custom Timestamp Strategy ........................................................................ 275 Database-side Timestamping .................................................................................... 277 User Ids for Entity Tracking and Soft Deletion .................................................. 278 Mindscape LightSpeed User Guide 10 Built-In User Identification Strategies ....................................................................... 278 Using a Custom Identification Strategy .................................................................... 278 Concurrent Editing ............................................................................................ 280 Guidance for Optimistic Concurrency Checking ....................................................... 280 Implementing Policies in Hand-Coded Entities ................................................. 282 Column Names for Policy Fields................................................................................ 282 Change Tracking ................................................................................................ 283 Saving Only Changed Fields ...................................................................................... 283 Working with Legacy Databases........................................... 284 Invoking Stored Procedures .............................................................................. 285 CRUD Stored Procedures .................................................................................. 286 CRUD Procedure Conventions .................................................................................. 287 CRUD Procedure Limitations ..................................................................................... 287 Using Natural Keys ............................................................................................ 289 Implementing the GeneratedId Method .................................................................. 289 Natural Keys and Column Mappings ......................................................................... 290 When the Natural Key is Assigned ............................................................................ 291 Natural Keys and the IdentityColumn Identity Method ........................................... 291 Using Composite Keys ....................................................................................... 292 Composite Key Types ................................................................................................ 292 Composite Keys in the Designer ............................................................................... 292 Composite Keys in Hand-Coded Entities ................................................................... 293 Assigning Composite Keys......................................................................................... 294 Composite Foreign Keys............................................................................................ 294 Foreign Keys That Are Part of a Composite Key ........................................................ 296 Composite Foreign Keys That Overlap the Primary Key ........................................... 296 Many-to-Many Associations Represented As Composite Keys ................................ 296 Mapping Database Types to Domain Types...................................................... 298 Custom Wrappers ..................................................................................................... 298 Field Converters ........................................................................................................ 299 Field Converters for Hand-Coded Entities ................................................................ 302 Field Converters and Querying ................................................................................. 303 Querying Considerations for Field Converter Design................................................ 303 Working with Database Providers ........................................ 305 DB2 .................................................................................................................... 306 Mindscape LightSpeed User Guide 11 LINQ Support............................................................................................................. 306 Tools Support ............................................................................................................ 306 MySQL ............................................................................................................... 307 Designer Support ...................................................................................................... 307 Creating Tables from the Designer ........................................................................... 307 Oracle ................................................................................................................ 308 Using Oracle Stored Procedures with LightSpeed .................................................... 308 ODP.NET Versioning Considerations ......................................................................... 308 Designer Support ...................................................................................................... 309 PostgreSQL ........................................................................................................ 310 Designer Support ...................................................................................................... 310 SimpleDB ........................................................................................................... 311 Connection String Format ......................................................................................... 311 Limitations ................................................................................................................ 311 Eventual Consistency and Consistent Reads ............................................................. 312 Data Storage ............................................................................................................. 312 Tools Support ............................................................................................................ 313 LightSpeed 5 Changes ............................................................................................... 313 SQLite ................................................................................................................ 314 SQL Server ......................................................................................................... 315 SQL Server 2008 ........................................................................................................ 315 SQL Server 2012 ........................................................................................................ 315 SQL Server 2000 Limitations ..................................................................................... 316 SQL Server 2000 Designer Support ........................................................................... 316 SQL Server Compact.......................................................................................... 317 Versions .................................................................................................................... 317 Paging and Ordering ................................................................................................. 317 LINQ Support............................................................................................................. 317 Tools Support ............................................................................................................ 317 VistaDB 4 ........................................................................................................... 318 Paging and Ordering ................................................................................................. 318 LINQ Support............................................................................................................. 318 Tools Support ............................................................................................................ 318 Low Level Database Access ............................................................................... 319 Creating ADO.NET Objects with LightSpeed ............................................................. 319 Using ADO.NET Objects with LightSpeed .................................................................. 319 Mindscape LightSpeed User Guide 12 Windows Azure Table Service ........................................................................... 320 Database Migrations ............................................................ 321 Creating Migrations .......................................................................................... 322 Writing Migration Code ............................................................................................ 322 Creating Migrations from a Model ........................................................................... 323 Create Migrations from Scratch ................................................................................ 324 Data Types ................................................................................................................ 324 Identity Generation................................................................................................... 324 Injecting Custom User Code...................................................................................... 324 Running Migrations ........................................................................................... 325 Running Migrations from Visual Studio .................................................................... 325 Running Migrations from the Command Line........................................................... 326 Running Migrations from Your Application .............................................................. 326 Creating SQL Scripts from Migrations ............................................................... 328 Choosing the Generation Settings ............................................................................ 328 Database Support ............................................................................................. 330 Appendices .......................................................................... 331 Configuration Reference ................................................................................... 332 Tips, Tricks and Troubleshooting ...................................................................... 335 Use a Short-Running Unit of Work............................................................................ 335 Use a Single LightSpeedContext................................................................................ 335 Use Configuration Files Where Possible ................................................................... 335 Partial Classes ........................................................................................................... 335 Use Eager Loading and Named Aggregates to Tune Loading Performance .............. 336 Avoid Needless Change Tracking .............................................................................. 336 Measure the Performance Impact of Changing UpdateBatchSize ........................... 336 Consider Changing IdentityBlockSize ........................................................................ 337 Keep Sequences in Sync with IdentityBlockSize ....................................................... 337 ObjectDisposedException in SystemTransactionCompletedEvent ........................... 337 Command Line Tools Reference ....................................................................... 338 lsgen – Create Entity Classes from Database ............................................................ 338 lsmigrate – Apply Migrations .................................................................................... 339 Database Providers in Command Line Tools ............................................................. 339 Languages in Command Line Tools ........................................................................... 340 LINQ Support Limitations .................................................................................. 341 Unsupported LINQ Operators ................................................................................... 341 Mindscape LightSpeed User Guide 13 Comparisons to an Associated Entity ....................................................................... 342 CLR Methods in a LINQ Query................................................................................... 342 Joining, Grouping and Combining ............................................................................. 343 Set operations ........................................................................................................... 343 Database Specific Limitations ................................................................................... 343 Further Reading ................................................................................................ 344 Index ................................................................................... 345 Mindscape LightSpeed User Guide 14 Introduction Mindscape LightSpeed User Guide 15 What is LightSpeed? At its worst business logic can be very complex. Rules and logic describe many different cases and slants of behavior, and it’s this complexity that objects were designed to work with. A Domain Model creates a web of interconnected objects, where each object represents some meaningful individual, whether as large as a corporation or as small as a single line on an order form. Martin Fowler LightSpeed is a framework that helps you to rapidly build persistent domain models for .NET applications. LightSpeed helps you to define classes and their relationships, and to conveniently load and save entities using a database, avoiding the need to write large amounts of boilerplate data access code. Using LightSpeed, you work at the level of business objects, not at the level of database rows. Mindscape LightSpeed User Guide 16 New to LightSpeed? Your LightSpeed installation includes the book Getting Started with LightSpeed, which walks you through the process of creating a domain model and using it in a simple application. You can view this using the Mindscape > LightSpeed > Guides > Getting Started link on the Start menu. The installation also includes a number of samples which will help you understand how to use specific features of LightSpeed and how to structure applications. You can launch the samples using the Mindscape > LightSpeed > Samples link on the Start menu. We have also posted a number of screencasts on the Mindscape Web site, which demonstrate the basics of LightSpeed and drill into a number of key features. Mindscape LightSpeed User Guide 17 Old Hand? If you’re already experienced with LightSpeed, you can find out what’s new and different in this version of LightSpeed from the Mindscape > LightSpeed > Guides > Upgrade Guide link on the Start menu. Mindscape LightSpeed User Guide 18 Key Features LightSpeed’s design philosophy is centered on the following guiding principles: Convention over configuration. Support idiomatic .NET domain models: validation, data binding, change notification etc. Highly usable API and low barrier to entry. Encapsulate and encourage best practice patterns: session per request, Unit of Work etc. Small, lightweight and fast. There isn’t room here to provide a full list of LightSpeed features, but among the key features are: Domain modelling. LightSpeed supports domain-driven design concepts such as entities and value objects, the Unit of Work pattern and aggregates. Visual model design. You can create models in a visual designer, making it easy to see the relationships between entities and reducing the need for hand coding. Models can be created from an existing database or from scratch using a toolbox. Rapid design iteration. The designer allows quick, non-destructive synchronisation of the database and the model, meaning there are no speedbumps as you evolve your model. LINQ. You can use the popular LINQ syntax and methods to perform LightSpeed queries, gaining the benefits of code completion, compiler type checking and so on. LightSpeed also provides a query object API for dynamic queries and finer control. Validation. You can specify validation rules at the entity or property level. LightSpeed automatically checks validity before allowing an entity to be saved. Each entity exposes an Errors collection which supports data binding for easy presentation. Eager and lazy loading. You can load an entity’s dependencies in the same database query as the entity, avoiding the infamous N+1 problem. You can also define your own eager load graphs for different situations. Mindscape LightSpeed User Guide 19 UI framework support. LightSpeed implements several standard UI integration interfaces for you, including IEditableObject, INotifyPropertyChanged and IDataErrorInfo, making it easy to use entities in data-driven user interfaces. Convention-based mapping. You can just create your classes without having to spend extra effort mapping them to a database schema. Safe, efficient data access. LightSpeed always uses parameters in database statements, avoiding the risk of SQL injection vulnerabilities. Statements are optimised and batched for efficiency. Multiple databases. You can use LightSpeed with Microsoft SQL Server, Oracle, MySQL, PostgreSQL, IBM DB2, SQLite, SQL Server Compact and VistaDB. It also works with the Amazon SimpleDB and Microsoft SQL Azure cloud databases. Database migrations. For manageable and controllable upgrading and downgrading of the database schema. Distributed application support. You can ship an object graph over the wire, work with it on a client and return the changes efficiently to the server without needing to leave the LightSpeed API. Mindscape LightSpeed User Guide 20 About This Book This book contains conceptual documentation and guidance information for LightSpeed. You should read it in conjunction with these other books: Getting Started with LightSpeed, which walks you through the process of creating a simple LightSpeed application LightSpeed API Reference, which provides detailed documentation for all LightSpeed classes and members You can access these books from the Mindscape > LightSpeed folder on the Start menu. Mindscape LightSpeed User Guide 21 Creating Domain Models The domain model is a conceptual model of a ‘domain of interest’ or problem domain. The domain model represents all the kinds of entities that are relevant to the system or application you’re working on, and the relationships between them. This chapter describes how to build and evolve domain models using LightSpeed. Mindscape LightSpeed User Guide 22 Objects and Databases When you analyse a business domain, you are creating a conceptual model of that domain. You identify the entities in that domain, the state and behaviour of those entities, and their relationships. However, at some point, that conceptual model has to be translated into a concrete software implementation. In fact, in almost all practical business applications, it has to be translated into (at least) two concrete software implementations: one implementation in terms of programming entities (objects), and one in terms of a relational database. This is where things start getting tedious and potentially complex, because the object and relational worlds use quite different representations. At best, the code to query the database, load objects and save them again is laborious and repetitive. More often, there are additional complications, such as multiple types of objects with associations between them – for example, a Customer has a set of Orders – or inheritance relationships – for example, we have identified AudioClip and VideoClip as sharing a considerable amount of state and behaviour, and would like to model this using inheritance. The extremely different representations of associations and inheritance in the object and relational worlds makes it complicated as well as laborious to write code that translates between the two representations. This is where object-relational mapping comes in. An object-relational mapper, or ORM, takes care of the mechanical details of translating between the worlds of programmatic objects and relational data. The ORM figures out how to load and save objects, using either explicit instructions such as an XML configuration file, or its own heuristics, or a combination of the two. This lets you, the programmer, focus on writing your business logic and application functionality against the domain model in its object representation, without having to worry about the details of the relational representation. LightSpeed and Object-Relational Mapping LightSpeed as an object-relational mapper leans strongly towards using its own heuristics to figure out how to load and save data: that is, it works out how objects and properties map to tables and columns without having to be told. This is known as Mindscape LightSpeed User Guide 23 convention over configuration. The immediate practical benefit of this is that we don’t need to write rules telling the ORM how to load and save objects. The impact is that it requires us to keep our object design and our database design reasonably in sync. However, even this has a higher-level benefit: it guides us towards a consistent data design that reflects the business domain. What does this mean in practical terms? It means that when we identify a domain entity, we always have the same basic tasks to wire it up in LightSpeed: Create a class representing the domain entity. Declare properties representing the attributes of the new entity. Create associations to other entities. Create the underlying database schema. Once we’ve defined our entity in this way, we can use LightSpeed to retrieve or persist instances of this entity to and from the database. We can also go on to configure behaviour (e.g. adding validation) and performance (e.g. caching or lazy-loading) as required, and to extend the domain model by adding custom methods. LightSpeed provides a visual designer for creating domain models and defining domain entities. The designer makes it easy to produce both the object and relational representations from a single master model, providing an extremely convenient modelling workflow. You can also define entities purely in code. For a step-by-step introduction to creating domain models and using them in LightSpeed, see the Getting Started guide, which you can find on the Start menu. Mindscape LightSpeed User Guide 24 Creating Models with the Visual Designer The LightSpeed visual designer integrates into Visual Studio 2008, Visual Studio 2010 and Visual Studio 2012. To create a model with the visual designer, add a LightSpeed Model item to your Visual Studio project. The easiest way to do this is to right-click the project in Solution Explorer and choose Add > New Item, then choose LightSpeed Model in the Add Item dialog. You can find it in the Data tab as well as the main list. The designer is initially blank (except for some links for users who want help getting started). You can either create entities in the designer using the Toolbox, or create entities from your existing database tables. Mindscape LightSpeed User Guide 25 Creating Entities in the Designer To create an entity in the designer, open the Visual Studio Toolbox and drag the Entity from the Toolbox onto the design surface. Initially, the new entity will be called Entity1. To change this, just type the new name while the entity is selected. The Visual Studio Properties window shows more options for configuring the entity: Mindscape LightSpeed User Guide 26 For example, you can also edit the entity name through the Name box in the Properties window. Many of the settings in the Properties window are things you’ll only need to think about as you start building up your model. For example, you’ll use the Persistence options if you need to customise the way the entity is stored in the database. This book covers the various options under the relevant chapters. You can also get an idea of what each option does by looking at the description area at the bottom of the Properties window. One option that’s important for all models is the Identity Type option. Every entity in LightSpeed has an Id, a unique identifier that allows LightSpeed to tell it apart from other entities of the same type. The default identity type is Int32 – the .NET Int32 type, equivalent to the C# int type. If you expect to create a huge number of entities, you’ll probably want to change this to Int64 (C# long). Some users prefer GUID Ids to numeric Ids, so you can also choose the Guid identity type. (The String identity type is usually used only for natural keys in legacy databases: see Working with Legacy Databases if you think you need string keys.) Mindscape LightSpeed User Guide 27 To add properties to the entity, right-click it and choose Add New Entity Property. A shortcut for this is to hit the Insert key when the entity (or an existing property of the entity) is selected. This will add a new entity after the entity that is currently selected. You can edit the name of the new property by typing: Mindscape LightSpeed User Guide 28 The default data type for newly created properties is String. Use the Properties grid to change this if necessary: You can also enter the type in front of the property name, just like declaring a C# field: When you save your model, LightSpeed generates .NET classes for each of the entities you have defined. These classes are stored in a generated code file with the same name as the model file and a .cs or .vb file extension. You can open this file in Visual Studio to view the entity class code, but you cannot edit the file – if you do, your changes will be overwritten next time LightSpeed regenerates the code. Always make changes through the designer, never in the generated code file. Mindscape LightSpeed User Guide 29 The generated entity classes are partial classes. This means you can extend them through your own partial class files, for example to add domain methods. Again, always do this through a partial class file, never by editing the generated code. Using Entity Classes As mentioned above, each entity in the designer is a .NET class. All entity classes inherit from the LightSpeed Entity<TId> class, which in turn inherits from the Entity base class. The Basic Operations chapter describes how to load, create, modify and delete entities, and the Getting Started book (linked on the Start menu) and screencast (linked from the designer surface and the Get Started command) provide walkthroughs. The properties you specify on the designer become properties of the class. You can get or set these properties using normal C# or Visual Basic syntax: Customer customer = /* get a customer – see Basic Operations chapter */; Console.WriteLine(customer.FirstName); customer.MemberNo = 12345; Creating Database Tables from Entities As discussed above, a domain model is realised in at least two ways: an object model and a relational database schema. The object model is automatically generated when you save the visual model file. You can also have LightSpeed generate the database schema for you. To do this, you must first tell LightSpeed what kind of database you are using, and provide the connection string to that database. To do this, click on the model background and enter these settings into the Properties window. Mindscape LightSpeed User Guide 30 (You only need to do this once – LightSpeed remembers the settings for future updates.) Now, if you right-click on the model background, you will see an Update Database option: Click on this and LightSpeed will compare your model to the database and display a list of changes that need to be made to the database. (If the database doesn’t already exist, LightSpeed may be able to create it for you. This depends on the database provider. For providers where LightSpeed can’t create databases, you’ll need to use a suitable database administration tool to create a blank database.) Mindscape LightSpeed User Guide 31 You can use Update Database to keep your database schema in sync with your model as your model evolves. Update Database does not regenerate tables each time, but instead applies only the changes it detects between the model and the database schema. Hence, it does not modify or delete existing data (except when it detects that, for example, a column needs to be deleted because it is no longer in the model) so it is safe to use with databases containing test data. It is not, however, a production tool! If you want to capture the changes that Update Database detects, so that you can run them against production environments in a controlled way, see the Database Migrations chapter. You can exclude an action proposed by Update Database by clearing the relevant checkbox. However, because the difference between the model and database remains, LightSpeed will continue to suggest the change until you reconcile the difference. Update Database allows you to very rapidly iterate your model. Because it is non-destructive it is easy to tweak the model incrementally until you are happy with it, try out experimental changes, and so on. For more information about this, see Workflows for Rapid Application Development in the chapter Working with Models in the Visual Designer. Mindscape LightSpeed User Guide 32 Creating Entities from Database Tables The previous sections assumed that you were creating a new model from scratch, and could begin from the domain model design. It may be that your database already exists, and you want to create a model that corresponds to that existing database schema. To do this, open the Visual Studio Server Explorer, and expand the database you want to model. (If the database isn’t already in Server Explorer, right-click Data Connections and choose Add Connection to add it.) Select the tables you want to include in your model, and drag them onto the design surface. When you drag tables from a database, you don’t need to specify the entity name and identity type, or the property names and data types, because these are already set up in the database through the tables and columns. LightSpeed works them out from the database schema. You can make changes to an entity which has been dragged from Server Explorer just as if you had created it using the Toolbox. For example, you can add new properties by choosing Add New Entity Property or using the Insert key. Of course, this means your entity class is now out of sync with your database, but you can use Update Database to fix that. Update Database works out what changes need to be made and applies them to the database for you. Mindscape LightSpeed User Guide 33 (In this case, you don’t need to have entered a database provider or connection string the way you did when you started from scratch. LightSpeed works them out from the Server Explorer connection.) If you want to keep the database as the master source for the model, you can use the Update From Source command instead of Update Database. Update From Source updates the model to be in sync with the database schema. For example, if you have added a column to a table, Update From Source will add a corresponding property to the entity. Note however that Update From Source only Mindscape LightSpeed User Guide 34 looks at existing entities – this is because a database may contain a huge number of tables not related to the task at hand, and you don’t want these cluttering up the model. Like Update Database, Update From Source is non-destructive. For example, if you’ve applied validations or renamed a property (provided you have kept the database column mapping), Update From Source will not overwrite your changes. Of course, if you have changed something that takes the model out of sync with the database, such as changing a property data type, then Update From Source will propose to change it back. As with Update Database, you can exclude an action proposed by Update From Source by clearing the relevant checkbox, but the action will continue to appear until you reconcile the difference. Creating Associations Between Entities LightSpeed supports three kinds of associations: one-to-many, one-to-one and many-to-many. To create a one-to-many association, select the One To Many Association connector in the toolbox, and drag an arrow from the ‘one’ end to the ‘many’ end. For example, if a Customer can have multiple Orders, drag the arrow from Customer to Order. Mindscape LightSpeed User Guide 35 This creates a property at each end of the association. The ‘one’ end has a collection property, representing the collection of associated ‘child’ entities. The ‘many’ end has a backreference property, representing the ‘parent’ entity. In the example above, Customer has an Orders collection property, and Order has a Customer backreference property. LightSpeed guesses names for these properties based on the entity names. You can edit these names by clicking on them in the diagram, or by selecting the arrow and editing the Collection Name and Backreference Name options in the Properties window. In code, you work with these properties in the same way as with other collection and object properties: // Using a collection property customer.Orders.Add(newOrder); customer.Orders.Remove(cancelledOrder); // Using an entity association order.Customer = requestingCustomer; A one-to-many association also results in a foreign key property. You can’t customise the name or type of this property, because the name is always the backreference name followed by “Id” – for example, CustomerId – and the type is always the identity type of the parent entity. You may sometimes use the foreign key property in your code, especially in serialisation scenarios, and you can customise its mapping to a database column if required (see Controlling the Database Mapping). Mindscape LightSpeed User Guide 36 A one-to-one association is added in much the same way as a one-to-many association, except that it has a source and a target instead of a collection and a backreference. When you use Update Database to create or update database tables, LightSpeed creates a foreign key column in the appropriate table to represent each association. If you are creating entities from database tables, then LightSpeed creates associations for you based on foreign keys in the database. Consequently, when you drag a table onto the designer, any columns that are foreign keys do not appear as properties – instead, they appear implicitly as the foreign keys of the inferred associations. Many-to-Many Associations Many-to-many associations work in a slightly different way to one-to-many or one-to-one associations, because they have to be stored in a different way at the relational level. Instead of a simple foreign key, a many-to-many association requires a whole table of foreign keys. This table is variously known as a join table, relationship table or through table. Each entry in the through table represents a pair of associated entities; and an entity can participate in multiple pairs. A many-to-many association is modelled in LightSpeed using a through association, so named because it goes ‘through’ an intermediate entity. The intermediate entity corresponds to the through table and is known as the through entity. To create a through association, select the Through Association connector in the toolbox, and drag an arrow between the entities you want to associate. You must then select the arrow and specify the through entity. The easiest way to do this is to enter a name in the Auto Through Entity box: LightSpeed will create a minimal through entity for you. (For more information about this and how to get finer control over the through entity, see the chapter Working with Models in the Visual Designer.) Mindscape LightSpeed User Guide 37 A through association results in two collection properties, one at each end. LightSpeed guesses names for these based on the names of the entities. When you use a through association from code, you’ll usually uses these two collections, in just the same way as normal collections – you can iterate over them, add items to them, remove items from them, and so on. Using a through association // iterating over the through association foreach (Tag tag in contribution.Tags) Console.WriteLine(contribution.Title + " is tagged " + tag.Value) // modifying the through association collection contribution.Tags.Add(penguinTag); contribution.Tags.Remove(dromedaryTag); // modifying the other end penguinTag.Contributions.Add(waddlingVideo); The through association also results in a through entity class and one-to-many associations from the ‘main’ entity classes to the through entity class (which in turn manifest in code as collection, backreference and foreign key properties). These are visible in code, but most applications don’t need to use them. You’ll typically only work with through entities for diagnostics, or if you want to associate further data with each pairing in the many-to-many (for example, a ‘tagged by’ field). Mindscape LightSpeed User Guide 38 When you use Update Database to create or update database tables, LightSpeed creates any required through tables, with suitable foreign keys. XML Documentation Your model can include documentation for entities, properties, one-to-many associations and stored procedures. Documentation will be emitted as XML documentation comments which are displayed in Intellisense or can be built into a Help file using a tool such as Sandcastle. To view or edit documentation, right click on the designer and choose Documentation. The LightSpeed Documentation window is displayed. Depending on the selected entity, this will display different fields—typically Summary, Remarks and Additional. Enter your documentation into these fields. The Documentation window tracks your selection in the same way as the Properties window. For most fields, LightSpeed will generate the required documentation tags (e.g. <summary>) for you. However, for fields marked (XML), you must include the container tags yourself. This allows you to generate elements such as <exception> which are not directly represented in LightSpeed. LightSpeed supports documentation for most common model elements, but not for some less frequently used elements. Please visit the support forum (linked from the Start menu) if you need to document a model element which does not currently support documentation. Mindscape LightSpeed User Guide 39 Creating Models in Code The visual designer is an extremely convenient way to create domain models, and most LightSpeed developers use it for almost all modelling requirements. You can also build a LightSpeed model in code. Even if you use the designer for most tasks you may sometimes want to drop down to the code level, for example to add some logic to a property setter. In fact, at run time a LightSpeed model is just code. Even if you only use the visual designer, the actual runtime model is the generated code from the designer. The easiest way to understand LightSpeed coding conventions is to sketch out a model in the designer, then examine the generated code. You’ll see that it follows a very regular pattern, and you can copy that pattern in hand-written code. Creating Entity Classes To create an entity class, define a .NET class which inherits from Entity<TId>. The TId type parameter is the identity type of the class. For example, to define a Customer class whose Id is an integer, write: public class Customer : Entity<int> { } Entity classes must have a public default constructor. If you don’t specify a constructor, the C# compiler supplies a public default constructor, but if you specify a non-default constructor, you’ll need to provide a default constructor as well. (See below for Visual Basic considerations.) The persistent state of an entity is defined by its fields. It’s very important to understand that LightSpeed is interested in fields, not properties! The designer blurs this distinction, because in most entities, every field is wrapped by a property and every property wraps a persistent field. But when you hand-write code it’s essential to understand it. For example, it means you mustn’t use C# automatic properties, Mindscape LightSpeed User Guide 40 because the backing field for automatic properties is compiler-generated and will have the wrong name: public class Customer : Entity<int> { public string Surname { get; set; } } // Error – backing field will NOT be named Surname Instead, you must explicitly create a field with the right name. (LightSpeed also permits the underscore prefix.) A persistent field in LightSpeed public class Customer : Entity<int> { private string _surname; // LightSpeed ignores underscore prefix } You can then create a wrapper property so that application code can access the persistent value. The property getter can just return the field value, but the property setter must call the Entity.Set method. The Set method is important because it is how LightSpeed knows that the field has changed. This is essential for knowing that at entity needs to be saved, and to support application interfaces such as IEditableObject and INotifyPropertyChanged. A wrapper property for a persistent field public class Customer : Entity<int> { private string _surname; public string Surname { get { return _surname; } set { Set(ref _surname, value); } } } Mindscape LightSpeed User Guide 41 You don’t have to provide a wrapper property and LightSpeed won’t care if you don’t. LightSpeed only cares about the field, and about the Set method being used to modify it. Because LightSpeed cares only about fields, not properties, LightSpeed attributes – for example, validation attributes, or attributes that control the database mapping – must go on fields rather than properties. (The compiler will warn you if you make a mistake.) Creating Associations Between Entities In the designer, a one-to-many association is represented as a single arrow, which results at the code level in a collection, a backreference and a foreign key property. In code, you need to create all of these fields – and, normally, wrapper properties – explicitly. To represent a collection, use a field of type EntityCollection<T>; to represent an entity reference, use a field of type EntityHolder<T>. (Foreign keys are just normal scalar fields, typically of type int or Guid.) The collection and holder fields should be marked readonly, and initialised to new collection and holder instances. Association fields always come in pairs. If the Customer class defines a collection of Orders, then the Order class must define a reference to Customer. These pairs are reverse associations. LightSpeed will throw an exception at runtime if it can’t find the reverse for an association. Furthermore, each EntityHolder<T> must be matched with a foreign key field, whose name is the same as the EntityHolder<T> field followed by Id. For example, if the Order class has a holder named _customer, it must have a scalar field named _customerId. Mindscape LightSpeed User Guide 42 A one-to-many association therefore looks like this in code: Representing a one-to-many association public class Customer : Entity<int> { private readonly EntityCollection<Order> _orders = new EntityCollection<Order>(); } public class Order : Entity<int> { private readonly EntityHolder<Customer> _customer = new EntityHolder<Customer>(); private int _customerId; } When application code accesses an association property, you need to ensure that the association is loaded. To do this, call the Get method. This loads the association – the collection or the associated entity – if required. If the association is already loaded, Get doesn’t do anything. You’ll usually call Get from a property getter. Application code can update a collection using the Add and Remove methods. To update an entity reference, you must call the Set method. Set updates the contents of the EntityHolder<T> and updates other information such as the foreign key field. Mindscape LightSpeed User Guide 43 The conventional property wrappers for a one-to-many association therefore look like this in code: Property wrappers for a one-to-many association public class Customer : Entity<int> { private readonly EntityCollection<Order> _orders = new EntityCollection<Order>(); public EntityCollection<Order> Orders { get { return Get(_orders); } } } public class Order : Entity<int> { private readonly EntityHolder<Customer> _customer = new EntityHolder<Customer>(); private int _customerId; public Customer Customer { get { return Get(_customer); } set { Set(_customer, value); } } public int CustomerId { get { return _customerId; } set { Set(ref _customerId, value); } } } Remember, as with simple fields, LightSpeed cares only about fields, not properties, so you’re not forced to follow this pattern or even to expose association properties at all. One-to-one associations are implemented in the same way as one-to-many associations, except that you use an EntityHolder<T> at both ends. The usage of the EntityHolder<T> is the same, and there must be a foreign key field at one end. Normally, LightSpeed can match associations up with their reverse associations because there is only one association between any two classes. If you have multiple associations between the same pair of classes, you must use ReverseAssociationAttribute to pair them up. Mindscape LightSpeed User Guide 44 public class Customer : Entity<int> { [ReverseAssociation("Customer")] private readonly EntityCollection<Order> _orders = new EntityCollection<Order>(); } public class Order : Entity<int> { [ReverseAssociation("Orders")] private readonly EntityHolder<Customer> _customer = new EntityHolder<Customer>(); private int _customerId; } Many-to-Many Associations As mentioned above, a many-to-many association in LightSpeed is represented by a through association. A through association is implemented in terms of a one-to-many association to a through entity and a many-to-one association from the through entity to the target entity. For example, suppose you want to model a many-to-many association between Contribution and Tag entities. You would need a through entity, which we will call ContributionTag, and one-to-many associations from Contribution to ContributionTag and Tag to ContributionTag. Most through entities contain nothing except the associations to the entities being linked, which are represented as the EntityHolder<T> ends of one-to-many associations. As usual the foreign key fields are required as well. So the ContributionTag entity would look like this: Mindscape LightSpeed User Guide 45 Representing a through entity in code public sealed class ContributionTag : Entity<int> { private int _contributionId; private int _tagId; private readonly EntityHolder<Contribution> _contribution = new EntityHolder<Contribution>(); private readonly EntityHolder<Tag> _tag = new EntityHolder<Tag>(); // Wrapper properties omitted for brevity. Wrapper properties are optional // and could be left out if you do not expect to work directly with through // entities. } Conversely, Contribution and Tag each have a reverse association which is one-tomany: each Contribution can have any number of ContributionTags and each Tag can also have any number of ContributionTags. Here’s the relevant fragment of Contribution: The underlying one-to-many association for a through association public class Contribution : Entity<int> { private readonly EntityCollection<ContributionTag> _contributionTags = new EntityCollection<ContributionTag>(); // Wrapper property omitted } You would define a similar association from Tag to ContributionTag. Finally, you can now implement the through association. This is a field of type ThroughAssociation<TThrough, TTarget>. The through association needs to be initialised with the EntityCollection representing the one-to-many association from the source entity (Contribution) to the through entity (ContributionTag). To load the through association, call the Get method. As with other associations, this is usually done in the property getter. Mindscape LightSpeed User Guide 46 Implementing a through association public class Contribution : Entity<int> { private ThroughAssociation<ContributionTag, Tag> _tags; public ThroughAssociation<ContributionTag, Tag> Tags { get { if (_tags == null) { _tags = new ThroughAssociation<ContributionTag, Tag>(_contributionTags); } return Get(_tags); } } } Again, the Tag entity will contain similar code for its Contributions through association. The through entity is a true entity in the model, so you can use it to hang data and behaviour relating to the association itself. For example, to track who applied a particular tag to a particular contribution, you could put an AddedBy field on the ContributionTag entity. Special Considerations for Visual Basic A nice feature of .NET is the great interop between components written in different languages. LightSpeed is no exception and can be used just as easily from Visual Basic as from C#. However, when hand-coding entities in Visual Basic, there is one minor difference, which is due to Visual Basic classes initialising in a slightly different order from C# classes. The difference is that you must manually call the LightSpeed Initialize method from the entity constructor, as follows: Mindscape LightSpeed User Guide 47 Writing an entity constructor using Visual Basic Public Class Status Inherits Entity(Of Integer) Public Sub New() MyBase.New(False) Initialize() End Sub End Class You only need to do this if you are hand-coding entities. If you use the designer then it is taken care of for you. Another small wrinkle is that Get and Set, the LightSpeed Entity methods, are reserved words in Visual Basic, and must therefore be escaped with square brackets: Calling the Get and Set methods from Visual Basic Public Property StatusName() As String Get Return [Get](_statusName) ' note square brackets around Get End Get Set(ByVal value As String) [Set](_statusName, value) ' note square brackets around Set End Set End Property Again, if you use the designer, it will take care of this for you. Creating Code Models from Database Tables If you have an existing database schema, and you would prefer to develop your entities in code rather than using the designer, you can use the lsgen command-line tool to create C# or Visual Basic classes from your database. See the Appendices for instructions on using lsgen. Mindscape LightSpeed User Guide 48 Validation LightSpeed provides a rich, extensible object-level validation framework. An entity may be validated by calling the Entity.Validate method or querying the Entity.IsValid property. Validation errors are exposed at the entity-level through the bindable Entity.Errors collection. Custom validation may be performed by overriding the Entity.OnValidate method. Objects are always validated before they are saved to the database and a ValidationException will be raised if an attempt is made to save an invalid object. Specifying Validation Criteria To specify validation criteria for a property, select the property, go to the Properties window and enter the required validations. Mindscape LightSpeed User Guide 49 The following validation options are available: Validation Description Validate Email Ensures that the property contains a valid email address. In LightSpeed 5, this now supports emails with + and ‘ characters. Validate Format For strings, ensures that the string conforms to the supplied regular expression Validate Length For strings, validates the string length. Write <= n if the string must be no longer than n characters, >= n if the string must be at least n characters, and n - m if the string must be between n and m characters. Validate Presence Ensures that a value has been provided for the property. For numeric types, this means the value is non-zero; for strings, it means the value is not null or the empty string. Validate Unique Ensures that the property value is unique – ideal for email addresses, user names, etc. See Validation Considerations below. Validate URI Ensures that the property contains a valid URI Validate Value For numeric values, validates the property value. You can specify a range using the format min - max, or a comparison using the =, !=, <, >, <= and >= operators, which have the same meanings as in C# (e.g. <= n to ensure that the value is less than or equal to n). Advanced Validation Options If you need more fine control over validation than you can enter in the Properties window, you can use the LightSpeed Model Explorer to manage individual validation attributes. LightSpeed Model Explorer provides the following additional capabilities: Additional options such as the Allow Empty String option on PresenceValidation or the URI Kind or Is Required options on URI Validation. Custom error messages (select the validation and edit its Custom Error in the Properties window). Simplified editing user interface for Length and Range validations (separate entries in Properties window for maximum and minimum). Mindscape LightSpeed User Guide 50 Simplified editing for Comparison validations (drop-down list of operators). Support for custom validations. (Enter the validation attribute as you would like it emitted in the model code, excluding the surrounding brackets. For example, MyValidation(123). Note that, depending on the contents of the attribute declaration, this may make your model language-specific.) To open the LightSpeed Model Explorer, choose View > Other Windows > LightSpeed Model. Then expand the tree view to show the property whose validation you want to customise, and open its Validations folder. You can then modify validations through the Properties grid, and add them by rightclicking the property and choosing Add New validation_type Validation. In particular, to add a custom validation, choose Add New Custom Validation. Mindscape LightSpeed User Guide 51 Note that some validation options in the Properties window may map to different underlying validation objects in the tree view. For example, a Validate Value expression may be implemented as a Range Validation or a Comparison Validation. Automatic Validations LightSpeed infers several validations automatically based on the underlying model. Association presence validation – ensures that any required associations are present. LightSpeed infers this based on whether the association foreign key field is nullable. DateTime range validation – ensures that any DateTime fields fall within a range acceptable for the database at hand. These validations are built-in and therefore result in built-in messages. To override the message, you must apply a declarative validation in code (see below). Use the ValidateAttribute with a rule type of PresenceAssociationValidationRule or ProviderDateRangeValidationRule as appropriate, and specify your custom message on the ValidateAttribute. Declarative Validation in Code For hand-coded entities or fields, you can specify validation by applying attributes to the field. (Remember LightSpeed attributes go on fields, not properties.) Simple declarative validation [ValidateLength(0, 50)] [ValidatePresence] private string _firstName; The following attributes are available: Attribute Description ValidateAttribute Used to specify a custom validation rule Mindscape LightSpeed User Guide 52 Attribute Description ValidateComparisonAttribute Compares the target field to another value, e.g. less than 100 ValidateEmailAddressAttribute Ensures that the property contains a valid email address ValidateFormatAttribute For strings, ensures that the field conforms to the supplied regular expression ValidateLengthAttribute For strings, ensures that the string length falls between the specified bounds ValidatePresenceAttribute Ensures that a value has been provided for the property. For numeric types, this means the value is non-zero; for strings, it means the value is not null or the empty string. ValidateRangeAttribute For numeric values, validates that the value is within the specified range ValidateUniqueAttribute Ensures that the property value is unique – ideal for email addresses, user names, etc. See Validation Considerations below. ValidateUriAttribute Ensures that the property contains a valid URI Note that the designer merges some of these attributes (for example, the Validate Value option can map to either ValidateComparisonAttribute or ValidateRangeAttribute depending on the particular validation). Overriding OnValidate To perform more complicated validation, such as whole entity validation logic, override the Entity.OnValidate method. Report errors by adding them to the Errors collection. Mindscape LightSpeed User Guide 53 Overriding OnValidate protected override void OnValidate() { if ((Contributor == null) && (ApprovedBy == null)) { Errors.AddError("Must have one or the other"); } } Validation Considerations When using the Validate Unique validation (or the ValidateUniqueAttribute), you should be aware that: It incurs a COUNT(*) query against the database each time the validation runs. The designer will create a unique constraint on the column which should make this fast, but you should still be aware that it can cause a lot of queries during, for example, a bulk import. The check is performed against the database, not against other in-memory entities. For example, if you create two new entities with the same value, and save them in the same unit of work, you can bypass the uniqueness validation. Again, a unique constraint at the database level will catch this, but it will result in a database exception rather than a validation exception. In LightSpeed 5, unique validation is scoped to the level which declares the field, not the level of the entity which is being validated at that time. Localising Validation Messages LightSpeed validation messages are intended to be human-readable. If you need to display messages in a language other than English, or just to replace the messages with your own messages, you can do so by creating a satellite resource DLL and/or a display naming strategy. For more information, see Localization in the Building Applications with LightSpeed chapter. Mindscape LightSpeed User Guide 54 Basic Operations The core operations of LightSpeed are the familiar CRUD (Create, Read, Update, Delete) database operations. The CRUD model lies behind the majority of database-backed applications and Web sites. This chapter shows you how to implement CRUD using LightSpeed. Mindscape LightSpeed User Guide 55 Working with Entities All persistent data is represented in LightSpeed as entities. An entity represents a business object with identity. It can be as big as a company or as small as a single order line. Of course, an entity can have associations to other entities, allowing an aggregate to represent a rich business domain. An entity can be loaded, modified and saved, and retains its identity throughout its lifecycle. The Unit of Work When you work with entities, you normally do so as part of a unit of work. The unit of work pattern is described as: Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. In LightSpeed, a unit of work represents a business transaction in progress. It tracks the entities involved in that business transaction, takes care of loading entities from the database if required, and provides a way to save any changes made in the course of the business transaction. Roughly speaking, a unit of work is a collection of entities and their pending database operations. A unit of work is represented in LightSpeed as the IUnitOfWork interface. IUnitOfWork provides operations to load, add and remove entities, and to save pending changes. The basic process of working with entities in LightSpeed is therefore very simple: Begin a unit of work. Perform whatever combination of loads, adds, updates and removals are required for the business function you’re implementing. Save any pending changes in the unit of work. End the unit of work. During this process, the unit of work object automatically: Tracks loaded entities in an identity map. Mindscape LightSpeed User Guide 56 Tracks entity state changes, so that it knows which entities if any need to be saved. Updates change tracking and versioning information if required. Connects to the database as and when required. The LightSpeedContext Object A LightSpeedContext object represents the configuration and connection settings for a particular database instance. A LightSpeedContext is like a souped-up connection string: in addition to the physical connectivity information of the connection string, the context also specifies the database engine, mapping conventions, logging behaviour and several other options. Most applications require only one LightSpeedContext, representing the application database and the way the application maps to that database. Those settings won’t change over the course of the application, though they may of course be different for different instances of the application. You’ll therefore normally create your LightSpeedContext object as a static or singleton object – there’s no benefit, and some disadvantages, to creating a new LightSpeedContext every time you need one. Mindscape LightSpeed User Guide 57 LightSpeed Configuration Basics As described above, the LightSpeedContext object contains the configuration and connection settings for a particular database instance. The usual way to store these settings is in the application configuration file – web.config for Web applications and sites, appname.exe.config for desktop applications – though you can also set them in code. LightSpeedContext supports a lot of configuration options, which we describe more fully in the relevant sections of this user guide, and summarise in the Appendices. In this section, we’ll describe the minimal configuration for connecting to a database. LightSpeed Configuration Section When you store settings in the application configuration file, you must declare and implement a lightSpeedContexts section, and define your specific settings within that section using the add element, as follows: Declaring the lightSpeedContexts section <configSections> <section name="lightSpeedContexts" type="Mindscape.LightSpeed.Configuration.LightSpeedConfigurationSection, Mindscape.LightSpeed" /> </configSections> Implementing the lightSpeedContexts section <lightSpeedContexts> <add name="Test" /> </lightSpeedContexts> <!-- We will add more here shortly --> Loading a LightSpeedContext from Configuration To load a LightSpeedContext from configuration, pass the name of the configuration file entry to the LightSpeedContext constructor: Mindscape LightSpeed User Guide 58 Loading a LightSpeedContext from configuration private static readonly LightSpeedContext _context = new LightSpeedContext("Test"); Specifying the Data Provider LightSpeed needs to know what kind of database you are using. Specify this using the dataProvider attribute: Specifying the data provider <lightSpeedContexts> <add name="Test" dataProvider="SQLite3" /> </lightSpeedContexts> The following values are recognised for dataProvider: Provider Name Description SqlServer2000 Microsoft SQL Server 2000 using System.Data provider. SqlServer2005 Microsoft SQL Server 2005 using System.Data provider. SqlServer2008 Microsoft SQL Server 2008 using System.Data provider. SqlServer2012 Microsoft SQL Server 2012 using System.Data provider. MySql5 MySQL 5 database using MySql.Data provider. PostgreSql8 PostgreSQL 8 database through Npgsql provider. PostgreSql9 PostgreSQL 9 database through Npgsql provider. SQLite3 SQLite 3 database through System.Data.SQLite provider. Oracle9 Oracle 9 (or higher) database through System.Data.OracleClient provider. Oracle9Odp Oracle 9 (or higher) database through Oracle.DataAccess provider. Mindscape LightSpeed User Guide 59 Provider Name Description VistaDB4 VistaDB 4 database through VistaDB.4 provider. SqlServerCE SQL Server Compact 3.5 through System.Data provider. SqlServerCE4 SQL Server Compact 4 through System.Data provider. DB2 DB2 9.5 through IBM.DB2 provider. WindowsAzureTableService Windows Azure Table storage provider. AmazonSimpleDB Amazon SimpleDB cloud database or compatible. The default provider is SqlServer2005 (Microsoft SQL Server 2005). Specifying the Connection String Connection strings are stored in the <connectionStrings> element as per the standard .NET configuration file schema. When configuring LightSpeed you can refer to a connection string by name using the connectionStringName attribute: Specifying the connection string <lightSpeedContexts> <add name="Test" connectionStringName="Sample" /> </lightSpeedContexts> <connectionStrings> <add name="Sample" connectionString="Data Source=sample.db3" /> </connectionStrings> Other Configuration Options Other common configuration options include: pluralizeTableNames identityMethod quoteIdentifiers Mindscape LightSpeed User Guide 60 See the chapter Controlling the Database Mapping for more information on these options. Mindscape LightSpeed User Guide 61 Querying the Database Using LINQ The LightSpeed designer declares a strong-typed unit of work class that exposes properties representing queries for different types of entity. To query the database using LINQ, we need to create a unit of work of this special type, associated with our specified configuration settings. We can then issue queries against it using the normal LINQ syntax. Creating a Strong-Typed Unit of Work To create a strong-typed unit of work, we use the LightSpeedContext<TUnitOfWork> class, where TUnitOfWork is the strong-typed unit of work class, and call CreateUnitOfWork on that context. Because the unit of work class implements IDisposable, this should normally be done in a using statement. Creating a strong-typed unit of work for use with LINQ public class Program { private static readonly LightSpeedContext<StoreUnitOfWork> _context = new LightSpeedContext<StoreUnitOfWork>("Test"); public static void UseUnitOfWork() { using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { // Do work here } } } (In future we won’t normally show the LightSpeedContext. We’ve shown it here because, when you use LINQ, it’s important to remember to use the strong-typed generic version of LightSpeedContext.) Mindscape LightSpeed User Guide 62 Writing LINQ Queries Using C# Syntax The strong-typed unit of work exposes properties named after your entities. You can write LINQ queries against these properties using the built-in C# LINQ syntax. (The Visual Basic syntax is similar.) Querying the database using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { var todaysOrders = from o in unitOfWork.Orders where o.OrderDate >= DateTime.Today select o; Console.WriteLine("Number of orders: " + todaysOrders.Count()); foreach (var order in todaysOrders) Console.WriteLine("Order reference: " + order.OrderReference); } When you write a LINQ query against a LightSpeed query property, the query is translated to SQL and executed on the database. For example, the LINQ where clause is translated to a SQL WHERE clause. This means processing is efficient – for example, LINQ does not bring back all Order entities and filter them on the client. Writing LINQ Queries Using the Standard Query Operators You can also write LINQ queries against the strong-typed unit of work using the LINQ extension methods or standard query operators. Mindscape LightSpeed User Guide 63 Querying the database using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { var todaysOrders = unitOfWork.Orders .Where(o => o.OrderDate >= DateTime.Today); Console.WriteLine("Number of orders: " + todaysOrders.Count()); foreach (var order in todaysOrders) Console.WriteLine("Order reference: " + order.OrderReference); } Writing LINQ Queries Against Hand-Coded Entities If you use the visual designer, it creates a strong-typed unit of work class for you. If you are writing entity classes by hand, you must create the strong-typed LINQ queries yourself. To do this, call the Query<T> extension method on IUnitOfWork: Hand coding a LINQ query using Mindscape.LightSpeed.Linq; // Bring extension methods into scope // Method 1: Declare your own strong-typed unit of work class public class StoreUnitOfWork : UnitOfWork { public IQueryable<Order> Orders { get { return this.Query<Order>(); } } } // Method 2: Call Query explicitly on a weak-typed unit of work IUnitOfWork unitOfWork; // weak typed var todaysOrders = unitOfWork.Query<Order>() .Where(o => o.OrderDate >= DateTime.Today); Mindscape LightSpeed User Guide 64 Common LINQ Techniques To filter a query – that is, to tell LightSpeed which entities you want to return – use the where keyword or the Where extension method. To sort a query, use the orderby keyword or the OrderBy extension method. Sorting is in ascending order by default: the orderby keyword allows you to specify the descending modifier, which corresponds to the OrderByDescending method. The orderby keyword supports sorting on multiple attributes; additional attributes correspond to the ThenBy or ThenByDescending method. var recentOrders = from o in unitOfWork.Orders where o.CustomerId == customerId orderby o.OrderDate descending select o; To perform paging of a query, use the Skip and Take extension methods. If you don’t also specify an order, either explicitly in the LINQ query or implicitly on the entity class, Skip and Take order entities by Id. You can combine Skip and Take if you want to page through a result set. var ordersToDisplay = unitOfWork.Orders .OrderBy(o => o.OrderDate) .Skip(pageStart) .Take(pageCount); To work with the entities returned from a LINQ query, use the foreach keyword to iterate over the query, or use the ToList extension method to load the results into a list. If you are only interested in a single entity, apply the First or Single extension method to obtain it. First returns the first matching entity, ignoring any others; Single checks that there is only one matching entity. Mindscape LightSpeed User Guide 65 List<Order> allOrders = unitOfWork.Orders.ToList(); Order order = unitOfWork.Orders.Single(o => o.Id == orderId); If you want to know how many entities fit the query criteria, apply the Count extension method. If you want to know if any entities fit the query criteria, apply the Any extension method. int pendingOrderCount = unitOfWork.Orders .Where(o => o.Status == OrderStatus.Pending) .Count(); To perform a projection – that is, to select only a subset of the entity fields – use the select keyword or the Select extension method. If you perform a projection, then you will typically project into a non-entity type, and the data will not be associated with the unit of work or cached in the identity map, and changes to the object will not be saved when the unit of work is flushed. This is therefore typically used for presenting partial, read-only information about an entity. var orderSummaries = from o in unitOfWork.Orders select new { OrderId = o.Id, o.OrderReference }; To bulk remove based on a query use the Remove extension method in the Mindscape.LightSpeed.Linq namespace. This allows Remove by Query to be specified using a simple expression, such as: Mindscape LightSpeed User Guide 66 Remove using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { unitOfWork.Orders.Remove(); unitOfWork.SaveChanges(); } using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { unitOfWork.Orders.Where(o => o.Id > 20).Remove(); unitOfWork.SaveChanges(); } To target database views you can use the .WithViewName extension method in the Mindscape.LightSpeed.Linq namespace to specify the view which the query is to operate over. All of these methods are translated to SQL so that LightSpeed does not waste time and bandwidth pulling back unwanted rows or columns. For example, if you specify Take(5) then LightSpeed will limit the number of rows returned to 5; if you specify Count() then LightSpeed issues a SQL COUNT query rather than materialising entities on the client. See also Advanced Querying Techniques later in this book. LINQ Expressions LINQ allows you to write queries of arbitrary complexity. LightSpeed handles only queries that can be translated to SQL on the database at hand. Consequently, if you write complex queries, you may encounter NotSupportedException at runtime. This indicates that LightSpeed was not able to translate the LINQ query to SQL. Consider simplifying the query, and performing further operations on the client. You can use the ToList() and AsEnumerable() operators to partition work between the database and the client. For known limitations on what LINQ expressions LightSpeed can translate to SQL, see the Appendices. Mindscape LightSpeed User Guide 67 Querying the Database Using Query Objects LightSpeed offers another way of querying the database, using query objects. In the query objects API, instead of writing your query using the LINQ syntax or operators, you construct objects that represent the query specification, and pass them to an appropriate method on IUnitOfWork. IUnitOfWork also provides some handy overloads to make common queries more convenient. Creating a Unit of Work When you use query objects, you don’t need a strong-typed unit of work: you just use the IUnitOfWork base interface. Consequently, you don’t need to use the generic LightSpeedContext, and can instead use the non-generic base context class. Creating a unit of work for use with query objects public class Program { private static readonly LightSpeedContext _context = new LightSpeedContext("Test"); public static void UseUnitOfWork() { using (IUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { // Do work here } } } Query Expressions The most commonly used query object is the QueryExpression object. You can pass a QueryExpression directly to IUnitOfWork.Find<T> to load entities by criteria, like the LINQ Where operator. To create a QueryExpression, use the Entity.Attribute static method to represent the attribute you want to query on, then apply comparison operators such as ==, <, and so on. Mindscape LightSpeed User Guide 68 Using a QueryExpression to query by criteria IUnitOfWork unitOfWork; // weak typed IList<Order> orders = unitOfWork.Find<Order>( Entity.Attribute("CustomerId") == customerId); You can combine query expressions using Boolean operators such as && and ||: IList<Order> orders = unitOfWork.Find<Order>( Entity.Attribute("CustomerId") == customerId && Entity.Attribute("OrderDate") == DateTime.Today); Query expressions also support the In, Like and Between methods for criteria that can’t be represented using the built-in operators: IList<Order> orders = unitOfWork.Find<Order>(Entity.Attribute("CustomerId").In(1, 3, 5)); Query expressions support traversal into associated entities using the dot syntax: IList<Order> orders = unitOfWork.Find<Order>(Entity.Attribute("Customer.Name") == "Bob"); Sorting and Paging To sort and page a query, pass Order and Page objects to the Find<T> method. You can construct these objects using static methods and fluent builder methods on the Order and Page classes. Mindscape LightSpeed User Guide 69 Using Order and Page to select a specific range of entities IList<Order> orders = unitOfWork.Find<Order>( Entity.Attribute("CustomerId") == customerId, Order.By("OrderDate"), Page.Offset(20).LimitTo(10)); Query Objects In some cases you need to specify additional querying options over and above the criteria, sort order and paging. In these cases, you must create a Query object and pass this to Find. The Query object allows you to specify projections, perform full text searches and customise entity load graphs. Specific functions are covered in the relevant sections of this user guide, or see the Query object in the API reference. Single Entity Queries The Find method returns a list of entities. If you expect a query to return only a single entity, you can use the FindOne method to avoid the overhead of extracting that entity from the list: Order order = unitOfWork.FindOne<Order>(Entity.Attribute("OrderReference") == orderRef); If you want to look up an entity by Id, there is a special FindById method: Order order = unitOfWork.FindById<Order>(123); Always use FindById for identity lookups, because it tries the lookup in the unit of work’s identity map first, and queries the database only if the lookup fails. This greatly improves efficiency if the entity is already part of the unit of work. Mindscape LightSpeed User Guide 70 Count Queries If you only need to know how many entities meet your query criteria, without bringing back those entities, you can use the Count method instead of Find. long orderCount = unitOfWork.Count<Order>(new Query( Entity.Attribute("Customer.Name") == "Bob")); The Count method doesn’t have an overload that allows you to pass a QueryExpression; you must pass a full Query object. You can pass a QueryExpression in the Query constructor. Choosing Between LINQ and Query Objects Because LightSpeed offers both LINQ and Query-based APIs for queries, you may wonder which you should choose. In general, most developers prefer LINQ, because it is tightly integrated into C# and Visual Basic – for example providing Intellisense support – and is familiar from LINQ to Objects or other data access technologies such as LINQ to SQL. LINQ also makes it much easier to write group and join queries, and to perform projections through its convenient initialisation and anonymous type syntax. However, in some cases LINQ’s static nature makes it unsuitable. For example, if the user can choose a field to filter on, then it may be easier to pass that field name to Entity.Attribute than to construct a LINQ query at run time. In addition, because LINQ queries are translated into query objects, LINQ incurs a small translation overhead, though this is normally insignificant compared to the cost of the database query. Mindscape LightSpeed User Guide 71 Creating, Modifying and Deleting Entities The previous sections show how to query the database using LightSpeed. In many applications you will also want to save changes to the database – adding new entities, modifying or deleting existing ones. As with querying, LightSpeed supports these operations through the unit of work. How Changes are Saved LightSpeed saves changes to a unit of work, not an individual entity. When you add, modify or delete an entity, it is not saved immediately. Instead, the unit of work just notes that the entity needs to be saved. When you call IUnitOfWork.SaveChanges(), LightSpeed saves all the entities that need it. This means that you can coordinate the persistence of multiple related changes, and minimises the number of database roundtrips. Adding a New Entity To add an entity to the unit of work, call IUnitOfWork.Add. The entity itself can be created however you like, typically using the new operator or a factory method. Creating a new entity and adding it to the unit of work Order order = new Order { OrderReference = orderRef }; unitOfWork.Add(order); If a new entity is associated with another entity that is already part of a unit of work, it automatically becomes part of the same unit of work. This saves you having to remember to add the entity to the unit of work separately. Any kind of association will trigger this. Mindscape LightSpeed User Guide 72 Creating a new entity which implicitly becomes part of the unit of work Customer customer = unitOfWork.FindById<Customer>(customerId); Order order = new Order { OrderReference = orderRef }; customer.Orders.Add(order); // implicitly adds order to same unit of work as customer Note that because LightSpeed saves units of work, you must add the entity to a unit of work – whether explicitly or implicitly – in order for it to be saved. Just creating the entity is not enough! As part of adding the entity to a unit of work, LightSpeed assigns an Id to the entity. Before a new entity becomes part of a unit of work, its Id is invalid and should not be used. Updating an Existing Entity To update an existing entity, load it into a unit of work and set any required properties to their new values. Updating an existing entity Customer customer = unitOfWork.FindById<Customer>(customerId); customer.Name = "Bob"; LightSpeed automatically determines that the entity has changed, and marks it to be saved. Mindscape LightSpeed User Guide 73 Deleting an Existing Entity To delete an existing entity, load it into a unit of work and call IUnitOfWork.Remove. Deleting an existing entity Customer customer = unitOfWork.FindById<Customer>(customerId); unitOfWork.Remove(customer); If an entity has dependent associations, LightSpeed cascade deletes the dependent entities. This avoids database integrity errors due to foreign key constraints. For example, if every Order is associated with a Customer, and you delete a Customer, then all Orders associated with that Customer are also deleted. However, if the association is not dependent – that is, if your model allows dangling Orders not associated with a Customer – then the Orders are merely detached from the Customer and remain in the database. You can override the default cascade delete behaviour by setting LightSpeedContext.CascadeDeletes in code or the cascadeDeletes attribute in configuration, or by setting the Cascade Deletes option on an entity (which affects all associations where that entity is the parent), or by setting the Is Dependent option on an individual association. In all cases, remember that entities are not deleted from the database immediately, but will be deleted when you save the unit of work. Saving the Unit of Work When you have made all the changes you need to make, call IUnitOfWork.SaveChanges: Saving the unit of work unitOfWork.SaveChanges(); Mindscape LightSpeed User Guide 74 SaveChanges validates all entities that are due to be added or updated, and will not save an invalid entity. (Invalid entities may be deleted.) By default, the saved entities remain part of the unit of work, in case you want to carry out more changes on them. You can remove them, forcing LightSpeed to reload fresh copies, by calling the SaveChanges(bool reset) overload and passing true. However, it is usually clearer to start a new unit of work for the new batch of activity. After you have finished with a unit of work, you must call Dispose. Mindscape LightSpeed User Guide 75 Transactions SaveChanges is automatically transactional: that is, if more than one entity needs to be saved, LightSpeed guarantees that the changes to the database will be atomic and durable (provided the database supports transactions). To achieve this, SaveChanges automatically begins a transaction before sending the first change, and commits it after sending the last change if all changes have been successful. As far as LightSpeed is concerned, however, each SaveChanges is an independent transaction. If you need multiple LightSpeed operations to be part of a single transaction, you must specify that transaction yourself. Using TransactionScope The easiest way to control transactions with LightSpeed is to use the .NET TransactionScope class. To do this, simply surround any calls to IUnitOfWork with the standard TransactionScope block: Using a system transaction with a LightSpeed unit of work using (var transactionScope = new TransactionScope()) { using (var unitOfWork = _context.CreateUnitOfWork()) { var contribution = unitOfWork.FindById<Contribution>(1); contribution.Description = "A description"; unitOfWork.SaveChanges(); } transactionScope.Complete(); } Using ADO.NET Transactions However, not all data providers support TransactionScope. (Specifically, at the time of writing, Oracle and PostgreSQL do not.) Mindscape LightSpeed User Guide 76 For these cases, IUnitOfWork exposes a BeginTransaction method. This method returns a standard ADO.NET IDbTransaction object. In this case, the usage pattern is slightly different as we need to flush changes using the IUnitOfWork.SaveChanges method before committing the transaction and then completing the unit of work. Using an ADO.NET transaction with a LightSpeed unit of work using (var unitOfWork = _context.CreateUnitOfWork()) { using (var transaction = unitOfWork.BeginTransaction()) { var contribution = unitOfWork.FindById<Contribution>(1); contribution.Description = "A description"; unitOfWork.SaveChanges(); transaction.Commit(); } } Automatic Transactions Remember that you only need to manually specify a transaction if you need to coordinate at a larger scope than a single SaveChanges. LightSpeed automatically ensures that all database flush operations run within a transaction. In most cases therefore you will not need to create transactions explicitly. Mindscape LightSpeed User Guide 77 Controlling the Database Mapping When you’re working with LightSpeed, you’re working at the level of .NET domain objects. In the ideal world, the database would be transparent to you: objects would persist and could later be retrieved, but you wouldn’t need to know how or where that persistence happened. In reality, of course, this doesn’t happen. You may be working with an existing database, which you want to map into a more usable domain model. You have to work with database administrators who have specific rules on how the database should be organised. You may need to control which fields get persisted and which do not. You may need to move some business logic into the database for performance reasons. LightSpeed adopts a principle of convention over configuration, so that if you just define your domain model and do nothing more, it will be mapped to the database in a sensible way. This chapter describes the default LightSpeed conventions, then goes on to describe how you can override those conventions to meet your particular requirements. Mindscape LightSpeed User Guide 78 Understanding the Default Mapping By default, LightSpeed maps the domain model to the database as follows. Entity Classes Map to Tables Each entity class maps to a database table. The name of the table is the name of the entity class. For example, an Employee entity class is mapped to an Employee table. If the LightSpeedContext.PluralizeTableNames configuration option is set to true, then the name of each table is the plural of the entity class name. For example, a Person entity class is mapped to a People table. (Pluralization uses English language rules, and allows for most common irregular plural forms.) The ‘table per class’ mapping changes if you use inheritance in your domain model. See Domain Modelling Techniques for more information. Entity Id Maps to the Id Column The Entity<TId> base class defines an Id property which represents an identifying key for the entity. The Id property maps to the Id column. Consequently, in the default mapping, every table must have an Id column (which should be the primary key). The ‘Id column’ mapping changes if you use composite keys. You should only do this if you are working with a legacy database that you cannot change to support a scalar surrogate key. See Working with Legacy Databases for more information. Fields Map to Columns Each field in an entity maps to a database column. The name of the column is the name of the field. For example, a FirstName field is mapped to a FirstName column. The ‘column per field’ mapping changes if you use value objects to represent structures or semantics that turn up repeatedly in the business model. See Domain Modelling Techniques for more information. Mindscape LightSpeed User Guide 79 It’s important to be aware that LightSpeed maps fields, not properties, to columns. This allows you to decouple your API from your persistence model if required. For example, you might choose not to present some attributes directly as properties, instead allowing them to be accessed and modified only through domain methods. If you are using the designer, of course, it cannot create domain methods for you, but you can suppress the wrapper properties by setting the Generation option to FieldOnly. Associations Map to Foreign Key Columns Each one-to-many or one-to-one association in an entity is backed by a field which maps the foreign key for that association. (Many-to-many associations are represented by a pair of one-to-many associations, and are therefore backed by a pair of foreign keys fields.) The foreign key maps to a column just as a normal field does. The foreign key field name is that of the backreference (or EntityHolder field) , with an “Id” suffix. For example, a Manager association is backed by a ManagerId field which is mapped to a ManagerId column. Id Values Come From KeyTable When LightSpeed needs to assign an Id to an entity, by default it obtains the next value from a table named KeyTable. (Actually, for efficiency, LightSpeed gets values in blocks, rather than one at a time.) In the default mapping, therefore, each database must contain a table named KeyTable with the appropriate schema. See Identity Generation below for more information. Default Mapping Example The picture below shows a simple LightSpeed and how it is mapped in the database. Note that: The database table names are the same as the entity names. Each database table has a primary key named Id. This isn’t shown on the LightSpeed designer, but it is present in each LightSpeed entity. The database column names are the same as the entity property names. The database contains a foreign key column for each association, and the foreign key name is the name of the association followed by “Id.” Mindscape LightSpeed User Guide 80 Mindscape LightSpeed User Guide 81 Overriding the Default Mapping In greenfield development, it’s a good idea to keep to the default mapping. This ensures that terms are used consistently across the domain model and the database, so that everyone is using the same language and confusion is avoided. In brownfield development, this often isn’t possible, and even in greenfield projects there may be cases where the default mapping is problematic. Mapping Individual Tables and Columns The most common scenario for overriding the default mapping is that you are working with an existing database whose terminology you don’t want to carry forward into your domain objects. In this case, you can override table and column mappings on an ad hoc basis, specifying an override for each table or column where you want to change the name. Mapping Individual Tables and Columns in the Designer To specify the name of the table for an entity class, select the entity, go to the Properties grid, and set the Table Name option. To specify the name of the column for a field, select the field, go to the Properties grid, and set the Column Name option. To specify the name of the identity column – that is, the column that corresponds to the Id property – select the entity, go to the Properties grid, and set the Identity Column Name option. To specify the name of the foreign key column for an association, select the association arrow, go to the Properties grid, and set the Column Name option. If you have dragged a table into the model from a database, and you’re dissatisfied with the inferred names, you can quickly rename an entity or field while retaining its mapping by using the Refactor > Rename command and checking the “Keep existing name as database table/column name” option. Mindscape LightSpeed User Guide 82 If you have dragged a table into the model from a database, and the primary key column has a name other than Id, LightSpeed will automatically create the identity column mapping for you. Mapping Individual Tables and Columns in Code To specify the name of the table for an entity class, apply TableAttribute to the class. To specify the name of the column for a field, apply ColumnAttribute to the field. To specify the name of the identity column, apply TableAttribute to the class and set the attribute’s IdColumnName. To specify the name of the foreign key column for an association, apply ColumnAttribute to the field containing the foreign key. Defining Your Own Mapping Convention Mapping table and column names works well for individual tables and columns, but if you are working with a database which uses a regular convention that happens to be different from LightSpeed’s, then you may want to encapsulate that convention rather than mapping each and every table or column. For example, some organisations use a sort of Hungarian notation to name database elements: tables are prefixed tbl, columns are prefixed col, and so on. Other organisations like to use distinct primary key names in different tables, so the Employee table’s primary key would be EmployeeId while the Customer table’s primary key would be CustomerId. Conventions such as this are represented by implementations of the INamingStrategy interface. INamingStrategy allows you to map the names of most LightSpeed elements, but for the time being we will focus on tables and columns. We could represent the Hungarian convention as follows: Mindscape LightSpeed User Guide 83 Representing Hungarian notation as a LightSpeed naming strategy public class HungarianNamingStrategy : INamingStrategy { public string GetTableName(string defaultName, string className) { return "tbl" + defaultName; } public string GetColumnName(string defaultName, string fieldName) { return "col" + defaultName; } // other members } All members of INamingStrategy get passed a defaultName by LightSpeed. If you want to accept the default convention in a particular case, just return defaultName. Once you have implemented a naming strategy to represent your convention, you must tell LightSpeed to use it. To do this, set LightSpeedContext.NamingStrategy in code, or the namingStrategyClass attribute in configuration. When setting the naming strategy in configuration, you must provide a full assembly-qualified type name. Specifying the Hungarian convention in configuration <add name="Test" namingStrategyClass="MyApp.HungarianNamingStrategy, MyApp" /> Specifying the Hungarian convention in code _context.NamingStrategy = new HungarianNamingStrategy(); Mindscape LightSpeed User Guide 84 Reserved Words Occasionally you will want to use a name in your domain model which is a reserved word in SQL. The classic example is Order, which clashes with SQL’s ORDER BY keywords. To prevent errors in this case, set the quoteIdentifiers configuration attribute, or set LightSpeedContext.QuoteIdentifiers in code. Mindscape LightSpeed User Guide 85 Overriding Persistence Behaviour By default, all fields in a LightSpeed entity are persistent: that is, LightSpeed expects to be able to load them from the database, and includes them when saving the entity. Excluding a Field From Persistence Some entities need to hold non-persistent state. For example, an entity might have a field which caches the result of an expensive calculation, or which represents some UI status which needs to be passed around with the entity but is not part of its persistent state. Such fields are known as transient fields. To mark a field as transient in the designer, set its Transient option to true. To mark a field as transient in code, apply TransientAttribute to the field. Excluding a Field From Being Saved Occasionally you will have a database column that you want to load, but not to save. The classic example is a computed column: it is useful to have the result of the computation, but trying to save a value back into that column would cause an error. In this case you do not want the field to be transient because transient fields are not loaded from the database – they are not persistent at all. You want the field should be loaded but not saved. To exclude a field from being saved in the designer, set its Load Only option to true. To exclude a field from being saved in code, make the field readonly (ReadOnly in Visual Basic). Mindscape LightSpeed User Guide 86 Examples Overriding persistence behaviour [Transient] private EditStatus _editStatus; // neither loaded nor saved private readonly decimal _discountedPrice; // loaded, but not saved Mindscape LightSpeed User Guide 87 Identity Generation Every LightSpeed entity has an Id, which LightSpeed uses to uniquely identify the entity. When a newly created entity becomes part of a unit of work for the first time, LightSpeed assigns it an Id. LightSpeed has a number of ways of generating Ids for entities. The default identity method is KeyTable, which is an efficient and portable way of generating numeric Ids. This section describes how to override the default, and the alternative options. Identity Methods in LightSpeed The available methods of generating Ids are defined in the IdentityMethod enumeration. LightSpeed supports the following methods of generating Ids: Identity Method KeyTable Id Type Numeric Sequence Numeric MultiSequence Numeric Guid GuidComb GUID GUID Identity Column Numeric Description Uses a table in the database to store the next Id, and advances this value every time a new block of Ids is required. Similar to KeyTable, but uses a native database sequence object. Similar to Sequence, but allows multiple independent sequences (e.g. sequence per table). Uses the normal .NET algorithm to create a GUID. Uses a special algorithm to create GUIDs with special sorting characteristics that make database indexing more efficient. The Id is generated by the database when the entity is saved, using the database’s built-in identity column or autoincrement functionality. Specific methods are discussed in more detail below. Setting the Identity Method Globally You will normally choose a single method of generating Id values, which is used for all entities in your model. This simplifies administration by applying a single consistent Mindscape LightSpeed User Guide 88 policy across the entire database. To do this, set LightSpeedContext.IdentityMethod in code, or the identityMethod attribute in configuration. Specifying the Guid identity method in configuration <add name="Test" identityMethod="Guid" /> Specifying the Guid identity method in code _context.IdentityMethod = IdentityMethod.Guid; Overriding the Identity Method on a Per-Entity Basis In a few cases, notably legacy databases where different tables have different identity types – for example, some tables have GUID primary keys while others have numeric primary keys – you may need to override the global identity method for specific tables. To do this, select the entity that is mapped to that table, and set its Identity Method option. (For hand-coded entities, apply TableAttribute and set the IdentityMethod property.) Some legacy databases require that the identity be part of the business data rather than an opaque value. For information about this, see Using Natural Keys in the Working with Legacy Databases chapter. KeyTable Identity Generation KeyTable identity generation uses a single table in your database that stores the current identity value. This allows LightSpeed to secure a block of identities and use them to set the identity value of newly created entities in your system. This works because every identity in the database, even in different tables, is unique. KeyTable works with numeric identity types (Int32 and Int64). Mindscape LightSpeed User Guide 89 KeyTable is a great identity method if you need high performance from you database as LightSpeed does not need to flush new entities to obtain identity values. That means less round trips to the database and therefore a more speedy application. KeyTable is also portable between database engines. For these reasons, KeyTable is the default identity method in LightSpeed. By default, KeyTable secures identity values in blocks of 10. You can override this by setting LightSpeedContext.IdentityBlockSize in code, or the identityBlockSize attribute in configuration. A large value means LightSpeed needs to consult the key table less often, which improves performance, but can result in ‘wasted’ Ids if few entities are added in each run of the program. If you use the KeyTable method, you must create the key table in the database. To do this, run the KeyTable.sql schema file for your database, which you can find in your LightSpeed install directory. Sequence and MultiSequence Identity Generation Sequence identity generation is similar to KeyTable, but is natively supplied by the database engine. This means using sequence identity generation is limited to databases that support it: Oracle and PostgreSQL. Sequence identity generation has similar characteristics to KeyTable, but is not portable because many databases do not support sequence objects. By default, Sequence and MultiSequence assume that the sequence increment is 10. If the sequence increment amount is something other than 10, you must set the identity block size to the sequence increment amount. To do this, set LightSpeedContext.IdentityBlockSize in code, or the identityBlockSize attribute in configuration. If the identity block size is different from the sequence increment amount, this will cause errors. If you use the Sequence method, you must create the sequence in the database. To do this, run the Sequence.sql schema file for your database, which you can find in your LightSpeed install directory. The provided Sequence.sql assumes the default identity block size of 10; if you change the identity block size you must change the sequence increment amount and vice versa. Mindscape LightSpeed User Guide 90 If you use the MultiSequence method, you must create each of the sequences you intend to use. MultiSequence is typically used with existing databases with a ‘sequence per table’ policy, in which case the sequences will presumably already exist. If you use MultiSequence, you must also implement a naming strategy to tell LightSpeed which sequence to use for each table. Your naming strategy class must implement the INamingStrategy interface, and return the per-table name from GetMultiSequenceName. All other members can return the default name. Specifying sequence names for the MultiSequence identity method public class SequencePerTableNamingStrategy : INamingStrategy { public string GetMultiSequenceName(string defaultName, Type entityType) { // Example naming convention: seq_employee_ids, seq_order_ids, etc. return "seq_" + entityType.Name.ToLower() + "_ids"; } // other members can all return defaultName (unless using a custom mapping policy) } See Defining Your Own Mapping Convention above for how to make LightSpeed use your naming strategy. Guid and GuidComb Identity Generation GUID identities are great for systems that require strong uniqueness (for example, to support replication) and are also extremely practical from a generation perspective, because LightSpeed never needs to consult the database to allocate an Id. However, while this is not an issue for many systems, GUIDs can have an impact on application aesthetics. For example, you may not want GUIDs present in your URLs if you are developing a web application. Obviously, GUID methods work only with the Guid identity type. The Guid identity method uses the .NET GUID generator to generate Ids. The GuidComb identity method uses an alternative algorithm, described in Jimmy Nilsson’s article The Cost of GUIDs as Primary Keys, to trade randomness for database Mindscape LightSpeed User Guide 91 INSERT performance. The COMB method generates GUIDs in a way that reduces the indexing cost when a new primary key value is inserted. Consider GuidComb if you want GUID Ids, have a very large data set and are performing a lot of inserts. IdentityColumn Identity Generation Identity Column identity generation supports auto-incrementing identity columns. Identity Column identity generation is limited to databases that support autoincrement columns: SQL Server, SQL Server Compact, VistaDB, PostgreSQL, MySQL and SQLite. Identity Column works with numeric identity types (Int32 and Int64). This approach, while supported, is not a recommended method for identity generation. Using this identity type means that LightSpeed cannot do optimized batching as well as it can with other identity methods. This is because LightSpeed needs to obtain the new identity value and update any in-memory associated objects before continuing the flush process. Consequently, the IdentityColumn method should be used only with legacy databases where the auto-increment columns cannot be converted to normal columns, for example because of other applications that rely on the auto-increment behaviour. Identity Generation Options Some identity methods can be configured in different ways. To specify configuration options for an identity method, set the LightSpeedContext.IdentityMethodOptions property. The options object must be appropriate to the identity method in use. At the time of writing, the only identity methods with configuration options are Sequence and MultiSequence, which can be configured using a SequenceIdentityMethodOptions object. See the SequenceIdentityMethodOptions class documentation in the API reference for information. How Block Allocation Methods Work The KeyTable and sequence identity methods reserve Ids in blocks to minimise the number of database queries required to allocate Ids. Block reservations are held at the LightSpeedContext instance level. This means that clients that share the same Mindscape LightSpeed User Guide 92 LightSpeedContext instance will get Ids from the same reserved pool, whereas clients that create different LightSpeedContext instances will have their own pools. For example, suppose you are writing a Web application. You might choose to create a LightSpeedContext per page. This would be wasteful because each page that performed even a single insert would reserve 10 Ids from the KeyTable or sequence, incurring its own database call. By contrast, if you created a singleton LightSpeedContext, for example as a static field in Global.asax.cs, and each page referred to that one context, then the first page that performed an insert would cause 10 Ids to be reserved, incurring a database call, but subsequent pages would receive Ids from that reserved block (until it ran out), avoiding further database calls and improving performance. In general, therefore, you should have a single LightSpeedContext instance per application (or one per database, if your application uses multiple databases). Of course you can and often will create multiple independent units of work from this one context – in the Web example you would have a unit of work per Web request, even though the units of work shared the same LightSpeedContext object. Mindscape LightSpeed User Guide 93 Working with Database Views There are two ways to use database views. The first is to when you have an entity class that is backed by a table in the normal way, but you want to load entities through a view rather than directly from the table. This allows the same entity type to be materialised from multiple different views, such as AllOrders, OverdueOrders or RecentOrders. The second is to create an entity class which maps the view, just as if the view were a table. This allows entities to be materialised from views when there is no concrete table, for example when a view joins multiple tables to provide a businessmeaningful presentation of denormalised data. In both cases, the view must contain a column containing unique identifying values. As with a table, this column must be called Id, or must be mapped using the Identity Column Name option or TableAttribute.IdColumnName. Loading Entities Through a View If an entity backs onto a table, but you sometimes want to load instances through a predefined view for performance reasons, you can associate the view with the entity by dragging the view from Server Explorer onto the entity shape. This adds a property to the unit of work representing a query through that view. The property’s name is the view name. The property can be used as follows: Querying through a view using LINQ // ProductsByPriceDescending is a view over the Product table using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { var mostExpensiveProducts = unitOfWork.ProductsByPriceDescending .Take(100) .ToList(); } You can also query a view using query objects, bypassing the designer and the generated helper property. To do this, set Query.ViewName. Mindscape LightSpeed User Guide 94 Querying through a view using query objects // ProductsByPriceDescending is a view over the Product table using (IUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { Query query = new Query { ViewName = "ProductsByPriceDescending", Page = Page.Limit(100) }; var mostExpensiveProducts = unitOfWork.Find<Product>(query); } We also offer this ability to perform ad-hoc queries using views if you are using LINQ through the WithViewName extension method. Querying through a view using the WithViewName extension method // ProductsByPriceDescending is a view over the Product table using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { var mostExpensiveProducts = unitOfWork.Products .WithViewName(“ProductsByPriceDescending”) .Take(100) .ToList(); } In either case, the view must have the same schema as the table because you are loading it into the same type of entity. Entities loaded through a view can be modified or deleted in the normal way, with the changes being applied via the underlying table. Creating an Entity Class to Map a View If you only have a view – that is, there is no backing table, or you don’t have access to the backing table – then you can load entities from the view by creating an entity class that maps the view. To do this, drag the view onto the designer surface (that is, the background). The designer infers an entity class from the view columns just as it would Mindscape LightSpeed User Guide 95 from table columns, and queries for this kind of entity will be passed to the view just as if the entity were backed by a table. In most cases, you will not be able to save changes to entities that are backed by views, because databases generally do not allow inserts, updates or deletes against views. You may therefore wish to mark the entity fields as Load Only so that application code can’t erroneously make changes. However, if your database supports updateable views, and if your view is updateable, then you can save changes against it in the normal way. Because a view does not have a primary key, if the identity column is not called Id then the designer will not infer the identity column name. You will need to fix this up manually in this case. Mindscape LightSpeed User Guide 96 Invoking Stored Procedures It is sometimes necessary to encapsulate very complex queries as stored procedures. You can invoke a stored procedure from LightSpeed either to load a set of entities, to calculate a single value such as a count or total, or just to execute it with no return value. To add a stored procedure to the designer, drag it onto the designer surface. LightSpeed will create a SelectProcedure, ScalarProcedure or NonQueryProcedure accordingly depending on the result schema of the procedure. For a SelectProcedure, LightSpeed tries to identify the type of entity being returned; if no existing entity is suitable then LightSpeed creates a new entity class based on the result schema. In any case, LightSpeed generates a method on the unit of work to invoke the procedure. Invoking a Stored Procedure Using LINQ To invoke a stored procedure, call the appropriate method on the unit of work. Invoking stored procedures using LINQ using (StoreUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { // SelectProcedure returns a collection of entities var expensiveProducts = unitOfWork.GetProductsCostingMoreThan(10000); // ScalarProcedure returns a value var totalPendingOrderValue = unitOfWork.GetTotalPendingOrderValue(); // NonQueryProcedure does not return a value unitOfWork.ArchiveInactiveCustomers(DateTime.Now.AddMonths(-6)); } If the stored procedure has output or input-output parameters, these will appear as out or ref parameters to the method. Mindscape LightSpeed User Guide 97 Invoking a Stored Procedure Using Query Objects If you are not using the designer, you must use query objects to invoke a stored procedure. (The LINQ methods shown above use query objects internally, but are generated by the designer so that you do not need to work with the query objects directly.) The key class for using stored procedures with query objects is ProcedureQuery. ProcedureQuery encapsulates the name of the procedure and any parameters you want to pass to it. You can pass a ProcedureQuery to one of three methods defined on IUnitOfWork: To load entities, call the Find method. To get a single scalar value, call the Calculate method. To execute an action (no return value), call the Execute method. Invoking stored procedures using query objects using (IUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { // Loading entities ProcedureQuery entityQuery = new ProcedureQuery("GetProductsCostingMoreThan", new ProcedureParameter("minCost", 10000)); var expensiveProducts = unitOfWork.Find<Product>(entityQuery); // Getting a single value ProcedureQuery valueQuery = new ProcedureQuery("GetTotalPendingOrderValue"); var totalPendingOrderValue = unitOfWork.Calculate(valueQuery); // Executing an action ProcedureQuery actionQuery = new ProcedureQuery("ArchiveInactiveCustomers", new ProcedureParameter("olderThan", DateTime.Now.AddMonths(-6))); unitOfWork.Execute(actionQuery); } The query object API uses ProcedureParameter objects to represent stored procedure parameters. You can declare parameters with direction Output, InputOutput or ReturnValue to receive values returned by the stored procedure through parameters or as the return value. Mindscape LightSpeed User Guide 98 Database Considerations for Stored Procedures When loading entities through a stored procedure, the returned record set is treated exactly the same as if it had been returned by performing a table SELECT. Therefore: The set of columns in the record set must correspond in name and type to the fields of the entity. The Column Name setting (or ColumnAttribute) is respected as normal. The returned record set must also contain an Id column. If the Identity Column Name is set for the entity (or the entity has TableAttribute.IdColumnName), then the returned record set must contain a column with this name instead. When using stored procedures on Oracle, you must follow a special convention for returning results. See the chapter Working with Database Providers for more information. Additional Support for Stored Procedures For information about other ways of using stored procedures with LightSpeed, see the chapter Working with Legacy Databases. Mindscape LightSpeed User Guide 99 Building Applications with LightSpeed You can use LightSpeed in a variety of application architectures, from Web pages to rich clients and service-oriented distributed applications. In the following chapters, we’ll discuss in more detail how to use LightSpeed in specific scenarios. First, however, we need to address some areas which are common across application architectures. Mindscape LightSpeed User Guide 100 Configuration You can configure LightSpeed through code or through the configuration file (web.config or appname.exe.config depending on the type of application). We discussed the core configuration options in the Basic Operations chapter. In this section we will review other configuration options. For a full list of configuration settings, see the Appendices. How to Configure LightSpeed In general, it is best to configure LightSpeed through the configuration file. This ensures that all instance-specific configuration is externalised from the code, so that the code can be easily moved between different environments. As mentioned in the Basic Operations chapter, to use the configuration file you must declare and implement a lightSpeedContexts section. You can then define particular configurations by adding elements to that section, using the add tag: Declaring the lightSpeedContexts section <configSections> <section name="lightSpeedContexts" type="Mindscape.LightSpeed.Configuration.LightSpeedConfigurationSection, Mindscape.LightSpeed" /> </configSections> Implementing the lightSpeedContexts section <lightSpeedContexts> <add name="Test" /> </lightSpeedContexts> <!-- We will add attributes here shortly --> Individual configuration options are specified as attributes of the add tag. Mindscape LightSpeed User Guide 101 A few settings are not available through the configuration file. These settings can only be configured in code. These are typically advanced settings such as custom strategy classes which do not need to change between environments. Loading the Configuration A configuration is represented by a LightSpeedContext object. To load a LightSpeedContext from configuration, pass the name of the configuration file entry to the LightSpeedContext constructor: Loading a LightSpeedContext from configuration private static readonly LightSpeedContext _context = new LightSpeedContext("Test"); You can still modify the context object in code by setting its properties, for example if you need to apply a setting which is not available through configuration. Setting Up a LightSpeedContext in Code You can create a blank LightSpeedContext using the default constructor: private static readonly LightSpeedContext _context = new LightSpeedContext(); This will not load any settings from the configuration file. You can then set up the context object by settings its properties. You will usually use code-only setup only for prototypes and tests, because it leads to maintenance headaches when the code needs to be moved between environments. Context Per Application, Not Context Per Request In general, you should not create multiple instances of LightSpeedContext. As shown above, create a single (static) instance, and share it across your entire application. You will not usually modify a context object after initialisation, because your configuration Mindscape LightSpeed User Guide 102 will not change during a run. So it is safe to share a context object even across a highly concurrent application such as a Web site. By a single instance, we mean a single instance per database. If your application talks to two databases – for example copying data between them – then of course you will need two contexts. The point is that you should not create new contexts on every page or every screen: you should reuse the same context instance or instances throughout the lifetime of your application. Preferring the singleton pattern is not just a matter of not creating superfluous objects – redundant contexts can affect performance, for example resulting in superfluous database queries to allocate entity Ids (see Identity Generation in the chapter Controlling the Database Mapping). There are some cases where you may want to create multiple contexts. Generally we have found these would be either: You are connecting to more than one data source, or You are dealing with a multi-tenanted scenario and need to customize the connection string per tenant Terminology If you’re familiar with LINQ to SQL or the Entity Framework, you may associate the term ‘context’ with a domain model or database session, as in the LINQ to SQL DataContext. A LightSpeedContext is not like a DataContext: it holds configuration settings, not database data. The LightSpeed equivalent of a DataContext or ObjectContext is a unit of work. Mindscape LightSpeed User Guide 103 Configuration Settings There are a lot of configuration settings on LightSpeedContext, which are described in more detail in the relevant section and listed in the Appendices. This section lists some of the most commonly used. Attribute Usage connectionStringName The database connection string. The actual string is looked up from the name in the <connectionStrings> section. See the Basic Operations chapter for more information. dataProvider The type of database engine. See the Basic Operations chapter or the DataProvider enumeration for a list. pluralizeTableNames Indicates that LightSpeed should map entity classes to table names using the plural form of the class name. For example, a Person class would normally map to a Person table, but with pluralizeTableNames in effect it would map to a People table. quoteIdentifiers Quotes table and column names in SQL. This avoids conflicts with reserved words, but causes some databases to behave in a case-sensitive way, which can lead to mapping problems. schema The database schema in which the entity tables are found. identityMethod Specifies how LightSpeed assigns Ids to new entities. See Identity Generation in the Controlling the Database Mapping chapter. loggerClass The type of logger to which LightSpeed should log SQL statements and diagnostic and performance information. See Logging in the Testing and Debugging chapter for more information. All of these (and the other) configuration settings have equivalent properties on the LightSpeedContext class. Mindscape LightSpeed User Guide 104 Localizing LightSpeed Messages All of LightSpeed’s localizable resources such as error and validation messages are kept in a single resources file (Resources.resx). This file is made available as part of the LightSpeed installation and can be found in the Localization folder under the LightSpeed installation directory. To localize LightSpeed messages, translate the messages in Resources.resx, build the translation into a satellite DLL and deploy the satellite DLL alongside your application. A sample illustrating how to produce and deployed a localized satellite resource assembly can be found in the Samples folder. Localizing Property Names Although you can localize messages such as validation messages via a satellite assembly, in some cases, specifically validation, these messages contain placeholders which LightSpeed will fill in with property names – that is, the same names you use in your code. Normally, as per the ‘ubiquitous language’ of domain driven development, you will use property names which are familiar to your users. But if you are serving users in multiple locales then you may need to display different property names in each locale, so you will have to convert from the .NET property names to localized display names. To do this, create an implementation of the IDisplayNamingStrategy interface to perform the translation and return the translated name. Here is an example which simply looks up the property name in the application resources, allowing you to localize names through a satellite DLL. Mindscape LightSpeed User Guide 105 Implementing IDisplayNamingStrategy public class ResourceLookupStrategy : IDisplayNamingStrategy { public string GetDisplayName(string defaultName, Type entityType) { string resourceKey = entityType.Name + "_" + defaultName; return ResourceManager.GetString(resourceKey); // uses current culture } } To use this strategy, set LightSpeedContext.DisplayNamingStrategy in code, or the displayNamingStrategyClass attribute in configuration. Specifying a display naming strategy in configuration <add name="Test" displayNamingStrategyClass="MyApp.ResourceLookupStrategy, MyAssembly" /> Specifying a display naming strategy in code _context.DisplayNamingStrategy = new ResourceLookupStrategy(); Mindscape LightSpeed User Guide 106 Customising How LightSpeed Connects to the Database By default, a LightSpeed unit of work maps to a database connection. The unit of work opens the connection the first time it needs to talk to the database, and keeps it open until you dispose the unit of work. This usage reflects the recommended pattern of keeping units of work short-lived. For example, in a Web application using ‘unit of work per request,’ the unit of work lives just long enough to serve a page, so the database connection is quickly closed as well. Some applications, particularly rich clients, use long-running units of work. This can create problems for the ‘connection per unit of work’ strategy, because a large number of idle clients can starve the database of connections, and because it increases the chance of the connection going into a faulted state, leaving the unit of work saddled with an unusable connection. For cases such as these you can override the ‘connection per unit of work’ pattern by implementing a custom connection strategy. You can use this to gain finer control over how LightSpeed uses connections. Implementing a Custom Connection Strategy To implement a connection strategy, create a class that derives from ConnectionStrategy and overrides the Connection property and the Dispose(bool) method. Here is a simple connection strategy that opens a connection whenever it needs one, and leaves it open just like the default behaviour, but also allows application code to forcibly close the connection via a CloseConnection method. Mindscape LightSpeed User Guide 107 Implementing ConnectionStrategy public class CloseableConnectionStrategy : ConnectionStrategy { private IDbConnection _connection; public CloseableConnectionStrategy(LightSpeedContext context) : base(context) { } protected override IDbConnection Connection { get { if (_connection == null) { _connection = Context.DataProviderObjectFactory.CreateConnection(); _connection.ConnectionString = Context.ConnectionString; _connection.Open(); } return _connection; } } public void CloseConnection() { if (_connection != null) { _connection.Dispose(); _connection = null; } } protected override void Dispose(bool disposing) { if (disposing) { if (_connection != null) { _connection.Dispose(); _connection = null; } } base.Dispose(disposing); } } Mindscape LightSpeed User Guide 108 Using a Custom Connection Strategy To use a custom connection strategy, set the UnitOfWork.ConnectionStrategy property to an instance of your custom strategy. Using a custom connection strategy using (ModelUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { unitOfWork.ConnectionStrategy = new CloseableConnectionStrategy(_context); } You must do this before the unit of work first connects to the database – otherwise the unit of work will use the default connection strategy. Do not change connection strategies in the middle of a unit of work. If desired, you can set up the connection strategy in the constructor of a strong-typed unit of work, or through LightSpeedContext.UnitOfWorkFactory. Note that the unit of work will not invoke any actions on your custom strategy other than to get connections and to notify various operations. It is up to application code to do this. For example, if you want to use the CloseableConnectionStrategy to close the connection after loading a screen, you might write something like this: private void OnLoad() { LoadDataToScreen(); // We know we won't be needing the database again for a while, so drop the connection ((CloseableConnectionStrategy)(_unitOfWork.ConnectionStrategy)).CloseConnection(); } The connection is released, but the unit of work remains live, and will automatically reinitiate a connection when required – for example, if you load new entities, traverse an association, or call SaveChanges. Note that some LightSpeed tasks, such as traversing an association, internally require a connection. Your connection strategy will still be used in such cases, but your Mindscape LightSpeed User Guide 109 application code may not be aware of it. For example, a data binding that traverses an association could cause your strategy to reinitiate a connection: this connection would remain open, and your application code would not know about it in order to reclaim it. If this is a concern, you can override ConnectionStrategy.OnDatabaseOperationComplete to receive notifications of LightSpeed database activity, but care is required that you do not close a connection while LightSpeed is still using it. Mindscape LightSpeed User Guide 110 Building Web Applications You can use LightSpeed in both ASP.NET Web Forms and ASP.NET MVC web applications allowing you to develop in terms of your domain model and allow LightSpeed to manage the data access. Developing with LightSpeed in the context of a web application is largely the same as any other type of application; however the key point of difference is around the way in which you will want to scope your UnitOfWork. For web applications you will be concerned with scoping your UnitOfWork on a per request basis and how to perform data access within your code behind or controller code. This chapter will highlight how to cover both of these concerns in the context of both ASP.NET Web Forms and ASP.NET MVC applications. Additionally if you are deploying into a medium trust environment (this is often the case with shared hosting providers) then you will want to review the section detailing specific changes you will need to make to your LightSpeed configuration and designer settings to support medium trust scenarios. Mindscape LightSpeed User Guide 111 Building ASP.NET Web Forms Applications Unit of Work Scoping To scope your UnitOfWork instances on a per request basis you will want to make use of the PerRequestUnitOfWorkScope<TUnitOfWork> helper class which is included as part of the Mindscape.LightSpeed assembly. First you will want to declare your LightSpeedContext so it is accessible for creating UnitOfWork instances. We recommend that this is done as part of your Global.asax.cs so it is always available. Example declaration of a static LightSpeedContext in Global.asax.cs public static LightSpeedContext<MyModelUnitOfWork> LightSpeedContext = new LightSpeedContext<MyModelUnitOfWork>("Development"); We would then recommend that you set up a base class for your pages (or if you are using a controller/presenter approach then in your base class for your controllers/presenters) which can leverage the PerRequestUnitOfWorkScope<TUnitOfWork> to provide you with access to your UnitOfWork. Example use of PerRequestUnitOfWorkScope<TUnitOfWork> in your base class public class PageBase : System.Web.UI.Page { private PerRequestUnitOfWorkScope<ModelUnitOfWork> _unitOfWorkScopeHolder; public PageBase() { _unitOfWorkScopeHolder = new PerRequestUnitOfWorkScope<ModelUnitOfWork>(Global.LightSpeedContext); } } Mindscape LightSpeed User Guide 112 The PerRequestUnitOfWorkScope class holds instances of your typed UnitOfWork instances in the HttpContext.Items collection so they will be available for the duration of the request. The UnitOfWork will only be instantiated once you call the .Current property on the PerRequestUnitOfWorkScope instance so it is safe to instantiate the holder class early and set up a property accessor to pass through to the .Current property on the holder. Example declaration of a UnitOfWork property on your base class public ModelUnitOfWork UnitOfWork { get { return _unitOfWorkScopeHolder.Current; } } Finally you will need to dispose the UnitOfWork instance at the end of the request to ensure the database connection is released. We recommend that this is done as part of the EndRequest event and that you hook this in your Global.asax.cs Example of PerRequestUnitOfWorkScope<TUnitOfWork> disposal code protected void Application_EndRequest(object sender, EventArgs e) { var scope = new PerRequestUnitOfWorkScope<ModelUnitOfWork>(LightSpeedContext); if (scope.HasCurrent) { scope.Current.Dispose(); } } Validation LightSpeed provides a rich, extensible object-level validation framework. For more details about the validation framework in LightSpeed please review the Validation section in the Creating Domain Models chapter. Mindscape LightSpeed User Guide 113 When you approach validation with ASP.NET you will be interested in handling both client and server side validation concerns. You will implement your client side validation concerns manually and then you can use the .IsValid property in your postback events to determine if your updated LightSpeed entity is valid. Example of testing a LightSpeed entity for validity on postback // your manual assignment code would go here if (!MyEntityInstance.IsValid) { // triggering any additional validation messages would go here return; } You may also wish to surface the validation error messages for your entity and bind those to a summary on the page. The pattern we would generally recommend for this is to expose an Errors property on your page which returns a LightSpeed ValidationErrorsCollection instance. This can then be returned from any of your entities which are invalid. Mindscape LightSpeed User Guide 114 Example of testing a LightSpeed entity for validity on postback public Member Member { get { if (_member == null) { _member = new Member(); } return _member; } set { _member = value; } } public ValidationErrorCollection ValidationErrors { get { return Member.IsValid ? null : Member.Errors; } } // Example of binding the ValidationErrors collection on the page <% if (IsPostBack && ValidationErrors != null) { %> <div id="errors"> <h2>Remaining Details Required:</h2> <ul> <asp:Repeater runat="Server" ID="ErrorsRepeater"> <ItemTemplate> <li><%# System.Web.UI.DataBinder.Eval(Container.DataItem, "ErrorMessage") %></li> </ItemTemplate> </asp:Repeater> </ul> </div> <% } %> Data Binding using EntityDataBinder The Mindscape.LightSpeed.Web assembly contains a simple two way data-binder which can be used to help perform binding to and from your LightSpeed entities. To use the data binder you need to describe the binding behavior between form fields and data accessible to your page instance. Mindscape LightSpeed User Guide 115 An example describing a two way data-binding between two fields on a form and a Member entity which has been exposed on the page instance <lightspeed:EntityDataBinder runat="Server" ID="DataBinder"> <lightspeed:EntityDataBindingItem runat="Server" BindingMode="TwoWay" BindingSource="Member" BindingSourceMember="Username" TargetControl="SignUpUsername" TargetControlProperty="Text" /> <lightspeed:EntityDataBindingItem runat="Server" BindingMode="TwoWay" BindingSource="Member" BindingSourceMember="FirstName" TargetControl="SignUpFirstName" TargetControlProperty="Text" /> </lightspeed:EntityDataBinder> An example which would bind validation errors to an errors repeater <lightspeed:EntityDataBinder runat="Server" ID="DataBinder"> <lightspeed:EntityDataBindingItem runat="Server" BindingMode="OneWay" BindingSource="this" BindingSourceMember="ValidationErrors" TargetControl="ErrorsRepeater" TargetControlProperty="DataSource" /> </lightspeed:EntityDataBinder> Like other data-bound controls in ASP.NET you need to explicitly call .DataBind() on the control to ask it to perform a data-binding operation. Because the source data is specified as part of the declarative mark-up for the control there is no DataSource property which needs to be assigned. For unbinding data back to your entities you will need to call .Unbind() in your postback events after which it is sensible to validate your objects and then deal with any resulting errors. Mindscape LightSpeed User Guide 116 An example of calling DataBind and Unbind protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { DataBinder.DataBind(); } } protected void SignUpButton_Click(object sender, EventArgs e) { DataBinder.Unbind(); if (!Member.IsValid) { DataBinder.DataBind(); return; } } Samples For example code you can review the Aptitude Test sample which has been installed alongside LightSpeed. Mindscape LightSpeed User Guide 117 Building ASP.NET MVC Applications Unit of Work Scoping To scope your UnitOfWork instances on a per request basis you will want to make use of the PerRequestUnitOfWorkScope<TUnitOfWork> helper class which is included as part of the Mindscape.LightSpeed assembly. First you will want to declare your LightSpeedContext so it is accessible for creating UnitOfWork instances. We recommend that this is done as part of your Global.asax.cs so it is always available. Example declaration of a static LightSpeedContext in Global.asax.cs public static LightSpeedContext<MyModelUnitOfWork> LightSpeedContext = new LightSpeedContext<MyModelUnitOfWork>("Development"); We recommend that you use the LightSpeedControllerBase<TUnitOfWork> class which is provided as part of the Mindscape.LightSpeed.Web assembly as the base for your controllers, as this provides a per request scoped UnitOfWork which uses PerRequestUnitOfWorkScope<TUnitOfWork> and handles the disposal of any UnitOfWork instances on your behalf by default. Example controller declaration using LightSpeedControllerBase<TUnitOfWork> public class MyController : LightSpeedControllerBase<ModelUnitOfWork> { protected override LightSpeedContext<ModelUnitOfWork> LightSpeedContext { get { return MvcApplication.LightSpeedContext; } } } Mindscape LightSpeed User Guide 118 By default the LightSpeedControllerBase will dispose of any created UnitOfWork instance as part of the OnResultExecuted handler or alternatively when the controller instance is disposed. If you wish to manually control the disposal behaviour, for example you may wish to dispose it at the end of the request to allow further access to the UnitOfWork instance through an external PerRequestUnitOfWorkScope<TUnitOfWork>, then you will need to set the DisposeUnitOfWorkOnResultExecuted property to false to disable the automatic behaviour. Example of manual disposal code using the EndRequest event public class MyController : LightSpeedControllerBase<ModelUnitOfWork> { public MyController() { DisposeUnitOfWorkOnResultExecuted = false; } } // This code would reside in Global.asax.cs protected void Application_Start(object sender, EventArgs e) { EndRequest += new EventHandler(OnEndRequest); } void OnEndRequest(object sender, EventArgs e) { var scope = new PerRequestUnitOfWorkScope<ModelUnitOfWork>(LightSpeedContext); if (scope.HasCurrent) { scope.Current.Dispose(); } } Model Binding for LightSpeed Entities If you are using the DefaultModelBinder which ships with ASP.NET MVC you may wish to improve the experience in binding your LightSpeed entities. By default the DefaultModelBinder will interrogate the Errors property on a LightSpeed entity causing any errors to be concatenated together. Mindscape LightSpeed User Guide 119 The Mindscape.LightSpeed.Web assembly includes a custom model binder called LightSpeedEntityModelBinder which can be used with you ASP.NET MVC projects for binding LightSpeed entities. To register this model binder to just handle the entity types in your project, call the static LightSpeedEntityModelBinder.Register method, passing in the assembly which contains your entity types. Only types which derive from Mindscape.LightSpeed.Entity in that assembly will be associated with the LightSpeedEntityModelBinder. Configuring the use of the ModelBinder in Global.asax.cs protected void Application_Start() { RegisterRoutes(RouteTable.Routes); LightSpeedEntityModelBinder.Register(typeof(ModelUnitOfWork).Assembly); } One it has been set up your entities can be unbound either when unbinding takes place for an action argument or manually when you call UpdateModel. The main limitation to be aware of with the LightSpeedEntityModelBinder is that it will not traverse any loaded associations. An alternative to using the custom model binder provided with LightSpeed is to create your own model binder, likely sub-classing DefaultModelBinder to afford yourself the benefits it already provides. If you take this approach then you will primarily be interested in correctly handling any errors after the model binding takes place. This can be achieved as shown below. Mindscape LightSpeed User Guide 120 An example of implementing BindModel when sub-classing DefaultModelBinder and handling extracting error messages from your LightSpeed entity public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { object result = base.BindModel(controllerContext, bindingContext); if (typeof(Entity).IsAssignableFrom(bindingContext.ModelType)) { Entity entity = (Entity)result; if (!entity.IsValid) { foreach (var state in bindingContext.ModelState .Where(s => s.Value.Errors.Count > 0)) { state.Value.Errors.Clear(); } foreach (var error in entity.Errors) { if (error.ErrorMessage.EndsWith("is invalid")) continue; bindingContext.ModelState.AddModelError(error.PropertyName ?? "Custom", error.ErrorMessage); } } } return result; } Validation When you approach validation with ASP.NET MVC you will be interested in handling both client and server side validation concerns. The standard approach for handling validation concerns with ASP.NET MVC is to use Data Annotation attributes. While LightSpeed has its own native validation framework you can use the LightSpeedMvcValidatorProvider which is part of the Mindscape.LightSpeed.Web assembly to automatically supply the appropriate Data Annotation attributes needed to allow automatic validation to be applied by ASP.NET MVC. Mindscape LightSpeed User Guide 121 To make use of this you need to first add a new instance of the LightSpeedMvcValidatorProvider to your ModelValidatorProviders.Providers collection, typically this would be performed in your Global.asax.cs. Initializing the LightSpeed Validation Provider in Global.asax.cs protected void Application_Start() { ModelValidatorProviders.Providers.Clear(); ModelValidatorProviders.Providers.Add(new LightSpeedMvcValidatorProvider()); } The provider does not surface all LightSpeed validations because the Data Annotations framework does not contain equivalents for all of them. The following LightSpeed validations are surfaced: ValidatePresenceAttribute ValidateLengthAttribute ValidateRangeAttribute (int and double ranges only) ValidateFormatAttribute For server side validation you will likely combine the use of a model binder with the integrated LightSpeed validation to check if an entity is in a valid state following a binding operation. Again for this we would check .IsValid. If you are using a model binder as described in the previous section then any errors will be assigned into ModelState so you can display these when you refresh your view. Mindscape LightSpeed User Guide 122 An example of checking IsValid following a binding operation using UpdateModel try { UpdateModel(myEntity); } catch (InvalidOperationException) { updateModelErrored = true; } if (updateModelErrored || !myEntity.IsValid) { // handle validation/binding failure } Samples For example code, see the TV Guide MVC 2 sample or the Deals MVC 3 sample both of which have been installed alongside LightSpeed. Mindscape LightSpeed User Guide 123 Running LightSpeed in Medium Trust LightSpeed can be used in a medium trust environment, but requires some performance optimisations to be turned off, and some design conventions to be modified. Specifically you will need to apply the following changes. Set LightSpeedContext.UseMediumTrustCompatibility to true. This is a static property and therefore applies to all LightSpeedContexts in the AppDomain (trust is set at an AppDomain level, so there would be no point allowing different trust levels on different contexts). If you are writing your model in code, declare special fields such as CreatedOn, and all fields in value objects, to be read-write instead of the normal read-only. (You should still not provide property setters for these fields: application code should still treat them as immutable.) If you are using the designer, set Medium Trust Compatibility to true on the model. This tells the designer to generate special fields and value object fields to be read-write instead of read-only. Mindscape LightSpeed User Guide 124 ASP.NET Dynamic Data Beyond using LightSpeed as the domain model in a web application, your LightSpeed entities can be used to create ASP.NET Dynamic Data sites. To create a Dynamic Data site using LightSpeed: Create an ASP.NET Dynamic Data site using the Visual Studio New Project command. Add a LightSpeed model to the project. Add a reference to Mindscape.LightSpeed.Web.DynamicData.dll. Open the DynamicData > PageTemplates directory. For each .aspx page in this directory: Add the following directive immediately below the @Page directive <%@Register TagPrefix="ls" Assembly="Mindscape.LightSpeed.Web.DynamicData" Namespace="Mindscape.LightSpeed.Web.DynamicData" %> Find any references on the page to asp:LinqDataSource and change them to ls:LightSpeedLinqDataSource. Do not change any attributes or other content. Declare a LightSpeedContext<MyModelUnitOfWork>. This is typically declared as a static read-only field in Global.asax.cs. Example declaration of a static LightSpeedContext in Global.asax.cs public static LightSpeedContext<MyModelUnitOfWork> LightSpeedContext = new LightSpeedContext<MyModelUnitOfWork>("Development"); In Global.asax.cs, locate the section entitled “IMPORTANT: DATA MODEL REGISTRATION” and add the following line in place of the commented-out call to model.RegisterContext: Mindscape LightSpeed User Guide 125 Dynamic Data Model Registration model.RegisterContext( new LightSpeedDataModelProvider<MyModelUnitOfWork>(LightSpeedContext), new ContextConfiguration { ScaffoldAllTables = true, MetadataProviderFactory = t => new EntityDataAnnotationProvider( new AssociatedMetadataTypeTypeDescriptionProvider(t)) } ); Once you have completed these steps your Dynamic Data site will be configured to use the LightSpeed data source. You can then customise the appearance of your site and your entities using the usual Dynamic Data customisation techniques. Handling Associations When presenting associations, ASP.NET Dynamic Data calls ToString() on the associated object. The ToString() implementation in the Entity class returns a generic string which is not very friendly to end users. You should override ToString() in any class you want to display on a Dynamic Data site. Validation By default, ASP.NET Dynamic Data does not recognise LightSpeed validation attributes. Although validations still run, they run only on the server and ASP.NET Dynamic Data does not display validation error messages in a usable form. To enable integrated ASP.NET Dynamic Data validation, including client-side validation and error message display, when registering the data context, set the ConfigurationContext.MetadataProviderFactory to a call back which creates an instance of EntityDataAnnotationProvider. You will need to add a reference to Mindscape.LightSpeed.Web to bring this class into scope. Mindscape LightSpeed User Guide 126 Dynamic Data Model Registration model.RegisterContext( new LightSpeedDataModelProvider<TestUnitOfWork>(LightSpeedContext), new ContextConfiguration { ScaffoldAllTables = true, MetadataProviderFactory = t => new EntityDataAnnotationProvider( new AssociatedMetadataTypeTypeDescriptionProvider(t)) } ); (Please notice the use of AssociatedMetadataTypeTypeDescriptionProvider as an “inner” provider. This is required to enable “buddy classes” to work in cases where these are needed.) EntityDataAnnotationProvider does not surface all LightSpeed validations because ASP.NET Dynamic Data does not contain equivalents for all of them. Validations which are not surfaced still run, but without the integrated user experience. The following LightSpeed validations are surfaced: ValidatePresenceAttribute ValidateLengthAttribute ValidateRangeAttribute (int and double ranges only) ValidateFormatAttribute In addition, EntityDataAnnotationProvider depends on the use of designer naming conventions to locate validations (because LightSpeed validations are specified on fields but must be surfaced on properties). That is, the backing field for each property must have the same name as the property, prefixed with an underscore. If you have hand-coded entities which do not follow this convention, you will need to add Dynamic Data validation attributes to your properties by hand. Mindscape LightSpeed User Guide 127 Building WPF and Windows Forms Applications Desktop client development has some significant architectural differences when compared to Web applications. These differences vary from the nature of scoping your database connections through to data binding concerns and validation behaviours. This section discusses some potential boundaries for a unit of work, tips for dealing with long running units of work and what core LightSpeed features will give you a productivity boost on the desktop. Mindscape LightSpeed User Guide 128 Unit Of Work Scoping The key difference with desktop applications is that there is the potential for them to sit open and active for long periods of time – hours, days or weeks even. In these situations, it’s obvious that you do not want to necessarily be maintaining an open database connection the entire time. It is important to try and identify or create the boundaries that your application will use to scope data access. In a sense, you’re creating the same type of fundamental work patterns as the request/response type nature of the web but based on your own application workflow. The benefits of doing this are that your application code is maintainable and your entity life cycle management is simple to work with. Build with Design Patterns While all applications should use a higher level design pattern for structure such as MVP, MVVM or MVC, it’s doubly important for desktop applications as they otherwise do not enforce any natural boundaries (such as managing the request/response life cycle). It takes discipline to take the freedom of the desktop and impose your own boundaries and stick to them. The Unit of Work design pattern works best when tied to a certain section of actions to be undertaken. In an MVC structured application, for example, this means that while the rest of your application is doing various things, you are likely to only need to start and finish a unit of work in your controller – usually in very short lived manner. A short lived unit of work is the best way to use the pattern. LightSpeed does not enforce that you use these patterns but you will find natural scoping boundaries more easily if you do work with these structural patterns. Working with Dialogs One natural boundary in a desktop application is a pop up window or dialog. Take for example an application where you may wish to edit an employee’s details. The user double clicks an employee, a dialog appears and they can edit any of the employee details. They can then elect to save those changes or cancel the operation. Mindscape LightSpeed User Guide 129 Here’s how you may wish to structure this type of interaction: Controller uses a unit of work to fetch the employee to edit Controller closes the unit of work Controller creates the view and exposes a model which the view can bind against (in this case, we could bind directly to our employee entities properties) At some later stage, after editing, the user clicks “Cancel” then the dialog closes. Or, at some later stage, after editing, the user clicks “Save” then the controller creates a new short lived unit of work, attaches the entity, and saves the changes. Everything is nicely and neatly done! This represents an easy way to work with short lived units of work along with the boundaries enforced by your higher level design pattern to ensure you have a nice and clean way of working with entities. Of course, application specifics may be different – edit inline, certain users can edit only certain fields etc. – but the guidance above is a good starting point no matter what the user workflow. If the user is going to work with a complex graph of objects all within one dialog (or other boundary), you’ll need to be wary of items that could be lazy loaded, or otherwise require an active unit of work to be associated with the entity. If you wish to make use of these capabilities you’ll need to look at using a longer running unit of work so the entity can maintain a connection to the database. See Long Running Units of Work below for guidance on this. Concurrency If you’re developing a multi-user system you will want to think about concurrency. For example, you may enable optimistic concurrency checking and trap that exception on save if another user has edited the same entity between you loading it and saving it. How you deal with such a concurrency violation would be application specific but it is a situation you will need to address. See Concurrent Editing in the chapter Implementing Storage Policies with LightSpeed for more information. Mindscape LightSpeed User Guide 130 Using Refresh Buttons Often when building a desktop application there is the presumption that you can maintain a live data feed in the UI because you are removed from the request/response process. However, it’s wise to avoid this if you can, because rebinding entire grids or views means that you end up fighting the nuances of the user’s state (“they were editing field 3 in row 97 for the employee entity – we need to reset that cell to edit mode after each binding update”). The best solution here is to make liberal use of “refresh” buttons in your application. You retrieve the data once and wait until the user has decided they wish to refresh the information before you actively go looking for changes. Again, this is where the design patterns help out. Having a controller able to call a generic Refresh() method which rebinds the primary UI has the benefit of being easily callable from elsewhere in the controller. For example, if a user elects to edit an employee then when the controller has saved those changes it could also call the Refresh() method to ensure that as the dialog closes the primary UI is also updated to show the changes (and any other changes that other users may have made since the last refresh). Long Running Units of Work If after employing all of these techniques for making your desktop applications you find that there is still a need for a long running unit of work, then there are a number of points to bear in mind: If the database connection is lost the Unit Of Work will fail. To avoid this you may want to use a custom connection strategy to reconnect to the database if the connection fails for whatever reason. See Customising How LightSpeed Connects to the Database in the chapter Building Applications with LightSpeed for more information. Be wary of stale data. Entities held in the unit of work’s identity map or the global second-level cache may become stale with time. Use methods such as UnitOfWork.Detach(Entity) to remove objects from the caches before re-fetching to ensure you are obtaining fresh data. Mindscape LightSpeed User Guide 131 Be wary of concurrency concerns. In a multi-user system it’s important to make sure that data is not lost by saving stale data. LightSpeed always uses optimistic concurrency, so it won’t prevent two users editing the same record at the same time. Use features in LightSpeed such as Optimistic Concurrency Checking to prevent accidental data loss. Mindscape LightSpeed User Guide 132 Entity Support for Rich Client Frameworks LightSpeed support several features which make delivering rich client applications easy and fast. By using LightSpeed your entities automatically get: INotifyPropertyChanged support. All LightSpeed entities implement the INotifyPropertyChanged interface and all properties will notify when changed. This means that when data binding to LightSpeed entities the UI will update automatically when changes are made. IDataErrorInfo support. All entities implement the IDataErrorInfo interface. This is a core .NET Framework interface that provides the functionality to offer custom error information that a user interface can bind to. This means that by using the built in LightSpeed validation capabilities your UI can display validation error automatically if desired. IEditableObject support. All entities implement the IEditableObject interface. This is a core .NET Framework interface that provides functionality to commit or rollback changes to an object that is being used as a data source. IEditableObject is also applied to the EntityCollection type which contains associated entities. All of this support is built into LightSpeed and does not require the developer to do anything special to get these benefits. Mindscape LightSpeed User Guide 133 ClickOnce Deployment The .NET Framework provides ClickOnce deployment to facilitate running rich client applications from an intranet or Internet source. When Visual Studio builds a ClickOnce package, by default it contains your application and the assemblies it depends on, directly or indirectly. Applications which use LightSpeed may therefore by default end up with some ADO.NET providers that they not need. This can increase the download size of your ClickOnce package, and may even cause errors if ClickOnce expects to find a dependency in the client global assembly cache (GAC). For example, Visual Studio by default marks SQL Server Compact as a prerequisite for LightSpeed, even if your application does not use this database. To remove dependencies and prerequisites that your application does not need, open the Project Properties screen, go to the Publish tab, click the Application Files button and set the Publish status to Exclude. Be careful not to exclude assemblies which your application does use! Mindscape LightSpeed User Guide 134 Building Silverlight Applications You can use LightSpeed when developing Silverlight applications through the use of WCF RIA Services to provide access to your data. LightSpeed exposes a custom domain service provider to allow your domain model to be exposed through RIA Services in the standard manner allowing you to focus on consuming that data in your Silverlight client applications. Mindscape LightSpeed User Guide 135 Using WCF RIA Services with LightSpeed To get started with WCF RIA Services with LightSpeed you would start your application in the usual manner, which is to create a standard Silverlight application and then enable WCF RIA Services using the checkbox in the New Silverlight Application dialog. The next step is to set up a domain model. You will start by creating a LightSpeed model and designing it in the normal fashion. For more information on modelling please refer to the chapters on Creating Domain Models and Domain Modelling Techniques. Because we will be shipping our objects over the wire make sure you include references to System.ServiceModel and System.Runtime.Serialization so the appropriate DataContract mark-up is generated for your entities. You will also want to make sure you flag any associations with a DataMember attribute as appropriate to ensure any child collections or related entities are sent to the client along with the parent entity. For more information on how to define this please refer to the chapter on Building Distributed Applications. To expose our model using WCF RIA Services we need to create a domain service. You will need to do this manually rather than using the wizards that are available. Mindscape LightSpeed User Guide 136 Domain Service Basics To start, add a reference to the Mindscape.LightSpeed.ServiceModel.DomainServices assembly, which contains the helper classes you will need to build WCF RIA Services projects with LightSpeed. To create your domain service you will need to base your class on LightSpeedDomainService<TUnitOfWork> which provides the underlying mechanics for handling WCF RIA Services requests. To implement your derived class you will need to implement the CreateLightSpeedContext method to return the strong-typed LightSpeedContext to be used for creating units of work. The domain service caches the LightSpeedContext so you don’t need to worry about ensuring it is a singleton. Declaring a DomainService based on the LightSpeedDomainService<TUnitOfWork> public class ModelDomainService : LightSpeedDomainService<ModelUnitOfWork> { private ModelUnitOfWork _unitOfWork; protected override LightSpeedContext<ModelUnitOfWork> CreateLightSpeedContext() { return new LightSpeedContext<ModelUnitOfWork>("Development"); } public ModelUnitOfWork UnitOfWork { get { if (_unitOfWork == null) { _unitOfWork = CreateTypedUnitOfWork(); } return _unitOfWork; } } } Surfacing the Domain Entities Once you have implemented the basics for the domain service you will want to expose any entities which are to be handled at the client. This needs to be done not only for Mindscape LightSpeed User Guide 137 the primary entities which are being exposed but also for any related objects which we will be accessing. To expose an entity you must at a minimum define a queryable method for that entity type. This method must be marked with the[Query] attribute. If you are intending to perform updates, you will also want to expose Insert, Update and Delete methods for that entity type. Below is an example of a set of methods defined for a Film entity. Note the [Query] attribute on the query method. Declaring the CRUD methods for a Film entity [Query] public IQueryable<Film> GetFilms() { return UnitOfWork.Films; } public void InsertFilms(Film film) { } public void UpdateFilms(Film film) { } public void DeleteFilms(Film film) { } Generating and Using the Silverlight Data Source Once you have defined your entities as part of the domain service you can re-compile your solution and WCF RIA Services will code generate proxy classes in your Silverlight application to allow for remote data access to occur. You will now be able to find your exposed entities under the Data Sources tab, for example if we exposed Film and Cinema entities then the view would be as below. Mindscape LightSpeed User Guide 138 Now that your data is exposed you can continue programming normally and build up your Silverlight application. All other standard Silverlight/WCF RIA Services approaches can be applied such as exposing validation concerns by adding metadata classes to your entities, or exposing custom methods as part of the domain service. Mindscape LightSpeed User Guide 139 Building Distributed Applications LightSpeed provides you with a number of options for building distributed applications, whether it is just providing access to your data to remote users or implementing a fully connected client/server style implementation. When thinking about how you will design your system you will generally arrive at one of three approaches. You want to expose your data in a custom way. Rather than exposing the entities themselves to external consumers you want to describe Data Transfer Objects (DTOs) which will be populated from the data in one or more of your entities. You want to expose your entities directly, either through an RPC style API or via XML or JSON over a RESTful interface. You want to use LightSpeed from your client application but connect to a service endpoint rather than the database directly. LightSpeed supports all three of these approaches. Lastly, LightSpeed also provides support for exposing LightSpeed entities through RIA Services and Dynamic Data. You can find more information about building applications with RIA Services in the chapter on “Building Silverlight Applications” and you can find more information about building applications with ASP.NET Dynamic Data in the chapter on “Building Web Applications”. Mindscape LightSpeed User Guide 140 Distributed Entity Programming If the design of your distributed system is intending to provide a tight coupling between client and service by sharing your domain model then you will want to expose your entities directly either by hand crafted WCF services or by using a DistributedUnitOfWork to expose your service based UnitOfWork to your clients. If you are intending to enforce a clean separation across service boundaries then you will want to implement an approach using Data Transfer Objects and should review the associated section below on using Data Transfer Objects with LightSpeed. The Distributed Unit of Work In our chapter on Basic Operations we introduced the unit of work. A unit of work is used to scope your business transactions and manage loading, tracking and persisting entities. In a distributed scenario you are disconnected from the database so a LightSpeed unit of work is no longer valid, however from a design perspective the pattern remains valid. So to assist with distributed scenarios LightSpeed has a distributed version of the unit of work which allows the client to locally track objects and then serialize changes back to the server when changes are to be persisted. This distributed version of the unit of work, called DistributedUnitOfWork is found in the Mindscape.LightSpeed.ServiceModel assembly. You create a DistributedUnitOfWork instance in a similar fashion to instantiating a standard unit of work. This then creates a scope which will delegate its operations to a remote unit of work located at a known service endpoint. This affords you the convenience of programming as if you were dealing with a local unit of work while being disconnected from the database. Mindscape LightSpeed User Guide 141 Instantiating a DistributedUnitOfWork instance // DistributedModelUnitOfWork is a strongly typed DistributedUnitOfWork class var context = new LightSpeedContext<DistributedModelUnitOfWork>() { // Setting the UnitOfWorkFactory is required to generate DistributedUnitOfWork // instances (instead of base UnitOfWork instances) // // DistributedUnitOfWork handles the underlying transport concerns to connect // to the service UnitOfWorkFactory = new DistributedUnitOfWorkFactory<DistributedModelUnitOfWork>() }; using (var unitOfWork = context.CreateUnitOfWork()) { // you would perform your querying and persistence as normal } A distributed unit of work is represented in LightSpeed as the IDistributedUnitOfWork interface. IDistributedUnitOfWork mirrors the IUnitOfWork interface and provides an identical set of operations which allow you to load, add and remove entities, and to save pending changes. IDistributedUnitOfWork is only limited on operations which deal with database specific entities such as IDbReader and IDbCommand; in cases where functionality is not supported a NotSupportedException will be thrown and these specific methods have been noted in the XML documentation for the interface. To create a DistributedUnitOfWork a LightSpeedContext object must be instantiated with the UnitOfWorkFactory set to a new instance of a DistributedUnitOfWorkFactory class. The factory is used to determine how to connect to the service endpoint using WCF and by default will initialize itself using application configuration based on the endpoint named LightSpeedDistributedUnitOfWorkEndpoint . Alternatively the factory can be instantiated with a WCF EndpointAddress and Binding if you require runtime configuration. The DistributedUnitOfWorkService When the DistributedUnitOfWork runs on the client, there needs to be a service endpoint capable of servicing requests targeting the model being used by the client. To host this there is a DistributedUnitOfWorkService class provided which allows you to host an endpoint using WCF. Mindscape LightSpeed User Guide 142 There are two ways this could be hosted, either you will manually host an actual instance of the service within a dedicated process, for example a Console application or a Windows Service; or you will have the service activated by IIS using Windows Activation Services and a .svc file hosted in an ASP.NET website. Both approaches are supported. Manually Hosting the Service To host the service manually you need to first create a UnitOfWork instance which will be hosted by the service and then instantiate the service instance and pass that to the constructor of a WCF ServiceHost. This will create a single instance of the service hosting a single UnitOfWork which will be shared among all callers. Manually hosting a DistributedUnitOfWorkService in single instance mode var context = new LightSpeedContext<FilmFestival.Model.ModelUnitOfWork>("default"); using (var unitOfWork = context.CreateUnitOfWork()) { using (var host = new ServiceHost(new DistributedUnitOfWorkService(unitOfWork))) { host.Open(); Console.WriteLine("Host has started - press ENTER to shut down"); Console.ReadLine(); } } You will also need to specify configuration for the endpoint as part of your system.serviceModel configuration section. Here is an example using a basicHttpBinding. The contract which is used is the Mindscape.LightSpeed.ServiceModel.IDistributedUnitOfWorkContract. Mindscape LightSpeed User Guide 143 Example configuration for a DistributedUnitOfWorkService hosted in single instance mode <system.serviceModel> <services> <service name="Mindscape.LightSpeed.ServiceModel.DistributedUnitOfWorkService"> <endpoint address="http://localhost:3000/UnitOfWork" binding="basicHttpBinding" contract="Mindscape.LightSpeed.ServiceModel.IDistributedUnitOfWorkContract"/> </service> </services> </system.serviceModel> If you wish to have a new instance of the DistributedUnitOfWorkService created on a per WCF instance basis, then you will need to subclass DistributedUnitOfWorkService, providing the appropriate LightSpeedContext as a constructor argument to the base class. An example of how to implement this is shown below. Manually hosting a DistributedUnitOfWorkService in multi instance mode public class MyUnitOfWorkService : DistributedUnitOfWorkService { public MyUnitOfWorkService() : base(new LightSpeedContext("Development")) { } } using (var host = new ServiceHost(typeof(MyUnitOfWorkService))) { host.Open(); Console.WriteLine("Host has started - press ENTER to shut down"); Console.ReadLine(); } As with hosting in single instance mode you will need to specify configuration about the endpoint. Here is an example. The key difference from the example above is that we need to ensure our service type name is specified in the name attribute for the service configuration. The address, binding and contract are all the same. Mindscape LightSpeed User Guide 144 Example configuration for a DistributedUnitOfWorkService hosted in multi instance mode <system.serviceModel> <services> <service name="Mindscape.LightSpeed.Samples.MyUnitOfWorkService"> <endpoint address="http://localhost:3000/UnitOfWork" binding="basicHttpBinding" contract="Mindscape.LightSpeed.ServiceModel.IDistributedUnitOfWorkContract"/> </service> </services> </system.serviceModel> Supported Bindings Because the DistributedUnitOfWork has been built on WCF it should support any of the standard transports and bindings available within WCF. From a support perspective however the implementation has been developed and tested on only the Named Pipe, TCP and HTTP bindings. Configuring Your Client for a DistributedUnitOfWork Before you are able to receive results on your client you first need to configure the client to use a DistributedUnitOfWork instead of a standard UnitOfWork. While the configuration for a standard UnitOfWork revolves around assigning the connection string and database provider, the configuration required for a DistributedUnitOfWork focuses on the details of the endpoint that it needs to connect to. The primary aspect of configuration that is required to use a DistributedUnitOfWork is to override the UnitOfWorkFactory property on your context with an instance of a DistributedUnitOfWorkFactory class. Any runtime details about the endpoint can be configured through the constructor for this class, otherwise it is assumed you have a system.serviceModel endpoint named LightSpeedDistributedUnitOfWorkEndpoint configured. Mindscape LightSpeed User Guide 145 Example of creating a DistributedUnitOfWorkFactory class and a DistributedUnitOfWork var context = new LightSpeedContext<DistributedModelUnitOfWork>() { UnitOfWorkFactory = new DistributedUnitOfWorkFactory<DistributedModelUnitOfWork>() }; using (var unitOfWork = context.CreateUnitOfWork()) { // use as normal, UnitOfWork will be a DistributedModelUnitOfWork instance } Example configuration you would specify in system.serviceModel <system.serviceModel> <client> <endpoint name="LightSpeedDistributedUnitOfWorkEndpoint" address="http://localhost:3000/UnitOfWork" binding="basicHttpBinding" contract="Mindscape.LightSpeed.ServiceModel.IDistributedUnitOfWorkContract"/> </client> </system.serviceModel> Example of creating a DistributedUnitOfWorkFactory with runtime configuration about the endpoint var context = new LightSpeedContext<DistributedModelUnitOfWork>() { UnitOfWorkFactory = new DistributedUnitOfWorkFactory<DistributedModelUnitOfWork>(binding, address) }; Mindscape LightSpeed User Guide 146 Example of creating a DistributedUnitOfWorkFactory with a typed model public interface IMyTypedUnitOfWork { System.Linq.IQueryable<Member> Members{ get; } } public class MyDistributedUnitOfWork : DistributedUnitOfWork, IMyTypedUnitOfWork { public MyDistributedUnitOfWork() : base() { } public MyDistributedUnitOfWork(LightSpeedContext context) : base(context) { } public System.Linq.IQueryable<Member> Members { get { return this.Query<Member>(); } } } var context = new LightSpeedContext<MyDistributedUnitOfWork>() { UnitOfWorkFactory = new DistributedUnitOfWorkFactory<MyDistributedUnitOfWork>() }; Executing Operations at the Client Once you have configured your client to use a DistributedUnitOfWork you can create a UnitOfWork in the standard manner by calling LightSpeedContext.CreateUnitOfWork(). Subsequent calls to execute queries will trigger a service call as will calls to SaveChanges(). Data Contracts If the project containing your LightSpeed model references System.ServiceModel then the Visual Studio designer will automatically emit DataContract and DataMember attributes to mark up your model classes allowing them to be correctly serialized by the WCF DataContract formatter. Mindscape LightSpeed User Guide 147 By default all value properties are marked with a DataMember attribute including foreign key identity fields, but all associations are omitted from serialization. This is to allow you to more correctly specify what structures should be serialized in distributed scenarios rather than potentially opting in the whole domain model for serialization. It is highly recommended that you review which associations should be serialized when designing your model for distributed scenarios. Building WCF Services using Entities If you don’t wish to use a DistributedUnitOfWork to remote your entities between your client and service then the other approach which can be used is to expose your entities using a hand crafted WCF service. Review the section above regarding Data Contracts and ensure that you have designed your model for the distributed scenarios you wish to support. Unlike with a Data Transfer Object approach, because you have a single model to work with you will not be able to tailor your messages on a per request basis so we would recommend that you avoid prematurely opting in associations to your DataContracts. If you choose to include EntityCollections into serialization you will need to use a special version of the DataContract formatter known as the NetDataContractFormatter which allows correct serialization of collection types which have been marked with a DataContract attribute. Samples There are two samples available which make use of the DistributedUnitOfWork which will give you a practical view of how the DistributedUnitOfWork can be used across the two standard types of applications you are likely to be using it with. The first sample is the ATM sample within which the Teller project is an example of a WPF application which uses the DistributedUnitOfWork as part of a long running stateful client application. The second sample is the Film Festival sample within which the Website uses the DistributedUnitOfWork in a per request fashion as part of a stateless ASP.NET MVC web application. Mindscape LightSpeed User Guide 148 For a sample which demonstrates building a hand crafted WCF service which exposes entities then you should review the ATM sample. The ATMClient console application project makes use of several service calls which deal with entities which have been shared with the Website service. Mindscape LightSpeed User Guide 149 Building WCF Services using Data Transfer Objects If the design of your distributed system is intending to provide a loose coupling between client and service by sharing contracts then you will want to build your system using Data Transfer Objects to enforce a clean separation across service boundaries. You will then want to be able to map your domain entities to and from these Data Transfer Objects. If you are intending to use a tight coupling where you share the details of your domain model across the service boundary then you should review the associated section above on Distributed Entities. The design of your contracts is beyond the scope of this document and is not specifically a role which LightSpeed provides. However, we do provide assistance around importing and exporting your entities. Here is a basic example which is based around this LightSpeed model containing relationships between Contributions, Comments and Members of a system. Mindscape LightSpeed User Guide 150 Given this model we might wish to represent a data transfer object which provides the details of a member to form part of our published service contract. Example of a Data Transfer object which would represent a member [DataContract] class ApplicationMember { [DataMember] public int Id { get; set; } [DataMember] public string UserName { get; set; } [DataMember] public string Email { get; set; } } We need to achieve several steps to deal with data transfer objects within our system. We need to project LightSpeed Member entities into ApplicationMember instances so we can send these objects out of our system and over the wire. We need to allow inbound ApplicationMember to be mapped back to Member entities. We need to support both creating new instances and updating existing instances. The first step can be achieved by either creating a mapping function or by using LINQ to project the data as required. Assuming you are able to use LINQ then this would be the preferred approach. Example of projecting Member entities to ApplicationMember instances var members = unitOfWork.Members.Select(m => new ApplicationMember() { Id = m.Id, Email = m.Email, UserName = m.UserName }).ToList(); Mindscape LightSpeed User Guide 151 The second and third steps can be achieved by using IUnitOfWork.Import which provides functionality for mapping arbitrary data transfer objects back against the entity store. It will conditionally create new entities or update existing entities based on the value of the Id property held on the data transfer object. Calling IUnitOfWork.Import members.ForEach(m => unitOfWork.Import<Member>(m)); In our example the call to the Import method returns the actual Member entity instance that was attached to the UnitOfWork. As with the example above we do not make use of this, but if you need to perform any subsequent processing against the entity then it is available for use. The default mapping approach used by the Import method comes with two caveats. 1. We expect that the data transfer object will have an Id property which we can use to check if there is already an entity with that Id in the database, and load it if that is the case so we can perform an update. If no Id property is present then that aspect of the mapper will be ignored and you will always be dealing with new entities. 2. The mapper will use reflection to assign properties of the data transfer object which exactly match the value fields on the entity. In the example you will notice that ApplicationMember has a case sensitive match with the Member entity for the property UserName. If the property does not exactly match, then assignment will not happen. Providing Custom Mapping for IUnitOfWork.Import The default mapping approach provided with IUnitOfWork.Import will work well for one to one style mapping and constrains you to the use of a key property to ensure you get conditional insert/update behaviour. Lastly associations are also not currently traversed as part of the mapping so in the earlier example if an ApplicationMember held Contribution and Comments collections then these would not be imported back into the UnitOfWork. Mindscape LightSpeed User Guide 152 If you need finer control over the behaviour of the mapping then IUnitOfWork.Import provides an overload which allows you to provide a custom function which allows you specifically control how mapping occurs. The mapping function takes two arguments, the generic type argument and the source object we are passing to be imported. An example of a custom mapping function used with IUnitOfWork.Import members.ForEach(m => unitOfWork.Import<Member>(m, (t, o) => { var am = o as ApplicationMember; var member = unitOfWork.Members.SingleOrDefault(m2 => m2.Id == am.Id); member.UserName = am.UserName; return member; })); Compatibility with LightSpeed 3 Generated DTOs As part of LightSpeed 3 we offered code generation templates which would generate you some 1:1 style DTO’s and associated mapping helper methods. These have been deprecated however the code generation will still occur for these but the code itself now relies on the presence of a compiler conditional to be included. If you wish to continue using these templates then you must set the LS3_DTOS compiler conditional in the project containing your model file. Mindscape LightSpeed User Guide 153 Samples If you wish to review a practical sample which uses Data Transfer Objects with LightSpeed then you should review the ATM sample within which the ATMClient console application project makes use of several service calls which rely on DTOs which have been defined in the Contracts project. Mindscape LightSpeed User Guide 154 Testing and Debugging Testing and debugging are essential parts of the software development lifecycle. LightSpeed has been designed with testability in mind, and provides several features to facilitate testing, debugging and tuning a LightSpeed model or application. Mindscape LightSpeed User Guide 155 Unit Testing When undertaking unit tests it’s essential that the tests be repeatable. This means that developers need to ensure that the database is in an expected state at the beginning of each test run. To achieve this, tests can either be run against a real database with transactions or run against fake objects in memory. This guide explores both approaches. Sample code uses the NUnit unit testing product, but can easily be adapted to any unit testing framework. Using a Real Database Setup a test version of your database. Typically we have set up two databases for a project: ApplicationDatabase ApplicationDatabase_Test All of the test data will be stored in the ApplicationDatabase_Test. Next, create a transaction test fixture. The benefit of this is that all our test fixture classes can derive from the custom test fixture class and be ready with the transactional pattern for testing in place. Mindscape LightSpeed User Guide 156 An example of the transactional TestFixtureBase [TestFixture] public abstract class TestFixtureBase { private static readonly LightSpeedContext<MyModelUnitOfWork> _context = new LightSpeedContext<MyModelUnitOfWork>(); static TestFixtureBase() { // configure LightSpeed _context.ConnectionString = @"Data Source=ORMapper;Initial Catalog=ORMapper; Integrated Security=SSPI"; _context.Logger = new ConsoleLogger(); } private TransactionScope _transactionScope; private MyModelUnitOfWork _uow; [SetUp] public virtual void SetUp() { _transactionScope = new TransactionScope(); _uow = _context.CreateUnitOfWork(); } [TearDown] public virtual void TearDown() { _uow.Dispose(); _transactionScope.Dispose(); } } In this code all tests are run within a TransactionScope and the changes are rolled back upon completion of the test. Additionally, note that in the SetUp method we are creating a new unit of work and in the TearDown method we are destroying it by calling IUnitOfWork.Dispose. The creation of new units of work for each test is important because we want complete test isolation; that is, no test can affect the result of any other test. We achieve this isolation by having each test run in a new unit of work. Once this is done the development of the actual tests can commence. Mindscape LightSpeed User Guide 157 A simple unit test [Test] public void UpdatingContributionDescription() { var contribution = _uow.Contributions.Single(c => c.Id == 1); contribution.Description = "Some new description"; _uow.SaveChanges(true); var updatedContribution = _uow.Contributions.Single(c => c.Id == 1); Assert.AreNotSame(contribution, updatedContribution); Assert.AreEqual("Some new description", updatedContribution.Description); } This test finds the Contribution with Id 1 and then updates its Description attribute. We then call SaveChanges to flush our pending update to the database. Notice we pass true to the SaveChanges method. This is an idiomatic LightSpeed unit test pattern applicable whenever we want to verify a database update was made correctly. The SaveChanges method causes all pending updates to be flushed to the database, and the true argument signals that LightSpeed should additionally clear the identity map – the unit of work’s cache of the entities it is managing, which serves to prevent us loading the same Entity more than once. By clearing the identity map, we guarantee that subsequent requests for previously loaded objects hit the database and therefore allow us to validate that the database state of an entity is as we expect. Using Fakes In situations where in memory objects would be preferred there are helper classes included in LightSpeed to create fake entities and a test unit of work. LightSpeed includes two classes to help support faking queries and entities: TestUnitOfWork and EntityFactory. Both of these are in the Mindscape.LightSpeed.Testing namespace. TestUnitOfWork allows you to inject a fake result for a query. To do this, call the TestUnitOfWork.SetCollectionResult method, passing in the desired fake result. Subsequent calls to Find or FindBySql, or LINQ entity queries, will return the fake result Mindscape LightSpeed User Guide 158 rather than querying the database. TestUnitOfWork also allows faking of other query methods: Call TestUnitOfWork.SetSingleResult to specify a fake result for FindOne or FindById Call TestUnitOfWork.SetExpectedCountResult to specify a fake result for Count, or the LINQ Count() and LongCount() operators Call TestUnitOfWork.SetExpectedCalculateResult to specify a fake result for Calculate, or for LINQ aggregate operators such as Sum() Call TestUnitOfWork.SetProjectionResult to specify a fake result for Project, or for LINQ projection queries (where specific properties are selected into a named or anonymous type) Call TestUnitOfWork.SetSearchResult to specify a fake result for Search Note that fake results are returned directly – any query expression or LINQ Where operator is not applied! Your test fixture should set up the expected fake results. Creating a TestUnitOfWork and adding then selecting a fake entity [Test] public void FetchObjectByIdFromTestUnitOfWork() { var uow = new TestUnitOfWork(); var entity1 = EntityFactory.Create<Comment>(EntityState.Default, 1000); uow.Add(entity1); Assert.IsNotNull(uow.FindById<Comment>(1000)); } EntityFactory helps you to create entities with predictable ID values and in states other than the New state. Thus, you can use EntityFactory to fake entities that have been loaded from the database (and therefore begin in the Default state). Using Mocks If you are interested in interaction based unit testing (often characterized by the use of mocks, fakes or stubs), LightSpeed exposes a few key interfaces that facilitate such an Mindscape LightSpeed User Guide 159 approach. The primary extension point providing substitutability in LightSpeed is the IUnitOfWork interface. Implementing (or mocking) IUnitOfWork provides complete control over all major LightSpeed operations such as querying and persistence. In order to make your test context use your mock IUnitOfWork, create an implementation of IUnitOfWorkFactory that returns your custom implementation and then assign your custom IUnitOfWorkFactory to the UnitOfWorkFactory property found on the LightSpeedContext object. Some places in LightSpeed internally require a UnitOfWorkBase, not just an IUnitOfWork. Consider mocking UnitOfWorkBase rather than the interface, or using the TestUnitOfWork fake instead. Mindscape LightSpeed User Guide 160 Logging To aid in development, LightSpeed can be configured to log diagnostic messages. The logger is any object that implements the ILogger interface: this gives you the flexibility to log according to the requirements of the application and environment. For example, you can log to the Visual Studio Output window during development, then turn off logging in production. Or you could create a custom logger which would allow you to selectively turn on logging for production diagnostics. Built-In Loggers The framework provides two built-in loggers: ConsoleLogger writes logs to the console window. TraceLogger writes logs to all application trace listeners. Trace listeners are defined by the .NET Framework and can be specified in the configuration file via the <system.diagnostics> element. The .NET trace infrastructure also supports filtering of messages. Both built-in loggers log both SQL and debug messages. Enabling Logging To enable logging, set LightSpeedContext.Logger in code, or the loggerClass attribute in configuration. When setting the logger class in configuration, you must provide a full assembly-qualified type name. Enabling logging in configuration <add name="Test" loggerClass="Mindscape.LightSpeed.Logging.TraceLogger, Mindscape.LightSpeed" /> Mindscape LightSpeed User Guide 161 Enabling logging in code _context.Logger = new TraceLogger(); You can display additional logging information by setting LightSpeedContext.VerboseLogging. This is not available in configuration because it should be set only during debugging. Disabling Logging To disable logging after it has been turned on, set LightSpeedContext.Logger to null. _context.Logger = null; Building a Custom Logger In addition to the built-in loggers, you can create your own custom logger. You could do this for example to integrate with your application’s wider logging infrastructure, to take advantage of logging platforms such as log4net, or to support finer control over logging. To create a custom logger, implement the ILogger interface. ILogger has two methods: LogSql and LogDebug. LightSpeed calls LogSql for each SQL statement or batch sent to the database. It calls LogDebug primarily for performanceoriented messages such as cache hits and misses and database access times. LogSql and LogDebug are declared as receiving values of type object. The exact data that LightSpeed passes to these methods may change, but you can safely call ToString on these objects to get readable log text. In the LogSql method, you may also attempt to cast the object to CommandLog. If the object is a CommandLog then you can access individual properties in order to log them individually or log them in a more structured way such as to a database. Mindscape LightSpeed User Guide 162 A custom logger which uses the application’s logging infrastructure public class IntegratedLogger : ILogger { private void LogToApplicationLog(string text) { // passes text to whatever logging service or platform the application uses } public void LogSql(object sql) { LogToApplicationLog(sql.ToString()); } public void LogDebug(object text) { LogToApplicationLog(text.ToString()); } } Note that the VerboseLogging setting does not affect what information is available in the CommandLog object. CommandLog always contains full logging information. VerboseLogging affects only how much information is included in CommandLog.ToString(). Column Name Logging For each logged data parameter the column name is also logged. Mindscape LightSpeed User Guide 163 Profiling You can use the logging infrastructure to perform simple profiling. Query Patterns You can review the SQL logs to determine what queries LightSpeed is issuing and to look for inefficient patterns such as N+1 lazy loads. When looking for performance bottlenecks, it can be wise to identify a piece of code you want to profile, and turn logging off after executing that code – otherwise you may end up with a very large SQL trace, making it to track down whether you have traced a single inefficient run of code or a large number of efficient runs! Timing Queries When LightSpeed logs a SQL statement or batch, it also prints out the time taken to execute that statement. You can also access the time taken directly using the CommandLog.TimeTaken property. Using a Custom Logger for Profiling In some cases you may be able to use a custom logger to search for patterns or problems by drilling into the CommandLog object. For example, to detect poorlyperforming queries which might be candidates for tuning at the database level or refactoring into stored procedures, you could create a custom logger which logs only queries that take a long time to run. Or to detect N+1 problems you could create a logger which logs SQL statements on a per unit of work basis, and highlights where a unit of work has executed more statements than a threshold. Mindscape LightSpeed User Guide 164 A custom logger which uses CommandLog properties to detect slow queries public class AlertingLogger : ILogger { public void LogSql(object sql) { CommandLog commandLog = sql as CommandLog; if (commandLog != null) { if (commandLog.TimeTaken > LongQueryThreshold) { SendLongQueryAlert(commandLog); } } } public void LogDebug(object text) { /* do nothing * } } Using Profilers with the Interception API LightSpeed 5 introduces a dedicated API for profiling internal performance. This exposes LightSpeed events to external profiling tools, such as the ASP.NET MVC MiniProfiler, allowing them to intercept and modify the events. The API is available in the Mindscape.LightSpeed.Profiling namespace, which contains the Interceptor class. To connect a profiler to LightSpeed, you need to provide an implementation of this, such as in the figure below. Mindscape LightSpeed User Guide 165 An interceptor that sends LightSpeed events to ASP.NET MVC Mini-Profiler public class MiniProfilerInterceptor : Interceptor { public override IDbConnection CreateConnection(Func<IDbConnection> baseCreator) { var realConnection = baseCreator(); return ProfiledDbConnection.Get((DbConnection)realConnection, MiniProfiler.Current); // or return new ProfiledDbConnection(...) depending on which version you are using } public override IDbCommand CreateCommand(Func<IDbCommand> baseCreator) { var realCommand = baseCreator(); return new ProfiledDbCommand((DbCommand)realCommand, null, MiniProfiler.Current); } } You can then attach the profiler through the web.config using the new interceptorClass attribute. Alternatively, this can be done by setting the LightSpeedContext.Interceptor property. In web.config <lightSpeedContexts> <add name="Development" interceptorClass="LightSpeedMiniProfilerInterceptor.MiniProfilerInterceptor,LightSpeedMin iProfilerInterceptor" /> </lightSpeedContexts> Mindscape LightSpeed User Guide 166 Domain Modelling Techniques LightSpeed aims to enable you to model your domain in as natural a way as possible. This means allowing you to use object-oriented constructs such as inheritance, to represent ‘kind of’ relationships between entity classes, and value objects, to assign business semantics to an attribute or group of attributes. This chapter shows how to use these object-oriented techniques and how they map to relational database storage. Mindscape LightSpeed User Guide 167 Inheritance Inheritance, when used wisely, can greatly assist in developing expressive, maintainable domain models that neatly capture potentially complex domain behaviours. LightSpeed supports inheritance through the popular single table inheritance and class table inheritance patterns, which encapsulate different ways of mapping an inheritance hierarchy to a relational model. Regardless of the database mapping, the inheritance relationship is modelled using the familiar .NET inheritance feature. To derive one entity class from another: Designer: Choose the Inheritance arrow from the toolbox and drag an arrow from the derived to the base entity; or select the derived entity, go to the Properties grid, and set its Base Class to the desired entity type. Code: Use the normal C# or Visual Basic inheritance syntax. Discriminators The key aspect of implementing single or class table inheritance is to define a “discriminator” column. A discriminator column is simply a column used by LightSpeed to determine the type of entity to instantiate when loading a row from the underlying table. Quite often this column is also a foreign key to an associated reference data table. E.g. Employee has an EmployeeTypeId. Every inherited class in single or class table inheritance must specify a discriminator column. All classes in an inheritance hierarchy must specify the same discriminator column. Furthermore, each class in the hierarchy (except the root class) must specify a discriminator value. This tells LightSpeed, “If the discriminator column contains this value, materialise the row as this type of entity.” Each class must therefore specify a different discriminator value. If LightSpeed encounters a row which doesn’t match any of the derived class discriminator values, it treats it as an instance of the root class. Mindscape LightSpeed User Guide 168 To specify the discriminator for a derived class: Designer: Select the inheritance arrow from the derived to the base class, and fill out the Discriminator Name, Discriminator Type and Discriminator Value settings. If you set the Discriminator setting on the root class, LightSpeed will default the name and type for you. Code: Apply DiscriminatorAttribute to the derived class, specifying the discriminator attribute name and the value that identifies this class. Single Table Inheritance By default, LightSpeed maps inheritance hierarchies to the database using the popular Single Table Inheritance pattern (STI). As the name suggests, the key idea behind this pattern is that all of the data for a particular inheritance hierarchy is stored in a single table. As with most design patterns there are various trade-offs associated with the STI pattern, the most obvious being the classic time/space tradeoff. The STI pattern trades space for time – by storing the data in one table, querying and persisting the data becomes inherently simple and efficient. However, for hierarchies where the entities have different attributes, the underlying table may become sparsely populated. That said, most modern database systems are reasonably good at optimizing unused table space. Mindscape LightSpeed User Guide 169 You don’t need to do anything special to specify single table inheritance: if you follow the steps above, single table inheritance is what you will get. Class Table Inheritance When an inheritance hierarchy contains derived classes with a lot of state that is specific to those derived classes, single table inheritance results in an unnatural database design where the table contains many columns that are applicable only to specific subclasses. For situations like these, LightSpeed also supports Class Table Inheritance. In this mapping mode, there is a separate table for each class in the hierarchy, with each table containing columns only for the fields introduced in that class (plus an Id column). This provides a more natural database design at the expense of producing more complex and therefore less efficient queries (because LightSpeed must join the tables on each query). Here is how a class table inheritance structure appears in the domain model, and how it maps to the database. Class table inheritance is set up in much the same way as single table inheritance: you must provide a “discriminator” column on the table corresponding to the root class, and specify a discriminator attribute and value on each derived class. In addition, you must specify class table inheritance: Mindscape LightSpeed User Guide 170 Designer: For each inheritance arrow in the hierarchy, set Inheritance Type to ClassTableInheritance. Code: Apply InheritanceMappingAttribute to the root class of the hierarchy, with an InheritanceMappingKind of ClassTableInheritance Implementing Common Services in a Base Class LightSpeed supports a third form of inheritance, known as concrete table inheritance, which is not discriminated. In concrete table inheritance, each entity type maps to a table, and that table contains columns for each field on the entity type, including inherited ones. Concrete table inheritance is not polymorphic, so you cannot have associations to a base class or perform queries for a base class. As a general rule, any class that is a base class for concrete table inheritance should be abstract. Concrete table inheritance is useful when: You have a few fields that appear in every table in your database, and want to save re-entering them on each entity class. You have some common services such as helper methods which need access to class internals (and therefore cannot be extension methods), but do not contribute fields to the entity. You don’t need to load objects polymorphically in a single query. Which Should I Choose? If you need polymorphism but your derived classes don’t introduce extra state (only behaviour), or at least not too much extra state, use single table inheritance. Class table inheritance is a good fit when you need polymorphism, but have a lot of columns that make sense only for child entities. However, class table inheritance can introduce a significant performance penalty due to the need to perform joins in the database query, and to update multiple tables when saving, especially if you have a very deep or wide hierarchy. In addition, class table inheritance imposes some limitations (for example around cascade deletes) that are not an issue for single table Mindscape LightSpeed User Guide 171 inheritance. Therefore, you should prefer single table inheritance over class table inheritance unless the derived classes add a lot of new columns. If you don’t need polymorphism, you should use concrete table inheritance because it provides the best performance and most natural database mapping. Mindscape LightSpeed User Guide 172 Value Objects Many attributes of an entity can be represented by primitive values such as integers or strings. However, it’s sometimes useful to represent an attribute by a type with more specific meaning. Such an attribute might be a single column, for example representing a Salary column by a Money type instead of Decimal, or it might be multiple columns, for example representing LocationX and LocationY columns by a Point or Position type instead of a two separate Doubles. In these examples, Money, Point and Position are value objects. They are not entities, because they do not have identity of their own: they are just a way of representing entity attributes in a more business-meaningful way. Value objects are idiomatic in domain-driven design and are discussed in detail in Eric Evans’ book of the same name. Defining a Value Object Type in the Designer To define a value object type in the designer, drag a Value Object icon from the Toolbox into the model. You can name the type and add properties just as you do with entities. Notice that a value object type does not have a base class or identity type, because a value object represents an attribute of an entity, and does not have identity in itself. Creating a Value Object Member in the Designer Once you have defined a value object type, you can create an entity property of that type by choosing the Value Object Property connector from the Toolbox and dragging a line from the entity to the value object type. You can edit the name of the property through the line label or the Name property. Mindscape LightSpeed User Guide 173 Value Objects in Database-First Development Relational databases don’t have a concept of value objects, so when you drag a table onto the designer, LightSpeed can’t automatically infer value objects from the table schema. After dragging the table on, however, you may identify a set of columns which you want to represent as a value object member. If these columns’ names have a common prefix (see Value Object Database Mappings below) then you can extract them to a value object by selecting the columns, right-clicking the multiple selection and choosing Extract to Value Object. Depending on whether a value object type suitable for mapping these columns is already defined, the designer will offer to map the columns to an existing value object type, to add the columns to an existing value object type, or to create a new value object type (and a member of that type). Defining a Value Object Type in Code At the code level, a value object type is just an ordinary CLR type – class or struct. As with entities, LightSpeed is interested in the fields of the type, not the properties: therefore, you must not use automatic properties (because the backing field for an anonymous property does not have the ‘right’ name for LightSpeed to map it). Value object types should be immutable. All fields should be read-only, and the wrapper properties should be get-only. Mindscape LightSpeed User Guide 174 A simple value object type public class Money { private readonly decimal _amount; // Fields should be read-only public Money(decimal amount) { _amount = amount; } public decimal Amount { get { return _amount; } } // Do not use automatic properties } Creating a Value Object Member in Code Fields of value object type are defined in the same way as fields of primitive types, except that you must apply ValueObjectAttribute to the field. A member of Money type public class Employee { [ValueObject] private Money _salary; public Money Salary { get { return _salary; } set { Set(ref _salary, value); } } } Setting Value Object Properties Value objects are immutable. This means you cannot modify the properties of the value object directly. Mindscape LightSpeed User Guide 175 employee.Salary.Amount = 50000; // Compiler error – Amount property is read-only The reason for this is that value objects represent values. Suppose you have an Employee entity with a Salary of $50000. If the Employee gets a raise, then their Salary changes to a new value of $60000. It would be wrong to think that $50000 has mutated into $60000. $60000 is a different value from $50000, so it must be a different instance. (Remember, value objects don’t have identity.) Even if you created a mutable value object, you could not set properties this way, because value objects don’t have access to the Entity.Set method which is essential for notifying LightSpeed of changes needing saving – not to mention for UI interfaces such as IEditableObject and INotifyPropertyChanged. So when you set a value object property, you must always set it to a new instance of the value type. Setting value object properties employee.Salary = new Money(50000); hq.Location = new Position(-41.289, 174.777); A consequence of this is that when using data binding or data grids you must provide a way to edit your custom value types. In the Location example, the entity has a single Location, which will appear as a single column in a data grid. That column will need to display and allow editing of the Position value, but the editor will need to keep producing new Position objects. You cannot, for example, provide two text boxes, one bound to Location.X and one bound to Location.Y. Value Object Database Mappings By default, value object column names are prefixed with the name of the value object field on the containing class. Mindscape LightSpeed User Guide 176 For example, suppose we have a Site entity with a Location property of type Position, and that Position has the properties X and Y. Then these would be mapped to columns named LocationX and LocationY in the Site database table. You can map the column name suffixes associated with the fields in the value object in the same way as you map entity column names: by setting the Column Name option in the designer, or by applying ColumnAttribute in code. For example, suppose that Position.X were mapped to Longitude and Position.Y to Latitude. Then the Site Location property would be mapped to the LocationLongitude and LocationLatitude columns in the database. You can map the column name prefix associated with an occurrence of a value object by setting Column Name Prefix on the connector in the designer, or by applying ColumnAttribute to the value object reference in code. For example, suppose that Site.Location were mapped to the Coordinates prefix. Then LightSpeed would map this to CoordinatesX and CoordinatesY columns in the database. The following table shows how modifiers on a field in a value object (such as Position.X) and modifiers on an occurrence of a value object (such as Site.Location) affect the column name mapping. Site.Location Position.X No modifier Prefix “Coordinates” LocationX CoordinatesX LocationLatitude CoordinatesLatitude No modifier Column “Latitude” In legacy databases, the columns of a value object may not share a common prefix, or there may not be a consistent set of suffixes across different sets of columns that you’d like to map to the same value object type. In this case, you can map individual fields of the value object at the occurrence level using ValueObjectColumnAttribute. At the time of writing, this is not supported in the designer and can only be used on handcoded members. Mindscape LightSpeed User Guide 177 Mapping value object columns to an irregular database schema public class Site : Entity<int> { [ValueObjectColumn("X", "XPos")] [ValueObjectColumn("Y", "YPos")] private Position _location; [ValueObjectColumn("X", "Easting")] [ValueObjectColumn("Y", "Northing")] private Position _surveyCoordinates; } Mindscape LightSpeed User Guide 178 Reference Data and Lookups Many domains contain reference data – data that is generally static, and that users are not expected to modify. Typical examples include lists of countries or currencies. Reference data is also known as lookup data, referring to the fact that you use look things up in the reference data, but you don’t change it. A transactional entity – that is, one which is taking part in a unit of work – may refer to reference data, and it’s convenient to materialise this reference data as an entity for lookup purposes. For example, a Customer might have a reference to a Country so that you can display the country name. But for large data sets it’s not desirable for a reference entity to have a collection of all the entities associated with it. For example, if you have tens of millions of customers, you probably don’t want each Country entity to have a collection of the customers in that country. For this scenario, LightSpeed supports one-way associations. To make an association one-way, select the association arrow and delete the collection name. You should also cache the reference data entity using the Cached and Cache Expiry settings. (To make a one-way association between hand-coded entities, omit the EntityCollection<T> field on the reference entity class, and apply NoReverseAssociationAttribute to the EntityHolder<T> field in the transactional entity class.) One-way associations should be used only with reference data. There is an efficiency impact if you use a one-way association and the referenced entity is not cached. Therefore, one-way associations are useful only when the associated entities can be loaded once and rarely change. Mindscape LightSpeed User Guide 179 Working with Models in the Visual Designer LightSpeed is equipped with a powerful visual model designer, which provides numerous features for working more productively. The chapter Creating Domain Models described the core tasks and workflow of building a model using the designer. This chapter provides more details about designer productivity and customisation features. Mindscape LightSpeed User Guide 180 LightSpeed Model Explorer The LightSpeed design surface shows your domain model – entities, associations and value objects – and data access elements, such as views and stored procedures. Most of the time, this is all you will work with and all you need to see. For some advanced or infrequent actions, however, you will need to use the LightSpeed Model Explorer. This shows a tree view of your model file, including non-visual elements, and is the only way to create, edit or delete non-visual elements. Some of the techniques in this chapter require you to use the LightSpeed Model Explorer instead of the model design surface. To open the LightSpeed Model Explorer, open the Visual Studio View menu and choose Other Windows > LightSpeed Model. Mindscape LightSpeed User Guide 181 Workflows for Rapid Application Development A traditional challenge for object-relational mapping is keeping the model in sync with the database schema. Changes need to be repeated in both places, which increases work, creates the chance of mistakes and breaks your flow as you switch tasks. LightSpeed supports workflows which avoid duplication and speed up application development. Driving the Database from the Domain Model The most convenient way to work with LightSpeed is to do your modelling in the visual designer, creating entities and associations using the toolbox, adding properties using the Ins key or context menu, and configuring properties using the property grid. You can then use the Update Database command to synchronise the database schema to the model. This makes for a very rapid development workflow because you are working entirely within one tool, the LightSpeed designer, and synchronising to the relational world requires just a couple of mouse clicks and is normally very quick. This ‘model first’ approach does not provide fine control over the database design. You may occasionally find that you need to go into your database tool to tweak column definitions or to set up additional options such as indexes. At the very least, you will want to review the generated schema before putting it into production. Nevertheless, this approach makes for extremely efficient rapid application development and allows you to get your model working with a real database with the minimum of extra steps. Modelling Using Your Database Tools An alternative approach is to focus first on the database schema. You can build the database using such tools as SQL Server Management Studio or MySQL Workbench, then drag the tables from the Visual Studio Server Explorer into the LightSpeed model to create entity classes. You can also use the Update From Source command to synchronise existing entities to the underlying database tables, for example if you have added a column to a table. Mindscape LightSpeed User Guide 182 The ‘database first’ approach is familiar from older designers such as LINQ to SQL and the ADO.NET DataSet designer. LightSpeed offers a more rapid development experience because it allows you to update existing entities in a non-destructive way – that is, LightSpeed preserves any additional options you’ve applied to the entities, rather than requiring you to delete and recreate them. It is particularly efficient if you need to be very specific about the schema of your database – for example, if you have a lot of columns where you need to control the numeric precision and scale. The disadvantage is that it requires you to swap between the database design tool and the LightSpeed domain model. This can disrupt your mental flow, especially if you need to add a lot of LightSpeed-side options such as validation – by the time you have finished creating a large table and are ready to flip back to LightSpeed, the details of the columns at the top of the table will no longer be at the front of your mind. Still, many users feel more comfortable with a database-centric workflow, and of course it is pretty much mandatory if you are working with a legacy database that may be maintained by another team in the organisation. Which Should I Choose? You don’t have to! LightSpeed doesn’t require you to choose a ‘database first’ or a ‘model first’ workflow and stick with it. You can use whichever option is most convenient at any given time. Some developers like to drag the first few tables on ‘database first’ style, because this makes it easier to hook LightSpeed up with the right data provider and connection string, then evolve the rest of the model in a ‘model first’ style. As another example, you might also find that you create most of your entities ‘model first’ but occasionally jump over to the database to design a table that requires particular fine tuning, and bring that table in ‘database first.’ And perhaps while you are working on that table you realise you need to make some changes to other tables; and since you are in the database tool, it’s easier to stay in the database tool to make those changes, rather than jumping back to LightSpeed and breaking your flow. LightSpeed doesn’t insist that you follow a single regimented workflow. You can use whichever tool is more convenient at any given moment, and sync across quickly and painlessly. Mindscape LightSpeed User Guide 183 Reorganising Model Elements in a Database First Workflow The relational database model doesn’t have a way to express some of the richer concepts of an object-oriented domain model. When you drag a table into a model, LightSpeed maps it into the object world, but you may need to fix up the results to fit your desired semantics. LightSpeed provides a number of commands to help with this. Convert between one-to-many and one-to-one associations. When importing from a database LightSpeed always infers a foreign key as indicating a one-to-many association. To convert it to a one-to-one association, right-click the association arrow and choose Convert to One-to-One Association. You can convert in the other direction by right-clicking a one-to-one association arrow and choosing Convert to One-to-Many Association. Move a property between a base and derived class. When mapping a table to a single table inheritance hierarchy, the designer defaults to importing columns into the base class (the one associated with the table). If a property applies only in a derived class, you can move it by right-clicking the property and choosing Move to Derived Class. (LightSpeed allows you to move the property into several derived classes if required.) Conversely, if you decide a derived class property is actually more widely applicable, you can right-click it and choose Move To Base Class. Moving properties preserves attributes such as validation and column mapping so this is safer than recreating the property by hand as well as quicker. Extract a set of properties into a value object. By default the designer maps all columns directly into properties of the entity class. If you want to model a particular set of columns as a value object, select them, right-click and choose Extract to Value Object. Depending on what other value objects exist in the model you may be offered the choice of creating a new value object type, mapping the columns to an existing value object type or moving the selected column(s) into an existing value object property of the entity. See Value Objects in the Domain Modelling Techniques chapter for more information. Hide simple through entities. LightSpeed represents many-to-many associations as through associations, which are mediated by a “through entity.” In most cases the through entity has no attributes other than its associations to the entities with the many-to-many association. In this case you can suppress the through entity in the Mindscape LightSpeed User Guide 184 designer by right-clicking the through association and choosing Convert to Auto Through Entity. If you find you need to add properties or attributes to the through entity, you can reverse this by choosing Convert to Explicit Through Entity. See Manyto-Many Associations below for more information about modelling many-to-many associations. Configuring Database Synchronisation For some databases, LightSpeed offers configurable round-tripping policies to control database-specific aspects of synchronisation. To see whether there are any configuration options for your database, see the chapter Working with Database Providers. At the time of writing, the only database with a configurable policy is MySQL. To specify a round-tripping policy, open the LightSpeed Model Explorer, right-click the root model node, and choose Add New [Database] Roundtripping Policy. Mindscape LightSpeed User Guide 185 Enums and Other User-Defined Types The designer knows about most common data types, such as numeric types, strings and dates, and includes support for some database-defined types such as SQL Server 2008 spatial types. You can also add your own types to the designer. A particularly common use for this to represent integer columns as .NET enumerations (enums). To add a type to the designer, open the LightSpeed Model Explorer, right-click the root model node, and choose Add New User-Defined Type. Adding an Enum Type to the Designer To add an enum type to the designer, specify the following options for the newly created user-defined type: Name: The name to be displayed in the Data Type drop-down, e.g. Priority. CLR Type Name: The type name of the enum, as it should be emitted into the generated code. Typically, this will be the fully-qualified type name, e.g. MyCompany.Shipping.Priority, though you can omit the namespace if the enum is part of the same namespace as the model, or if the namespace is imported through the Imported Namespaces collection. Data Type: Leave this as Int32 for most enum types. If your enum is backed by a short or long integer type, specify the appropriate backing type. (This is used only for database synchronisation. It doesn’t affect the entity definition, or the enum type definition itself.) Is Value Type: Leave this as True for enum types. Is Standard Data Type: Leave this as True for enum types. Converter Type: Leave this blank for enum types. Adding a Database-Defined Type to the Designer The designer already supports some database-defined types. If you want to use a type which is defined by your database but not supported by the designer, you can add it as a user-defined type by specifying the following options: Mindscape LightSpeed User Guide 186 Name: The name to be displayed in the Data Type drop-down, e.g. SqlHierarchyId. CLR Type Name: The .NET type name of the type, as it should be emitted into the generated code. This is typically defined by the ADO.NET provider, e.g. Microsoft.SqlServer.Types.HierarchyId. You must fully qualify the name, or add the containing namespace to the Imported Namespaces collection. Is Value Type: Set this to True or False depending on whether the ADO.NET type is a struct or a class. (This is important if you have nullable instances of the type.) Is Standard Data Type: Set this to False for database-defined types. Database Type Name: Set this to the SQL name of the database-defined type, e.g. hierarchyid. This is used for database synchronisation. Converter Type: Leave this blank for types that are defined by the ADO.NET provider. Adding a User-Defined Type with Custom Mapping The third main use case for user-defined types is when you have a type that has a custom mapping from the database to the CLR. This can be because the type is a standard one but it is mapped to the database in a non-standard way, such as a Boolean which is mapped to a Y/N string column in the database, or an enum which is stored using its string name instead of its integer value, or it may be because the database column contains data which doesn’t map to a standard type, such as a string column in a legacy database which contains several pieces of data packed together as comma separated values. You can add such a type as a user-defined type by specifying the following options: Name: The name to be displayed in the Data Type drop-down, e.g. Money or YesNo. CLR Type Name: The .NET type name of the type, as it should be emitted into the generated code. This may be a .NET type defined in your code, e.g. MyCompany.Money, or a standard type, e.g. System.Boolean. Is Value Type: Set this to True or False depending on whether the CLR type is a struct or a class. (This is important if you have nullable instances of the type.) Mindscape LightSpeed User Guide 187 Is Standard Data Type: Leave this as True if the type is stored in the database using a standard designer type such as string or integer. Set it to False if it uses a non-standard type, such as a database-defined type or a SQLCLR type. Data Type: If you left Is Standard Data Type as True, set this to the standard type used for storing this type in the database. In the Yes/No example, this would be String. This is used for database synchronisation and does not affect the entity definition. Database Type Name: If you set Is Standard Data Type to False, set this to the SQL name of the type in the database. This is used for database synchronisation and does not affect the entity definition. Converter Type: Set this to the name of the class which converts between the database representation and the CLR type, such as Mindscape.LightSpeed.FieldConverters.YNBooleanFieldConverter. The class must implement the IFieldConverter interface (see also the FieldConverter class). See Mapping Database Types to Domain Types in the chapter Working with Legacy Databases for more information. Custom mappings are primarily intended for existing databases where the data format is already established. When creating new databases, prefer to use standard LightSpeed conventions, and where it’s appropriate to use a domain type such as Money, consider mapping it using a value object rather than a custom mapping. Using a User-Defined Type Once you have declared a user-defined type, you can use it just like any other type. It appears in the Data Type drop-down, and can be entered or shown against the property in the usual way. Mindscape LightSpeed User Guide 188 Refactoring in the Designer Refactoring is the process of improving your code without changing its observed behaviour. Familiar refactorings include Rename, Extract Method, etc. The LightSpeed designer provides several commands to help you refactor your model. All of these can be found by right-clicking and choosing Refactor. Refactoring Rename Applies To Property or entity Create Partial Class Entity Create Partial Classes Model Convert to Manual Implementation Property Extract Interface Entity Mindscape LightSpeed User Guide Description Performs a solution-wide rename, i.e. all references elsewhere in your code to the property or entity are updated. You can optionally keep the property or entity mapping to the same column or table name if you do not also want to rename your database object. Creates an empty partial class for the entity, where you can start adding your own code. Creates empty partial classes for all entities in the model. Removes the automatically generated code for the selected property and copies it into a partial class file (you must have created a partial class file first), so that you can manually edit it, for example to add business logic or error checking. Creates an interface declaring the properties of the selected entity. (Note that the entity is not automatically declared to implement the new interface: you must add this declaration by hand via a partial class file.) This refactoring is not available in Visual Basic. 189 Custom Views Models, especially large models, can be hard to navigate. LightSpeed provides several options to help you visualise your model more easily. Filtering You can filter your view to show only selected entities. This can be useful for locating the part of the model you’re interested in or for visualising model behaviour. To filter the model, open the LightSpeed Model Explorer, select how you want to filter the view and enter the string to be matched. For the convenience of keyboard users, you can also use a prefix character on the filter string instead of clicking the “Filter by” option. Filter By Name Prefix (none) Tag # Effect Shows only entities whose names contain the filter string. The string is treated as a regular expression, so you can perform simple pattern matching. Shows only entities with a matching tag. Entities with no tags at all are always shown. Tags are a convenient way of labelling a related set of entities. You can set tags using the Tags property. An entity can have more than one tag. Mindscape LightSpeed User Guide 190 Filter By Aggregate Prefix @ ! Effect Shows only entities which are part of the named aggregate specified by the filter string. An entity is part of the named aggregate if it has an association on which that aggregate is specified. Inverts the filter: that is, entities which match the filter are hidden instead of shown. (This can be specified only through a prefix, not through the Filter By drop-down.) You can optionally show additional entities which, although they don’t match the filter themselves, are related to entities that do. This help you to see the filtered entities in context. Show Option Show selected Show associated Show load graph Show inheritance Effect Shows only entities which match the filter. Also shows entities which have an association (one to many, one to one or through association) with the entities which match the filter. Only one level of association is followed; indirectly associated entities are not shown. Also shows entities which are eager loaded by an entity which matches the filter. The full eager load graph is shown, not just immediately associated entities. Only “always eager load” associations are considered; optionally loaded associations via named aggregates are not shown (but can be shown using the @ filter). Also shows the base and derived classes of the entities which match the filter. Sibling classes are not shown: if you need to understand the full hierarchy, enter a filter which matches the root of the hierarchy. QuickViews To save the current filter, right-click the model and choose View > Save Current as QuickView. Mindscape LightSpeed User Guide 191 To reload a saved filter, right-click the model, choose the View submenu and select the name of the saved view. Note that LightSpeed saves and re-applies the filter definition: if the model has changed, the result of the filter may be different from before. You can rename, edit and delete QuickViews via the Quick Views folder in the LightSpeed Model Explorer. The View submenu also contains commands to quickly filter by tag, if any tags are defined in the model, and to remove all filtering and show the entire model. Mindscape LightSpeed User Guide 192 Linked Models Another solution to the problem of large, complex models is to replace a single model file with multiple linked model files. Linked model files work well when your domain consists of a number of distinct subdomains, with relatively few links across subdomain boundaries. Don’t use linked model files if you have a lot of associations across subdomain boundaries – cross-file associations require quite a bit of maintenance and if you have too many of them you may find they are more trouble than they are worth! To link a set of model files, you must do two things: Set the Name of each model to the same value. (If you are specifying a namespace in the model, then this must be the same for all of the linked models too.) Choose one of the models to be the “main” model. For all of the other models, set Is Linked Child to True. If you use the Paste as Link command to create entity links, or the Refactor > Split Model At Association command to split an existing model into two files, it will set these properties for you. Code Generation Each linked model file in a set is code-generated separately. The code generation is done in such a way that the generated files combine to produce a single model using partial classes. For example, you will end up with a single strong-typed unit of work class, with queryable properties for all of the entity types, although the implementation for this class will be spread across three files. Because LightSpeed depends on partial classes to combine code, all linked model files must be in the same project. Creating Associations Across Model File Boundaries You can represent an entity from one file in another file by dragging an Entity on from the Toolbox and setting its Name to the desired linked entity, and settings its Is Link Mindscape LightSpeed User Guide 193 property to True. You can then create associations to the linked entity. You must create the associations in both files. For example, suppose you have two files, Sales.lsmodel and Logistics.lsmodel. The Sales subdomain contains an entity named PurchaseOrder and the Logistics subdomain contains an entity named Shipment. You want to create a one-to-many association between these entities, so that a PurchaseOrder has a collection of Shipments and a Shipment has a reference to a PurchaseOrder. To do this: Open Sales.lsmodel and drag on an Entity from the Toolbox. Set the entity’s Name to Shipment and Is Link to True. Click OneToManyAssociation in the Toolbox and drag an arrow from PurchaseOrder to Shipment. Open Logistics.lsmodel and drag on an Entity from the Toolbox. Set the entity’s Name to PurchaseOrder and Is Link to True. Click OneToManyAssociation in the Toolbox and drag an arrow from PurchaseOrder to Shipment. Save both models and rebuild your project. Creating and Maintaining Entity Links A quick way to create a link is to select the “main” entity definition, choose Edit > Copy or press Ctrl+C, switch to the target file, right-click the model background and choose Paste as Link. This will automatically set up the entity link’s properties, and can also link the two model files for you. Important: An entity link is not aware of the entity it links to. Therefore, you must manually keep properties in sync between the link and the original entity. In particular, if the entity has an Identity Type other than Int32, you must set the Identity Type on the link to be the same as the Identity Type on the entity. If you are using model-first database synchronisation or migrations, and the entity overrides the Table Name, Identity Column Name or Schema, then you must also replicate these settings to the link. You can check that these settings are in sync using the Check Links menu command. To see the definition of a linked entity, right-click the link and choose Go To Linked Entity. Mindscape LightSpeed User Guide 194 Changing the Designer Defaults When you create an entity in ‘model first’ style – that is, by dragging an Entity icon from the toolbox – LightSpeed provides defaults for its various settings. You can change the defaults for newly created entities by specifying a defaults policy. To do this, open the LightSpeed Model Explorer, right-click the root model node, and choose Add New Defaults Policy. You can then edit the defaults through the Policies folder. You can change the defaults for: Identity type. The Identity Type for newly created entities. Storage options. The Track Create Time, Track Update Time, Soft Delete and Optimistic Concurrency Checking options for newly created entities. Base class. The base class for newly created entities. Model-wide base classes are usually there to provide a few common fields or services, so this defaults to concrete table inheritance New in LightSpeed 5, defaults can also be specified for generating association DataMember attributes. The defaults policy affects only newly created entities, not existing entities. If you create an entity by dragging a table from Server Explorer, LightSpeed uses the table definition to infer the identity type and storage options, ignoring the defaults policy, though the base class is still respected. Mindscape LightSpeed User Guide 195 Customising the Generated Code As discussed in Creating Domain Models, LightSpeed models are ultimately just code, and the visual designer works by generating that code for you. LightSpeed offers several ways to customise and extend this generation process. Using Your Own Code Generation Templates Code generation in LightSpeed uses a set of NVelocity templates, written in VTL (Velocity Template Language) and found in the installation directory under Tools > Designer > Templates. You can edit these templates to change the way that the designer generates LightSpeed code. However, it’s recommended that you don’t edit the templates at the installation location for two reasons. First, whenever you upgrade LightSpeed, it will reinstall its templates over the top of yours. Second, if you’re working with other developers, or across multiple machines, you need to make sure the custom templates get copied to each machine. To solve these problems, LightSpeed allows you to keep your templates with the project. By doing this, you eliminate the risk of the templates being overwritten during install, and you can treat them as part of the project rather than a machine setting — for example putting them in source control so that when another developer gets the project sources they get the current templates as well. To do this, copy the template files from the installation location to a suitable projectspecific location. Be sure to choose the template appropriate for your project language (under the C# or VB directory in the installation location). Then go into Solution Explorer, select the project node and look at the Properties grid. If the project contains a LightSpeed model (a .lsmodel file), you’ll see an extra entry here called LightSpeed Template File. (You may need to open the .lsmodel file to make the extra entry appear.) Edit this to point to your copy of the “main” template (typically Base.vm): Mindscape LightSpeed User Guide 196 From this point on, whenever the model changes, the code will be regenerated using your copies of the templates. There are a couple of maintenance considerations for custom templates: 1. Visual Studio won’t automatically regenerate code when you change the template. Changes to the templates will only take effect next time you edit your model. If you don’t actually need to do anything to the model, just move something and move it back again – that will be enough to trigger regeneration. 2. We occasionally ship updates to the default templates, to reflect new features or options, or to fix bugs. If these updates are relevant to you, you’ll want to fold them into your custom templates. It’s therefore a good idea to keep a copy of the “Mindscape version our custom templates are based on” around. That way, when we update the templates, you can use a diff and merge tool to find the changes between Mindscape LightSpeed User Guide 197 the Mindscape versions and merge them into your files (or to merge your diffs from the baseline onto the new baseline). Extending the Designer Metamodel The designer allows you to specify entity and property options that are relevant to LightSpeed. It’s sometimes useful to be able to specify other options which aren’t relevant to LightSpeed but which logically belong on the entity or property, and to emit those options through a custom template. For example, suppose you have a user interface framework which uses DisplayNameAttribute to render the name of each property. You could specify DisplayNameAttribute manually, using the Custom Attributes collection, but this could become inconvenient if you have a lot of properties. You might wish instead to add a Display Name setting to entity properties, so that you could readily edit it in the grid, and to generate DisplayNameAttribute using that Display Name setting. You can do this in the LightSpeed designer using extension properties. The default code generation templates ignore extension properties, but custom templates can use them to generate whatever code is required. To define an extension property, open the LightSpeed Model Explorer, right-click the root model node, and choose Add New Extension Property Definition. You can specify the following settings for your extension property: Name: The name by which templates refer to the extension property. Extends: The kind of model elements to which the extension applies. Data Type Name: The CLR type name of the type of data that can be stored in the extension property. This must be fully qualified, e.g. System.Int32 or System.String. If the type is not defined in mscorlib.dll or System.dll, then the name must be assembly-qualified, or the assembly must be included in the Design Time Assemblies collection. Category, Display Name and Description: Additional options for how the extension property is displayed in the Visual Studio Properties grid. Mindscape LightSpeed User Guide 198 Once you have defined an extension property, it appears in the properties grid for every element of the kinds you specified in Extends, and you can enter values for it as if it were a built-in property. To use an extension property in a template, there are three methods which you can call from the template: HasExtendedProperty(name): Returns true if the user set a value for the named extension property on the element at hand, or false if the user did not set a value (or the extension property does not apply to this kind of element). GetExtendedPropertyValue(name): Returns the value set by the user for the named extension property on the element at hand, and throws an exception if the user did not set a value. You should always call HasExtendedProperty before calling this method. GetExtendedPropertyValue(name, defaultTo): Returns the value set by the user for the named extension property on the element at hand, or the defaultTo value if the user did not set a value (or the extension property does not apply to this kind of element). You can safely call this without calling HasExtendedProperty. The value returned from GetExtendedPropertyValue is the actual value of the property. This may not be suitable for emitting into the generated code directly. For example, if GetExtendedPropertyValue returns a string, and you want to emit that string into a DisplayNameAttribute declaration, you will usually want to quote that string. Or if it Mindscape LightSpeed User Guide 199 returns an enum value, you will usually want to emit it qualified with the enum type name. [DisplayName(Full Name)] [DisplayName("Full Name")] // would be an error // correct [Priority(High)] [Priority(Priority.High)] // would be an error // correct To convert a literal value to a code fragment for that literal value, call $Translator.TranslatePrimitive from your custom template. Generating DisplayNameAttribute from the DisplayName extension property #if ($field.HasExtendedProperty("DisplayName")) [DisplayName($Translator.TranslatePrimitive($field.GetExtendedPropertyValue("DisplayName")))] #end Using TranslatePrimitive ensures that values are converted to literals in a way which is correct for both the value and the target programming language. (Of course, if the extension property is intended to be used in a VTL expression such as a #if test, or if it represented a member name such as a property in a strongly-typed resource class, then you will not want to quote it. This is one of the reasons why GetExtendedPropertyValue returns a value rather than a code fragment.) Using T4 Templates with the LightSpeed Designer You can also use Visual Studio’s T4 templates with a LightSpeed model. This technique is useful when you want to generate separate files from the model, rather than tweaking or extending the entity code as you would if you were customising the built-in templates. For example, you could write a T4 template to generate WCF service interfaces or ASP.NET MVC controllers, perhaps shaped by your own extension properties. Mindscape LightSpeed User Guide 200 To create a T4 template which works with a LightSpeed model, create a .tt file in your project and add the following declarations at the top: <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #> <#@ assembly name="Mindscape.LightSpeed.Generator.Model.dll" #> <#@ assembly name="Mindscape.LightSpeed.Generator.Integration.Dsl.Mapping.dll" #> You must also add the paths to the designer assemblies to your project’s Reference Paths collection (Project > Properties > Reference Paths). Alternatively, you can specify the path in the assembly directives. The designer assemblies are not redistributable and you should not add them to your project. T4 needs them to process the template but you do not need them at run time. You must then specify the file extension for the generated file, using the output directive: <#@ output extension=".txt" #> Finally you must specify the LightSpeed model from which to generate code, using the LightSpeedModel directive. The processor attribute is always LightSpeedModelDirectiveProcessor; the requires attribute should specify fileName='lsmodel_file'. The following directive hooks the T4 template up to a LightSpeed model named Sample.lsmodel: // Line breaks added for clarity <#@ LightSpeedModel processor="LightSpeedModelDirectiveProcessor" requires="fileName='Sample.lsmodel'" #> You can now write T4 code as normal. The LightSpeed model is available through the this.Model reference. Mindscape LightSpeed User Guide 201 <# foreach (Entity entity in this.Model.Entities) { #> // Emit entity code here <# } #> The designer object model is not documented so you will need to ask in the LightSpeed forum or use the Visual Studio Object Browser to determine the programmatic names of metamodel classes and properties (though they usually correspond to the display names shown in the toolbox and property grid). Mindscape LightSpeed User Guide 202 Many-to-Many Associations As discussed in the chapter Creating Domain Models, LightSpeed represents many-to-many associations as through associations. A through association between A and B is implemented using a through entity, which represents an association between one A entity and one B entity. A given A entity may be associated with multiple through entities, each of which links on to one B entity, and vice versa. The designer provides two ways of presenting through associations. You can choose to show just the many-to-many relationship, treating the through entity as an internal implementation detail, which is the most convenient approach in most cases. Or you can choose to show the through entity explicitly, which provides you with fine control and extensibility at the expense of visual clutter. Using an Auto Through Entity The first option is referred to as using an auto through entity. Auto through entities avoid cluttering up the diagram when there’s no additional data associated with the relationship, and you don’t need fine control over database mapping, eager loading, etc. To specify an auto through entity, select the through association arrow and enter a name for the through entity in the Auto Through Entity box: Mindscape LightSpeed User Guide 203 If you have an explicit through entity and it’s not adding any value, you can convert it to an auto through entity by right-clicking the through association and choosing Convert to Auto Through Entity. Using an Explicit Through Entity The second option is referred to as using an explicit through entity. In this case the through entity appears on the diagram surface as a fully-fledged entity, allowing you to customise features such as database mapping and eager loading, and to associated extra data with the relationship. For example, suppose you represent tagging by a many-to-many association between Contributions and Tags, and you want to record which user applied each Tag to each Contribution. You can’t store that information on the Contribution or the Tag. The best place to store it is therefore on the through entity which represents the contribution–tag association. With an explicit through entity this is easy because you can work with the through entity just like any other entity. To specify an explicit through entity, select the through association arrow and choose the through entity from the Through Entity drop-down. When you show an explicit through entity, you must also explicitly model the one-tomany associations between the main entities and the through entity. This allows you to control details such as foreign key column mapping. Mindscape LightSpeed User Guide 204 If you have an auto through entity and you need to add extra data or fine-tune the database mapping, you can convert it to an explicit through entity by right-clicking the through association and choosing Convert to Explicit Through Entity. How Do Auto and Explicit Through Entities Differ? Fundamentally, they don’t. They result in the same code and the same database schema. They are not different things, just different ways of showing the same thing on the design surface. Mindscape LightSpeed User Guide 205 Designer Shortcuts and Tips Speeding Up Property Entry You can add a new property to an entity by pressing the Ins key while the entity or any of its properties is selected. You can set the type of a property by entering the type name before the property name when editing the property, as if declaring a C# variable. For example, if you type int Height, then the property is named Height and is of type Int32. As in C#, you can use the question mark suffix to make the property nullable (e.g. int? Height). Using the Ins key and inline type editing, you can easily enter multiple properties without taking your hands away from the keyboard. This can be much quicker than using the mouse when entering a lot of properties. Custom Attributes You can apply custom attributes to generated properties using the various Custom Attributes collections. You can set custom attributes on entities and properties via the Custom Attributes option, and for associations you can set them on either end of the association and the foreign key (via options such as Collection Custom Attributes and Backreference Id Custom Attributes). Custom attributes are applied to the wrapper property, not the backing field. Most LightSpeed attributes have to go on the backing field, so you can’t use custom attributes to apply LightSpeed attributes – it’s usually more convenient to use the Mindscape LightSpeed User Guide 206 designer equivalents anyway. Rather, they are intended for attributes consumed by other frameworks, such as BrowsableAttribute or DisplayNameAttribute. When you enter an attribute in the Custom Attributes dialog, you must fully qualify the attribute name, e.g. System.ComponentModel.Browsable. If you’re applying a lot of attributes from the same namespace, you can get around this by adding the namespace to the Imported Namespaces list (via the LightSpeed Model Explorer). Grabbing an Image of Your Model To get an image of your entire model, press Ctrl+C or choose Edit > Copy. You can then paste this into Word, PowerPoint or Paint as a bitmap. If you want only a subset of your model, select the elements you want to include in the image before copying. If you are filtering the view, the copied image will show only the elements that are included in the current filter. Using Reminder Notes The toolbox includes a ‘reminder’ icon which you can use to add notes to your model. You can link a note to a URL by filling out the Link URL property; in this case, the note will display a More… link which takes you to that URL. For example, a “to do” note could be linked to an entry in a bug tracking system. Use Get Started for Configuration File Entries Once you’ve hooked your model up to a database, you can use the Get Started command to see the LightSpeed configuration file entries. You can paste from the Get Started screen into your web.config or app.config. Be aware that the provided entries are a starter set: you may still need to configure other settings by hand, such as identityMethod or quoteIdentifiers. Mindscape LightSpeed User Guide 207 Rearranging Properties To rearrange entries in an entity’s properties list – for example, to alphabetise them or to group related items together – right-click a property and choose Move Up or Move Down. Assigning Keyboard Shortcuts to LightSpeed Commands To assign a keyboard shortcut to a LightSpeed designer command: Open Tools > Options > Environment > Keyboard, or Tools > Customize > Keyboard. In the “Show commands containing” checkbox, enter LightSpeed. The list box shows a list of LightSpeed designer commands. Choose the command you want, type the desired keystroke into the “Press shortcut keys” box and click Assign. When you’ve finished mapping commands, click OK. Using a Custom Base Class for Your Entities There are two ways to use a custom base class for your entities. One way is to create the base class normally using the designer, draw inheritance arrows and set the inheritance type to Concrete Table Inheritance. This works well with designer-database synchronisation because the designer can know about fields defined in the base class, but for larger models can result in a lot of arrows cluttering up the diagram. If the clutter becomes a problem, you can hide these arrows by selecting them and setting Show On Diagram to false. The alternative approach is to define the base class in code, create an External Class Reference to that class via the LightSpeed Model Explorer, and set each entity’s Base Class to the external reference via the Properties window. This avoids lots of inheritance arrows, but will result in a warning that the external class is being excluded each time you sync to the database. Mindscape LightSpeed User Guide 208 Changing Property or Entity Names When Other Code Already Uses Them If you want to change the name of a property or entity in the domain model, but you have lots of code that already uses the existing name, and you don’t want to change the database schema either, you can use the Refactor > Rename command to help you out. Right-click the property or entity and choose Refactor > Rename. Enter the new name and make sure that “Keep existing name as database column/table name” is ticked. LightSpeed will update all references to the property or entity in your code, and create a mapping between the renamed element and the existing database name. Writing Custom Property Getter or Setter Code To write custom code for property getters and setters, select the property you need to write custom code for, go to the Properties window and change the Generation option to FieldOnly. You can then write your own property getter and setter in the partial class. You can also right-click the property and choose Refactor > Convert to Manual Implementation; however, this will also convert the backing field to a manual implementation, which will prevent LightSpeed from applying subsequent changes to property settings. Setting Validation Options Which Aren’t In the Properties Window To set validation options that aren’t available in the Properties window, like ValidateUriAttribute.UriKind or custom error messages, use the LightSpeed Model Explorer to locate the validation, and edit its properties. See Validation in the Creating Domain Models chapter for details. Foreign Key Field Attribute in Designer From LightSpeed 5 onwards the designer supports setting the ForeignKeyFieldAttribute on one-to-one associations. Mindscape LightSpeed User Guide 209 Show Data Command Right-clicking on an entity and selecting ‘Show Data’ will open the Visual Studio table editor, allowing you to view the table data for that entity. This is available for SQL Server, MySQL and SQLite. For non-SQL Server databases you will need to have the appropriate Visual Studio integration package installed. For this to work you will need to have a connection to the database defined in the Server Explorer (which you will be prompted to create if you’re going the model-first route). Mindscape LightSpeed User Guide 210 Advanced Querying Techniques The Basic Operations chapter shows how to carry out most queries using LightSpeed. Using the techniques shown there, you can perform filtering, ordering and paging; and if you are using LINQ you can easily perform more advanced queries using the standard LINQ operators or the C# and Visual Basic language integrated syntax. This chapter describes additional query functionality such as full text search, speeding up query times by using compile queries and how to exploit database-specific or userdefined functions in your queries. This chapter also describes how you can complex queries if you are using query objects instead of LINQ. Mindscape LightSpeed User Guide 211 Full Text Search The search engine capabilities in LightSpeed provide an easy to use free text search implementation that is database engine independent. It provides developers a mechanism to perform Google-style queries and have them match any entities within a defined scope – for example, provide any entity that has the word ‘car’ in it. To implement full text search in your LightSpeed application you need to use a search provider. Out of the box, LightSpeed ships with a single search provider which is built on top of the open-source Lucene project. Lucene provides a very high performance and scalable search infrastructure. Developers can plug in their own search provider by implementing the ISearchEngine interface. To use a full text search you first need to instruct LightSpeed to use the search engine and where it can store the search index on disk. This is done via configuration of the LightSpeedContext by either XML configuration file or in code. Configuring the default search provider for LightSpeed in the config file <add name="Test" connectionStringName="Test" dataProvider="Sqlite3" searchEngineClass="Mindscape.LightSpeed.Search.LuceneSearchEngine" searchEngineFileLocation="c:\foo\bar\baz"/> Make sure that the search engine file location directory is writable by your application process as it will be written to as new entities are saved. Indexing Data LightSpeed does not index the entire database – it will only index data on entities that you care about. To enable indexing, select each of the properties that you want to include in the search index and set their Include in Full Text Index option to true. To do this in hand coded entities, apply IndexedAttribute to the fields: Mindscape LightSpeed User Guide 212 Instructing LightSpeed to store a value in the search index [Indexed] private string _registration; In advanced scenarios you can override the Entity.SearchData method to provide custom indexing data to be indexed. Note that you will still need to set Include in Full Text Index on one of the entity’s properties (or apply IndexedAttribute to one of its fields) to notify LightSpeed that you wish the entity to be indexed; however, when you override Entity.GetSearchData LightSpeed will use your custom implementation instead of getting indexing data from the attributed field. Overriding the GetSearchData() method to combine entity data for one entity protected override string GetSearchData() { if (Warrant != null) { return Warrant.SerialNumber + " " + Registration; } return Registration; } Overriding the GetSearchData method provides a powerful mechanism for collapsing indexed data into one entity. For example, when searching for a Car you may want to include engine details. These are stored in an Engine entity, but you can add them to the Car index data by returning them from Car’s GetSearchData method. If updating the fields that are being indexed or overriding GetSearchData, be sure to call a Rebuild of your search index to ensure that the search engine index is up to date with your changes. Building the Search Index By default LightSpeed will update the search index when you call UnitOfWork.SaveChanges. The changes that have occurred as part of the save will be Mindscape LightSpeed User Guide 213 updated in the search index. However, if you are adding a search index to an existing database it’s important to rebuild the search index from the entire database, not just a single change set. LightSpeed provides the Rebuild method for rebuilding the entire search index. Call Rebuild with the types of entities that you want to have indexed. Rebuilding the entire search index _context.SearchEngine.Rebuild(IsolationLevel.ReadCommitted, typeof(Comment), typeof(Tag), typeof(Car), typeof(Contribution)); Rebuilding the search index should not be needed often. Calling Rebuild() can be an expensive operation, and for larger database solutions you may wish to use Lucene outside of the LightSpeed framework to achieve the best performance. Performing Searches Performing searches using LightSpeed is done with a LightSpeed query object. This first example demonstrates searching for a comment with the word “video” in it. Basic search over one entity type Query query = new Query(); query.SearchQuery = "video"; using (var unitOfWork = _context.CreateUnitOfWork()) { unitOfWork.Find<Comment>(query); } Searching with LightSpeed is not limited to only returning one type of entity. The next example demonstrates searching for comments and tags at the same time. Mindscape LightSpeed User Guide 214 Basic search over several entity types Query query = new Query(); query.SearchQuery = "video OR Mindscape"; using (var unitOfWork = _context.CreateUnitOfWork()) { var results = unitOfWork.Search(query, typeof(Comment), typeof(Tag)); Assert.AreEqual(3, results.Count); } You can combine a full text search and a normal SQL query to limit the text search. Advanced search combining normal query and search index using (var unitOfWork = _context.CreateUnitOfWork()) { Query query = new Query(); query.SearchQuery = "video"; query.QueryExpression = Entity.Attribute("Member") == unitOfWork.FindById<Member>(41); var count = unitOfWork.Count<Comment>(query); Assert.AreEqual(1, count); } Advanced Operator Support As LightSpeed is using Lucene by default as a search engine developers can use any of the advanced operators that Lucene supports by specifying them in the SearchQuery string. Further Search Reading If you are developing a significant solution with LightSpeed and working with Lucene for search, we recommend that you read about Lucene and understand how to configure and operate it for the best performance. See the Lucene Web site for more information. Mindscape LightSpeed User Guide 215 Invoking SQL Functions Several SQL functions are built into LightSpeed and can be used in queries. If you are using LINQ, you need only specify the corresponding CLR method and LightSpeed will translate it to SQL. You can also extend this mapping to custom functions. If you are using query objects, you can specify some functions using query expression member functions, but in other cases must emit the SQL function explicitly. Mapping SQL Functions in LINQ When you write queries using LINQ, you write them using .NET objects. To use a function in the query, you just write the appropriate .NET method or property in your query, and LightSpeed will translate it to the equivalent SQL function. For example, if you want to perform a case-insensitive comparison, you could use the .NET String.ToUpper method to force all your strings to upper case: from e in unitOfWork.Employees where e.LastName.ToUpper() == searchText.ToUpper() select e; LightSpeed translates this to the SQL UPPER function: SELECT ... FROM Employee WHERE UPPER(Name) = @p0 Many standard .NET methods and properties are built into the LightSpeed provider. However, your database may provide SQL functions that don’t have a .NET equivalent, or you may have created user-defined functions that you want to use in queries. In this case, you can register a mapping between a .NET method and a SQL function. Once this is done, you can use the .NET method in a query, and LightSpeed will translate it to the specified function. Mindscape LightSpeed User Guide 216 To register a mapping between a .NET method and a SQL function, call ServerFunctionDescriptor.Register. You can register a member method or an extension method: this allows you to create methods on existing classes purely to have something to map to SQL. For example, suppose you wanted to call SQL Server’s (admittedly antiquated) DIFFERENCE function, which returns how similar two strings sound. There is no String method that maps naturally to DIFFERENCE, but you can define and register an extension method: Mapping a .NET method to a SQL function // Declaring the extension method public static class SoundexExtensions { public static int SimilarityTo(this string first, string second) { throw new NotImplementedException(); } } // Mapping the method MethodInfo similarityTo = typeof(SoundexExtensions).GetMethod("SimilarityTo"); ServerFunctionDescriptor.Register(similarityTo, "DIFFERENCE"); Once a method is mapped, you can use it in a LINQ query just as if it were built into LightSpeed: Using a mapped method from t in UnitOfWork.Towns orderby t.Name.SimilarityTo("radavleetsa") descending select t; The resulting SQL calls the SQL function to which the .NET method was mapped: SELECT ... FROM Town ORDER BY DIFFERENCE(Name, @p0) DESC Mindscape LightSpeed User Guide 217 Mapping to a Custom Function The same technique applies to mapping .NET methods to user-defined functions. However, you must be careful to specify the server-side function name in exactly the way the database wants to see it. For example, SQL Server requires that user-defined function names be prefixed with the schema. So when mapping your .NET method you must specify “dbo.MyFunction” instead of just “MyFunction.” ServerFunctionDescriptor.Register(clrMethodInfo, "dbo.MyFunction"); Mapping Argument Order When you map a .NET method to a SQL function, by default, the expression to which the .NET method is applied becomes the first argument to the SQL function. Sometimes this is not appropriate. For example, suppose you mapped the String.IndexOf method to the SQL Server CHARINDEX function. CHARINDEX requires the string to be looked for as the first argument, not the string to look in. ServerFunctionDescriptor.Register provides an overload which takes an implicit argument index. If you use this, the expression to which the .NET method is applied – the ‘implicit’ argument – will appear at that (0-based) index in the SQL function’s argument list. ServerFunctionDescriptor.Register(indexOfMethod, "CHARINDEX", 1); Mapping Member Functions Microsoft SQL Server allows you to define custom .NET types and methods within the database. A major use case for this is SQL Server 2008’s spatial data support, where the comparison functions are member functions of the geography and geometry data types. Member methods are not called using the usual SQL syntax, and must therefore be mapped specially. To indicate that the translation of a .NET method is a member Mindscape LightSpeed User Guide 218 method and must be emitted with member syntax, prefix the server method name with a dot. MethodInfo method = typeof(SqlGeography).GetMethod("STDistance"); ServerFunctionDescriptor.Register(method, ".STDistance"); // note the dot Invoking SQL Functions Using Query Objects The mapping step in LINQ is needed because the C# and Visual Basic compiler type-check LINQ expressions, so you can only call SQL functions by representing them as .NET methods on a suitable .NET type. If you are using query objects, you can pass SQL function names as strings, avoiding the need for mapping. To do this, use the Function method, passing the name of the SQL function and any required arguments: Invoking a SQL function using query objects unitOfWork.Find<Town>( Entity.Attribute("Name").Function("DIFFERENCE", "radavleetsa") > 2); As with mapped functions in LINQ, the expression by default becomes the first argument, but you can override this by specifying an implicit argument index. Also as with LINQ mappings, you can specify that the function should be called using member syntax by prefixing it with a dot. Entity.Attribute("UserName").Function(1, "CHARINDEX", " ") > 0; Entity.Attribute("Location").Function(".STDistance", searchLocation) < distance; Mindscape LightSpeed User Guide 219 Compiled Queries Compiled queries allow you to significantly speed up your query times by preprocessing LINQ queries or LightSpeed query objects into a compiled form. These can then be executed repeatedly in different units of work and with different parameters. The performance increase is gained by reducing query processing overhead as it only needs to be conducted once, instead of each time you run the query. Compiling a LINQ based query To use compiled queries when writing a LINQ query first bring the Mindscape.LightSpeed.Linq namespace into scope via a using statement and then you can call the Compile() extension method on a LINQ query. This will return you a CompiledLinqQuery instance which can be later executed. Two overloads are available; one for queries that return collections and another for values, such as Count(), Sum() or First(). The code below illustrates the use: Compiling a LINQ query using Mindscape.LightSpeed.Linq; // bring extension methods into scope var query = from p in UnitOfWork.Penguins where p.Age < 9 select p; CompiledQuery<IList<Penguin>> youngPenguins = query.Compile(); CompiledQuery<int> youngPenguinCount = query.Compile(q => q.Count()); Note that at this point you need a unit of work to compile the query. No entities are loaded, but a LightSpeedContext is required in order to build the right SQL statement for your database. This also provides handy query properties to write the query against. You can throw away the ‘compilation’ unit of work once this is done, and provide a ‘real’ unit of work when you execute the query. Compiling a query object based query To use compiled queries when writing a query object based query you need to use the CompiledQuery class which can be found in the Mindscape.LightSpeed.Querying namespace. This class has static methods for compiling Find, Count, Calculate and Project queries. The code below illustrates the use: Mindscape LightSpeed User Guide 220 Compiling a query object Query query = new Query(typeof(Penguin), Entity.Attribute("Age") < 9); CompiledQuery<IList<Penguin>> cquery = CompiledQuery.CompileFind<Penguin>(query, _unitOfWork); Executing a compiled query A compiled query is represented by a CompiledQuery<T> object, where T is the type of data to be returned — typically a list of entities, but it could also be a numeric type for count and aggregate queries, or a list of non-entity objects for projections. To run a compiled query, call its Execute() method, passing the unit of work in which you want to execute it: Running a compiled query IList<Penguin> ps = youngPenguins.Execute(unitOfWork); int pcount = youngPenguinCount.Execute(unitOfWork); The unit of work must be associated with the same LightSpeedContext instance as you used to compile the query. It is highly recommended to use a singleton LightSpeedContext as this guarantees the preceding requirement. This LightSpeedContext should be treated as immutable. Parameterising compiled queries In practice you probably won’t re-run the exact same query again and again. One or more of the parameters will vary from run to run. For example, you might be performing a search by name, with the name coming from user input. To represent a parameter in a compiled query, using the CompiledQuery.Parameter method: Mindscape LightSpeed User Guide 221 Adding a parameter var query = from p in UnitOfWork.Penguins where p.Name == CompiledQuery.Parameter<string>("name") select p; CompiledQuery<IList<Penguin>> namedPenguins = query.Compile(); Now when you call CompiledQuery.Execute, you must supply an ICompiledParameterValues which provides the values for each parameter. The simplest way to do this is to use an anonymous type and the CompiledQuery.Parameters method: Executing a compiled query, providing a value for the parameter var results = namedPenguins.Execute(unitOfWork, CompiledQuery.Parameters(new { name = "Gloria" })); (Note: anonymous types, though convenient, are relatively slow. Depending on the performance characteristics of your query, it may be worth using an optimised implementation of ICompiledParameterValues based on a switch statement or a dictionary. Mindscape LightSpeed User Guide 222 Exploring the Query Object Most advanced query operations are most easily achieved using LINQ. However, it is sometimes necessary or preferable to use query objects. This section summarises the features of the Query object. Basic Querying Operations The QueryExpression property specifies the criteria for the query – in effect the “where” clause. The Order property controls how the results are ordered, and the Page property allows you to select a sub-range of records by their index in the sort order. For full information about these properties, see the Basic Operations chapter. Controlling Entity Load The AggregateName property specifies a named aggregate to load. An aggregate specifies a pattern of eager loads, allowing associations or blobs to be optionally loaded depending on a particular page or screen’s requirements. See the Performance and Tuning chapter for details. Projections You can use the Projection collection to load a specific set of columns (or computed expressions). When doing this you must call the IUnitOfWork.Project method instead of IUnitOfWork.Find. The results are not materialised into entities and do not become part of the unit of work. You can access the results via an ADO.NET IDataReader for raw access, or have them materialised into data objects using the IUnitOfWork.Project<T> overload. The latter is similar to LINQ projections using the Select operator. When performing a projection, you can also set the Distinct property to deduplicate the results. Mindscape LightSpeed User Guide 223 Examples: If you want to perform a single column projection which returns a primitive type or a string then you can use the Project<T> overload as shown below: Projecting a single column and returning a list of Int32s var query = new Query(typeof(Contribution)); query.Projection.Add("ContributorId"); var results = unitOfWork.Project<int>(query); To deduplicate the results in the query above, we would use the Distinct property as shown below: Projecting a single column and returning a distinct list of Int32s var query = new Query(typeof(Contribution)); query.Projection.Add("ContributorId"); query.Distinct = true; var results = unitOfWork.Project<int>(query); If we want to project more than one column to a known type, that type needs to be declared with properties that match the name of the projected columns we are going to be returning. An example with two properties is shown below but the same approach applies regardless of how many columns are involved in the projection. Mindscape LightSpeed User Guide 224 Projecting multiple columns and return a list of a specified type struct MyProjectionResult { public int ContributorId { get; set; } public int ApprovedById { get; set; } } var query = new Query(typeof(Contribution)); query.Projection.Add("ContributorId"); query.Projection.Add("ApprovedById"); var results = unitOfWork.Project<MyProjectionResult>(query); If you require a custom mapping to be applied or don’t require object instances to be returned to you then you can use the Project() method which will return you a DataReader instance. Projecting and returning a DataReader instance var query = new Query(typeof(Contribution)); query.Projection.Add("ContributorId"); query.Projection.Add("ApprovedById"); using (var reader = unitOfWork.Project(query)) { while (reader.Read()) { // do work } } LightSpeed 5 includes improved support for conditionals in projections, such as HasValue. Views Specify the ViewName property to run the query against a view instead of against the entity’s normal backing table. See Working with Database Views in the chapter Controlling the Database Mapping for details. Mindscape LightSpeed User Guide 225 If you are using LINQ you can use the .WithViewName extension method Controlling Aliasing Aliasing for a LightSpeed query is specified through the Mappings property. The Mappings object allows you to specify a mapping between a type or query and a name. This alias is then either used automatically when LightSpeed builds the query, or you can use it when building up a query or a join to indicate which alias should be used when building the query. Managing aliasing manually should only be required for very precise control over queries as LightSpeed will generate the required aliasing automatically for all queries. Manually specifying aliasing for a query var query = new Query(typeof(Contribution)); query.Mappings.Add<Contribution>("t0"); query.Mappings.Add<Member>("t1"); One word of caution when manually specifying aliasing for a query is that the Mappings collection is used by LightSpeed to understand all of the types involved in the query, so if a mapping is added in to the collection but not subsequently used then a CROSS JOIN will be applied to join that table in to the query to ensure that any arbitrary criteria that may reference that aliased entry can be resolved. Joins Joins allow you to define expressions that will allow multiple entities to participate in a query. A Join as defined as part of a query is directly translated into a SQL join when LightSpeed translates a query into the underlying SQL statements required for the query. LightSpeed allows you to define three types of joins: Inner: The intersection between the two entity sets as intersecting on the supplied keys Mindscape LightSpeed User Guide 226 Outer: All rows in the left hand entity set paired with corresponding rows in the right hand entity set or null if no corresponding join can be made. This conforms to the syntax of a LEFT OUTER JOIN in SQL. CrossJoin: The Cartesian product of the two entity sets. To specify a join, you must assign an instance of the Join class to the Join property. Multiple joins can be specified by use of the .And() method which is available on any Join instance. LightSpeed offers several static methods to allow for easy instantiation of Join instances according to the type required and allows for generic specification of the two entity types involved. For more advanced scenarios such as joining against a sub-query there are additional overloads available. If you are interested in such scenarios please review the API documentation for the static Join instantiation methods named Inner() and Outer(). When performing a join, LightSpeed will automatically opt in all fields from the joined entity or query unless you have explicitly defined a projection for the query. The rational for this is that you are surfacing entities rather than individual columns. If your intention is to only make use of data from specific fields then we would strongly recommend you declare a set of appropriate projections to match the data you intend to use. Review the documentation in the Projections section in this document for more information on how to achieve this. Examples: To specify an Inner Join you can use the Inner<TLeft, TRight>() method as shown below: Specifying an inner join between two entity types var query = new Query(typeof(Contribution)); query.Join = Join.Inner<Contribution, Member>("ContributorId", "Id"); Mindscape LightSpeed User Guide 227 To specify an Outer Join you can use the Outer<TLeft, TRight>() method as shown below: Specifying an outer join between two entity types var query = new Query(typeof(Contribution)); query.Join = Join.Outer<Contribution, Member>("ApprovedById", "Id"); To specify a Cross Join you can use the CrossJoin<TLeft, TRight>() method as shown below: Specifying a cross join between two entity types var query = new Query(typeof(Contribution)); query.Join = Join.CrossJoin<Contribution, Member>(); If you need to specify multiple joins, you can use the .And() method to chain your join instances together. An example of this is shown below: Specifying multiple joins using Join.And() var query = new Query(typeof(Contribution)); query.Join = Join.CrossJoin<Contribution, Member>().And( Join.Inner<Contribution, Comment>("Id", "ContributionId") ); In advanced scenarios you may require LightSpeed to join against a sub-query, for example to surface specific projected fields from the sub-query while using it to filter or scope the top level query. The .Inner() and .Outer() methods provide an overload which allows you to join on another Query instance which will perform a join against that sub query of the type you have nominated. LightSpeed does not currently offer this with CrossJoins and these overloads are not available on the generic versions of these methods. Mindscape LightSpeed User Guide 228 An example of how this might be used is shown below: Specifying a join between an entity type and a query var innerQuery = new Query(typeof(Member), Entity.Attribute("Username") == "jb"); var query = new Query(typeof(Contribution)); query.Join = Join.Outer(typeof(Contribution), innerQuery, "ContributorId", "Id"); Grouping The Grouping property allows you to declare a grouping which will be applied to the query which will be ultimately translated into a SQL GROUP BY statement. A grouping is specified by assigning a Group instance to the Group property on the query object. The Group class provides a static method for instantiating Group instances and the Group class provides the .AndBy() method which allows you to specify multiple grouping columns. If you are performing a grouping query you will always need to obtain your results using a call to Project or Project<T> as LightSpeed will by default only select back the columns which are being grouped on. You can alternatively specify additional columns to be selected by explicitly declaring a projection set for the query, note that if you do so you need to ensure you include the columns being grouped on as this will override the default behaviour. Specifying a Projection set also allows you to add aggregates into the query. LINQ translations of grouping statements will typically perform two underlying calls, one to perform the grouping query and then a second batch to load the entities involved in the query to assign them into the corresponding resulting grouped collections allowing further client side projections to take place against those entities. Mindscape LightSpeed User Guide 229 Examples: To perform a standard grouping operation where the result will be a single column of the grouping key you can specify this as shown below: A basic grouping operation var query = new Query(typeof(Contribution)); query.Group = Group.By("ContributorId"); var results = unitOfWork.Project<int>(query); Alternatively you can specify a Projection set and use the Group.BySelection() method to indicate that LightSpeed should group by every column in the projection set. You need to ensure that this will generate a valid query for your target database, for example aggregates will not be supported by most database providers. Asking LightSpeed to group by the selection defined in your Projection set struct MyGroupingResult { public int ContributorId { get; set; } public int ApprovedById { get; set; } } var query = new Query(typeof(Contribution)); query.Group = Group.BySelection(); query.Projection.Add("ContributorId"); query.Projection.Add("ApprovedById"); var results = unitOfWork.Project<MyGroupingResult>(query); Mindscape LightSpeed User Guide 230 Alternatively you can use the .AndBy() method to chain your grouping expressions together, so to rewrite the above query using this approach would look like this: Using AndBy to chain grouping expressions struct MyGroupingResult { public int ContributorId { get; set; } public int ApprovedById { get; set; } } var query = new Query(typeof(Contribution)); query.Group = Group.By("ContributorId").AndBy("ApprovedById"); var results = unitOfWork.Project<MyGroupingResult>(query); Finally you can return aggregates as part of the result set by declaring these in your Projection set while independently specifying the grouping keys using Group instances. An example of this is shown below: Returning Aggregates as part of your projected result set struct MyGroupingResult { public int ContributorId { get; set; } public int Views { get; set; } } var query = new Query(typeof(Contribution)); query.Group = Group.By("ContributorId"); query.Projection.Add("ContributorId"); query.Projection.Add(Entity.Attribute("Views").Function("SUM")); var results = unitOfWork.Project<MyGroupingResult>(query); Subexpressions Subexpressions allow you to define common expressions that can be referenced by name in the query. See Subexpressions below for details. Mindscape LightSpeed User Guide 231 Unions and Intersections You can use the ComposedQueries collection and the ComposeMethod property to compose queries using the SQL UNION, UNION ALL or INTERSECT operator. Hints The Hints property allows you to pass index or table hints to databases where this is supported. See the Performance and Tuning chapter for more information. Using direct SQL statements LightSpeed 5 has added the support for specifying a direct SQL statement on the Query object by setting the RawSql property with the statement text. If this property is set then all other properties on the query will be ignored and the specified SQL statement will be directly used. The returning result set is expected to either be a full entity if used with Find or the required projection if used with Project. Other Querying Properties The Identifier property is used when querying for a single entity by Id. The IdentifiersOnly property specifies that LightSpeed should select only entity Ids, not full entities. The IncludeDeleted property specifies that the query should also return soft-deleted entities. See the Implementing Storage Policies with LightSpeed chapter for details. The SearchQuery property invokes full text search. See Full Text Search above. Mindscape LightSpeed User Guide 232 Subexpressions When you’re writing a query, you may find that an operation over an associated collection comes up repeatedly. For example, you may want to perform a calculation over the collection, and both order and filter by the result of this calculation. To avoid retyping the common expressions each time – and having the database engine re-evaluate them each time – you can define subexpressions for them, then use these subexpressions as if they were actual attributes of the entity. To define a subexpression, add it to the Query.Subexpressions collection. You must specify the name of the subexpression, the query expression it encapsulates, and the column in the target table on which to join to the main table. Defining a subexpression query.Subexpressions.Add( "TotalFreight", Entity.Attribute("Orders.Freight").Function("SUM"), "CustomerId"); // name // expression // join column Once a subexpression is defined, you can use it by specifying its name. You can use a subexpression name anywhere you would normally use a field name – typically in a query expression, sort order or projection. Mindscape LightSpeed User Guide 233 Using subexpressions in a query Query query = new Query(typeof(Client)); // Define TotalFreight and AverageValue subexpressions query.Subexpressions.Add( "TotalFreight", Entity.Attribute("Orders.Freight").Function("SUM"), "CustomerId"); query.Subexpressions.Add( "AverageValue", Entity.Attribute("Orders.Value").Function("AVG"), "CustomerId"); // Order by the aggregates query.Order = Order.By("TotalFreight").AndBy("AverageValue"); // Filter by the aggregates query.QueryExpression = Entity.Attribute("TotalFreight") > 10000 && Entity.Attribute("AverageValue") < 9000; // Use the aggregates in a projection query.Projection.Add("Name"); query.Projection.Add("TotalFreight"); query.Projection.Add("AverageValue"); var clientsPayingHighFreightForLittleValue = _unitOfWork.Project<FreightSummary>(query); In LINQ, you can create sub expressions using the let keyword; no special APIs are required. Mindscape LightSpeed User Guide 234 Working with Metadata Most of the time when you’re working with LightSpeed, you’re working with specific entities and using those entity classes. Sometimes, however, a utility method needs to be able to work with any kind of entity, rather than being specialised to a particular entity type. An example might be generic copy or compare methods, which could be useful in resolving optimistic concurrency conflicts. A generic compare method could compare the attribute values of one entity to those of another entity of the same type, and a generic copy method could that copy all or selected values. Such methods need to know what attributes an entity has, but it would be tedious to re-implement them for each entity class (and maintain them as the class evolved). To help you implement such methods, LightSpeed provides a metadata API which you can use to enumerate, examine and update entity attributes. Mindscape LightSpeed User Guide 235 The LightSpeed Metamodel LightSpeed provides three classes that represent entity metadata: EntityInfo represents an entity class. FieldInfo represents an attribute of an entity. AssociationInfo represents an association between entities. It is derived from FieldInfo because associations are a particular kind of field. ValidationInfo represents details of a validation which is being applied to an entity and will be based on a validation rule. Note that an EntityInfo represents a class, not an individual entity instance. Similarly, a FieldInfo represents a class member, not an instance of the field on a particular entity instance. Referencing the Metadata Assembly As metadata is only required in occasional cases, the metadata classes are not included in the main LightSpeed assembly. To use the metadata classes, you must add a reference to the Mindscape.LightSpeed.MetaData assembly, found in the installation directory under the Bin folder. Mindscape LightSpeed User Guide 236 Getting Class Information If you have an entity instance, you can get the metadata for that entity using the EntityInfo extension method. Getting the metadata from an entity instance using Mindscape.LightSpeed.MetaData; // bring extension method into scope var classInfo = order.EntityInfo(); Remember that the Entityinfo represents the metadata for the type of the entity: it is not specific to the entity instance. You can also get the metadata for an entity type without having an entity instance by using the EntityInfo.FromType static method: Getting the metadata from an entity type var classInfo = EntityInfo.FromType(typeof(Order)); Getting Class Settings EntityInfo has a number of properties relating to the entity class, primarily around storage policies. See the API documentation for information about these properties. You can also determine which other classes are part of the same single or class inheritance hierarchy as the entity class, using the InheritanceHierarchy property. (Concrete inheritance is not considered because concrete inheritance is not polymorphic and is not used in database mapping.) Mindscape LightSpeed User Guide 237 Getting Field and Association Information EntityInfo exposes the fields of the entity in several different ways. The Fields collection lists all fields defined on the entity class – both ‘value’ fields and associations. To get just the value fields, use the ValueFields collection; to get just the associations, use the Associations collection. Each of these collections has a corresponding collection with the “Flattened” prefix – FlattenedFields, FlattenedValueFields and FlattenedAssociations. In single or class table inheritance scenarios, these contain all fields of the relevant type from across the inheritance hierarchy. (In the absence of discriminated inheritance, they are the same as the non-flattened equivalents.) The FieldInfo and AssociationInfo classes have a number of properties relating to the field or association definition. For example, you can get the data type using the FieldType property, or the cardinality of association using the AssociationType property. Again, see the API documentation for information about these properties. Fields and Properties The LightSpeed metamodel represents fields, not properties, because fields are what LightSpeed works with. Normally there is a one-to-one correspondence between fields and properties, but if you are hand-coding entities, or have overridden the default designer behaviour using the Generation option, then this may not be the case. In this case, remember that the field collections represent fields, not properties. You will also notice that FieldInfo has three name properties: FieldName, PropertyName and HumanName. FieldName returns the name of the actual backing field which LightSpeed uses for persistence. PropertyName returns the name of the CLR property which application code uses to get or set that field. As noted above, in hand-coded cases there may not be such a property. Finally, HumanName returns the field name tidied up and split up into words for end user display. For example, if you create a property named OrderReference in the designer, the metadata name properties will be as follows: FieldName: _orderReference Mindscape LightSpeed User Guide 238 PropertyName: OrderReference HumanName: Order Reference Getting Validation Information ValidationInfo exposes information about a validation that is being applied to a field on an entity. To get this information you can enumerate the Validations collection on a FieldInfo. Each ValidationInfo instance provides a reference to the validation name which you can use for comparison purposes, the .NET type of the validation class if you want to use that for comparison or for any further work within your code and then a dictionary of name/object pairs which contain the actual data associated with the validation instance. For example for a length validation you will find a LengthValidationRule entry with Minimum and Maximum entries in the dictionary which hold integer values as set on the validation instance. Mindscape LightSpeed User Guide 239 Getting and Setting Fields through Metadata Getting Field Values To obtain the value of a field on a particular entity instance, call the FieldInfo.GetValue method: Getting the value of a field on an entity instance var referenceField = classInfo.Fields.First(f => f.PropertyName == "OrderReference"); object reference1 = referenceField.GetValue(order1); object reference2 = referenceField.GetValue(order2); GetValue accepts any entity, but throws an exception if the entity is not of the type on which the FieldInfo is defined. This can be an issue when inheritance is in play: if you are processing entities from across the inheritance hierarchy, then you will probably be using a flattened collection to ensure you process all fields, but a particular entity instance won’t have the fields declared in sibling or derived classes. To check if the FieldInfo is defined on the entity, call IsDefined: Using IsDefined to check that it is safe to get a field value var classInfo = EntityInfo.FromType(typeof(SalesItem)); // Not defined on all SalesItem subclasses var publisherField = classInfo.FlattenedFields.First(f => f.PropertyName == "Publisher"); if (publisherField.IsDefined(salesItem)) { object publisher = publisher.GetField(salesItem); } FieldInfo also provides a TryGetValue method which combines the check with the retrieval, but using IsDefined and GetValue is usually more convenient if you want to cast the value to a particular type. Mindscape LightSpeed User Guide 240 From LightSpeed 5 onwards, FieldInfo includes extra information on properties: Validations (which returns a list of the validations defined on entities of its type) IsNullable IsIdentityField Getting Association Values When you use FieldInfo.GetValue to retrieve an association value, this causes LightSpeed to load the association if it not already loaded. For to-one associations, the return value is the associated entity, not the EntityHolder which implements the association. Setting Field Values Conversely, you can set the value of a field on an entity instance using SetValue: Setting the value of a field on an entity instance var referenceField = classInfo.Fields.First(f => f.PropertyName == "OrderReference"); referenceField.SetValue(order, newReference); As with GetValue, you can use IsDefined to check that the field exists on the entity instance at hand, or TrySetValue to combine the check with the set. When you call FieldInfo.SetValue, you are directly setting the underlying CLR field. (Remember, the LightSpeed metamodel represents fields, not properties.) The property setter is not called. If you have custom logic in the property setter, for example to validate or transform input values, FieldInfo.SetValue bypasses that custom logic. Mindscape LightSpeed User Guide 241 Performance and Tuning LightSpeed makes it easy to work with your persistent data as .NET objects, abstracting away many details of your application’s use of the database. In practice, however, no application layer can completely abstract away the database. To get good performance, it’s important to understand what is happening at the level of the database connection and the database engine, and what options you have for improving performance at this level. Mindscape LightSpeed User Guide 242 Controlling How Entities Load By default, LightSpeed loads entities on demand. That is, if an entity has an association to another entity or a collection of other entities, the association is loaded the first time an application accesses it. This requires another query to the database, to retrieve the associated entity or the members of the collection. (Once the association is loaded, LightSpeed remembers that it’s loaded and doesn’t perform another database query.) For example: var var var var var var order = unitOfWork.FindById<Order>(1); reference = order.Reference; customer = order.Customer; customerName = order.Customer.Name; lines = order.Lines; line0value = order.Lines[0].Value // // // // // // queries the database for an Order scalar property, no database query queries the database for a Customer customer is loaded, no database query queries the database for OrderLines line is loaded, no database query This is a safe, conservative strategy which avoids superfluous loads. It is referred to as lazy loading because it lazily doesn’t do any work it doesn’t need to. However, when a unit of work needs a number of related entities, it can be imperfectly efficient. In the code fragment above, LightSpeed issued three database queries. If we knew ahead of time that the application was going to need the Customer entity and the Lines collection, it would have been more performant to perform all three queries in a single go. The problem gets worse when dealing with collections of entities. For example: var overdues = unitOfWork.Orders.Where(o => o.DueDate < today); foreach (var o in overdues) // queries the database for Orders { var customer = o.Customer; // queries the database for a Customer Console.WriteLine("Order {0} for customer {1} is overdue", o.Reference, customer.Name); } This code fragment results in one query for the orders, then, for each order, another query for the customer. If the first query returned 1000 orders, then we would do Mindscape LightSpeed User Guide 243 1000 queries for customers – a total of 1001 queries. This problem is called the N+1 problem because it means you need N+1 queries to process N objects. LightSpeed makes it easy to avoid these problems using eager loading. Eager Loading Eager loading means loading associated entities before they are needed. In LightSpeed, eager loading specifically means loading associated entities using the same database query as the source entity, avoiding an extra round trip. Here is how our N+1 code fragment works with eager loading. var overdues = unitOfWork.Orders.Where(o => o.DueDate < today); foreach (var o in overdues) // queries the database for Orders *and* Customers { var customer = o.Customer; // Customer is already loaded, no database query Console.WriteLine("Order {0} for customer {1} is overdue", o.Reference, customer.Name); } By using eager loading we replace 1001 trips to the database to a single trip! Of course, that one trip now has to do more work, so you should eager load only when you know you are going to need the associations. To implement eager loading, select an association that you want to be eager loaded, and: If you want parent entities to eager load their children, set Eager Load Collection to True. If you want child entities to eager load their parents, set Eager Load Backreference to True. In the example above, because you want the Order entity to eager load its associated Customer, you would select the Order to Customer association and set Eager Load Backreference to True. If you also wanted the Customer entity to eager load its collection of Orders, you would also set Eager Load Collection to True. (An association can be eager loaded in both directions.) Mindscape LightSpeed User Guide 244 For hand-coded associations, you implement eager loading by applying the EagerLoad attribute to the association field: Implementing eager loading on hand-coded associations public class Order : Entity<int> { [EagerLoad] private readonly EntityHolder<Customer> _customer = new EntityHolder<Customer>(); [EagerLoad] private readonly EntityCollection<Line> _lines = new EntityCollection<Line>(); } Eager loading cascades, allowing you to load a whole object graph in a single database round-trip. For example, if Customer.Orders is eager loaded, and Order.Lines is eager loaded, then when you load one or more Customers, LightSpeed will automatically fetch not only the orders for all those customers but also the lines for all those orders. Fine Grained Control Using Named Aggregates It often happens that some parts of an application want to eager load an association, but other parts do not. For example, a page which displays the details of an order knows that it will be displaying the order lines, so would want to eager load the Lines collection. But a page which displays a list of overdue orders doesn’t need to display the individual order lines, so it doesn’t want to eager load the Lines collection – but it might want to eager load the customers so that it could show the user who each overdue order belonged to. To support conditionally eager loading an association, LightSpeed uses named aggregates. ‘Aggregate’ is a term from domain-driven design that refers to a bounded object graph that we need to satisfy a particular application use case. (In the example above, there were two aggregates. The order details page was interested in an “order plus lines” aggregate. The overdue orders page was interested in an “order plus customer” aggregate.) Named aggregates allow you to name particular object graphs, and tell LightSpeed to eager load different aggregates in different situations. Mindscape LightSpeed User Guide 245 To specify that an association is part of a named aggregate, select the association arrow, and enter the aggregate name in the Collection Aggregates or Backreference Aggregates box (or both). An association can be part of multiple named aggregates – separate the aggregate names with semicolons. For hand-coded associations, specify the AggregateName property on the EagerLoad attribute. You can specify the attribute multiple times. Specifying that a hand-coded association is part of a named aggregate public class Order : Entity<int> { [EagerLoad(AggregateName = "WithAllDetails")] private readonly EntityHolder<Customer> _customer = new EntityHolder<Customer>(); [EagerLoad(AggregateName = "WithAllDetails")] [EagerLoad(AggregateName = "WithLines")] private readonly EntityCollection<Line> _customer = new EntityCollection<Line>(); } To eager load a particular aggregate using LINQ, specify it in your query using the WithAggregate() method: Eager loading a named aggregate using LINQ // Loads orders and associated lines, but not associated customers var orders = unitOfWork.Orders .Where(o => o.CustomerId == customerId) .WithAggregate("WithLines") .ToList(); In LightSpeed 5 a new feature was added where if a model defines named aggregates, constants based on their names are emitted. This allows you to write code such as WithAggregate(StoreAggregates.WithProductInfo), so you don’t have to specify the named aggregate as a string. Mindscape LightSpeed User Guide 246 To eager load a particular aggregate using query objects, specify it in using the Query.AggregateName property: Eager loading a named aggregate using query objects // Loads orders and associated lines, but not associated customers Query query = new Query(Entity.Attribute("CustomerId" == customerId)) { AggregateName = "WithLines" }; var orders = unitOfWork.Find<Order>(query); In both of the above samples, changing “WithLines” to “WithAllDetails” would eager load both the lines and the customers. Omitting the aggregate would mean both the lines and the customers were lazy loaded. The aggregate name is an attribute of a query, and affects all associations in the aggregate, not just the associations on the starting object. For example, if both Customer.Orders and Order.Lines are marked with the “WithAllDetails” aggregate, then loading a Customer with the WithAllDetails aggregate will load the customer’s orders, and all the lines of those orders. This allows you to load deep object graphs in a single database access if required. Mindscape LightSpeed User Guide 247 Controlling How Entity Data Loads By default, when LightSpeed loads an entity, it loads all the fields of that entity. This is an efficient strategy because most fields are small and the overhead of loading them is very low compared to the cost of having to go back to the database to load them on demand. However, some entities contain bulky data which is rarely used. For example, an employee record might contain a high-resolution security photograph of the employee. Most uses of an Employee don’t need the high-resolution photograph, so downloading it is a waste of time and bandwidth. You therefore typically want to mark the Photo field as lazy loaded – that is, to be loaded only when accessed, in a similar way to associations. Of course, there are probably cases where the Photo field will be required. For example, if you are working on a screen that prints security passes, you’ll always want the high-resolution photograph. For situations like these, you want to be able to eager load the field just like a normal entity field. As with associations, LightSpeed handles these requirements through named aggregates. To mark a field as lazy loaded, select the field and enter one or more aggregate names in the Aggregates box. (Unlike associations, you can’t specify that a field is always lazy loaded. If you never plan to eager load a field, just enter an aggregate name that you never plan to use.) For hand-coded fields, apply the EagerLoad attribute with the AggregateName property to the field, and implement the wrapper property using the Get method. Mindscape LightSpeed User Guide 248 Lazy loading a field public class Employee : Entity<int> { [EagerLoad(AggregateName = "WithSecurityBlobs")] private byte[] _securityPhoto; public byte[] SecurityPhoto { get { return Get(ref _securityPhoto); } set { Set(ref _securityPhoto, value); } } // MUST use Get method here } The marked field will now be loaded only when you access it: using { var var var } (IUnitOfWork unitOfWork = _context.CreateUnitOfWork()) employee = unitOfWork.FindById<Employee>(1); // does not load security photo name = employee.Name; // already loaded, no database query photo = employee.SecurityPhoto; // queries database to load SecurityPhoto column However, if you supply one of the aggregates that the field is part of, using the WithAggregate method or the Query.AggregateName property, then the field will be eager-loaded as if it were a normal field: using (StaffUnitOfWork unitOfWork = _context.CreateUnitOfWork()) { var employee = unitOfWork.Employees. .WithAggregate("WithSecurityBlobs") .Single(e => e.Id == 1); // loads security photo var photo = employee.SecurityPhoto; // already loaded, no database query } The aggregate name is an attribute of a query, and affects all fields in the aggregate, not just the fields on the starting object. For example, if Site.Employees is eager loaded or is marked with the “WithSecurityBlobs” aggregate, then loading a Site with the Mindscape LightSpeed User Guide 249 WithSecurityBlobs attribute will load all the employees on that site, including their SecurityPhoto fields. Mindscape LightSpeed User Guide 250 Understanding Named Aggregates Associations and fields have different default behaviours, and it is possible to get confused about how aggregates and the WithAggregate operator affect the loading of fields. This section summarises load behaviour so you can understand exactly what gets loaded and when under different circumstances. If you don’t do anything to a field, it will always be loaded when the entity loads. If you specify one or more aggregate names on a field, it will be loaded only when it is accessed, incurring a separate database access, unless you specify one of those aggregates in your query. If you don’t do anything to an association, it will be loaded only on demand, incurring a separate database access. If you specify eager load on an association, it will always be loaded when the entity loads, as part of the same database access. If you specify one or more aggregate names on an association (and don’t specify eager load), it will be loaded only on demand, unless you specify one of those aggregates in your query, in which case it will be loaded when the entity loads, like an eager load association. WithAggregate doesn’t affect fields or associations that aren’t part of a named aggregate. They will stay eager or lazy depending on how they were set up. WithAggregate propagates across associations, so you can use a named aggregate to load an entire object graph. You can specify multiple named aggregates on a field or association to control which subsets of entity data and/or the object graph get loaded in different situations. Visualising Aggregates You can use the designer to help you visualise named aggregates. See Custom Views in the chapter Working with Models in the Visual Designer. Mindscape LightSpeed User Guide 251 Bulk Updates and Deletes The normal cycle for working with an existing database row begins by loading it as an entity. You then modify or remove the entity, and save the changes to the database. This results in an UPDATE or DELETE statement for each affected row. In some bulk operations, the same change is being applied to all affected rows, and no entity operations are required. In this case, loading each row as an entity, then sending a statement per row, is not efficient. It would be more efficient to send a single UPDATE or DELETE with an expression indicating which rows to apply to it. This can be done using the IUnitOfWork bulk Update and Remove methods. Bulk Updates IUnitOfWork.Update takes a query specifying which rows to update, and an object or dictionary specifying the values to update those rows with. If you pass an object, then LightSpeed uses the names and values of its properties to compose the update. If you pass a dictionary, then LightSpeed uses the string keys of the dictionary and their associated values. The following example uses an anonymous type to update the IsBadPaymentRisk column. Performing a bulk update using a change object DateTime overdueDate = DateTime.Now.AddMonths(-6); Query overdueAccounts = new Query(typeof(Customer), Entity.Attribute("Balance") < 0 && Entity.Attribute("LastPaymentDate") < overdueDate); unitOfWork.Update(overdueAccounts, new { IsBadPaymentRisk = true }); unitOfWork.SaveChanges(); Mindscape LightSpeed User Guide 252 The following example uses a dictionary to perform the same update. Performing a bulk update using a change dictionary DateTime overdueDate = DateTime.Now.AddMonths(-6); Query overdueAccounts = new Query(typeof(Customer), Entity.Attribute("Balance") < 0 && Entity.Attribute("LastPaymentDate") < overdueDate); Dictionary<string, object> changes = new Dictionary<string, object>(); changes.Add("IsBadPaymentRisk", true); unitOfWork.Update(overdueAccounts, changes); unitOfWork.SaveChanges(); Note that like entity operations, bulk operations are not applied to the database immediately. You must still call SaveChanges. This allows bulk updates to be carried out atomically and to be coordinated with entity operations. Bulk Deletes IUnitOfWork.Remove takes a query specifying which rows to delete. Performing a bulk delete DateTime expiryDate = DateTime.Now.AddMonths(-6); Query expiredUsers = new Query(typeof(User), Entity.Attribute("LastActiveDate") < expiryDate); unitOfWork.Remove(expiredUsers); unitOfWork.SaveChanges(); As noted under Bulk Updates, you must remember to call SaveChanges to commit the bulk delete. To perform this type of query using LINQ you can use the .Remove() extension method in the Mindscape.LightSpeed.Linq namespace to indicate that the query should be a remove operation. As with IUnitOfWork.Remove you will need to call SaveChanges to commit the bulk delete. Mindscape LightSpeed User Guide 253 Considerations for Bulk Operations Bulk operations bypass the identity map. After performing a bulk operation, dispose the unit of work, or at least reset it (by passing true to SaveChanges). Bulk operations bypass the cache, and do not update the full text search index. Avoid using bulk updates if your application uses either of these features. Mindscape LightSpeed User Guide 254 Batching When you call IUnitOfWork.SaveChanges, LightSpeed flushes all pending changes – inserts, updates and deletes – to the database. To reduce the number of round-trips to the database, LightSpeed collects these pending changes into batches for submission into the database. Note that LightSpeed always commits changes under the aegis of a transaction. Therefore, even if there are more SQL statements than will fit in one batch, you are still guaranteed that the save will be atomic. Customising the Batch Size By default, LightSpeed collects pending changes into batches of 10. You can override the default batch size by setting LightSpeedContext.UpdateBatchSize in code, or the updateBatchSize attribute in configuration. Customising the batch size in configuration <add name="Test" updateBatchSize="20" /> Customising the batch size in code _context.UpdateBatchSize = 20; Increasing the batch size will reduce the number of database round-trips during a large save, but results in much longer SQL commands. Increasing batch size too far can therefore reduce performance instead of improving it. If you make a change, be sure to measure the impact on a system whose configuration (speed, throughput, latency) is as similar as possible to your production system. Particularly in low latency environments, reducing round trips may be a poor trade off. Mindscape LightSpeed User Guide 255 Identity Columns and Batching The IdentityColumn identity method defeats batching, because LightSpeed has to query for the database-allocated Id after each insert. Identity columns should be used only when required for compatibility with existing databases. Databases and Batching Some databases do not support batching. On these databases, each SQL statement is run as a separate command regardless of batching settings. Mindscape LightSpeed User Guide 256 Caching LightSpeed provides two mechanisms for caching of entity instances – a first level and second level cache. Both caches are designed to help in reducing the number of database queries that need to be executed. The first level cache, which is also known as the Identity Map, is always in use, will cover all entities and cannot be disabled; second level caching, however, must be explicitly enabled and configured for use as it is disabled by default and only extends to caching entities which you have explicitly opted in through use of the Cached option. First level caching is scoped to the current unit of work and therefore any cached objects will be unavailable as cached objects inside a second unit of work. The second level cache provide application wide caching – beyond the bounds of a unit of work. An example of how the first and second level caches will be hit using (var unitOfWork = context.CreateUnitOfWork()) { var contrib1 = unitOfWork.FindById<Contribution>(52); // This request will be returned from the level one cache in memory var contrib2 = unitOfWork.FindById<Contribution>(52); } using (var unitOfWork = context.CreateUnitOfWork()) { // If level two cache is not enabled, this would generate // a database call. With the level two cache enabled it would // be fetched from the cache as we fetched it in the previous // unit of work var contrib1 = unitOfWork.FindById<Contribution>(52); } Enabling Second Level Caching You do not need to use second level caching at all, but you may wish to enable it to improve performance of your application. If you’re noticing that your application is querying the database frequently for the same information (for example, reference data) then adding caching will provide an improvement in performance immediately. Mindscape LightSpeed User Guide 257 Web applications also benefit well from the second level cache as generally pages are constructed relatively quickly within a tightly defined unit of work and therefore the first level cache is not going to be of use across multiple requests. LightSpeed supports a pluggable cache provider model and provides implementations for two cache providers out of the box: DefaultCache – Uses the System.Web.Caching.Cache provider (which can be used in non-web apps too). This cache is a simple in-process cache. MemcachedCache – Uses the popular memcached distributed cache. Memcached can be distributed over an arbitrary number of servers and is suitable for high-load scenarios. Additionally you can implement your own cache provider by implementing the ICache interface. To enable second level caching, you can either do this programmatically or through configuration as shown below: Enabling second level caching programmatically context.Cache = new CacheBroker(new DefaultCache()); Enabling Second Level Caching using configuration <lightSpeedContexts> <add name="default" connectionStringName="Dev" cacheClass="Mindscape.LightSpeed.Caching.DefaultCache, Mindscape.LightSpeed" /> </lightSpeedContexts> Configuring the Second Level Cache Caching may be applied declaratively at the model level by applying the CachedAttribute. Mindscape LightSpeed User Guide 258 [Cached(ExpiryMinutes = 15)] public class Instance : Entity<long> { } Alternatively it can also be applied using the LightSpeed designer by selecting an entity and setting the cache to be enabled. Cache expiry times can also be configured with the designer if desired. When LightSpeed loads an entity decorated with the CachedAttribute it will immediately add it to the second-level cache. Any subsequent request for that entity by its id will cause a cache hit and the database load will be avoided. Understanding the Second Level Cache For the first level cache, LightSpeed will check if it already has an entity loaded for the given Id when you perform any type of fetch where you specify an ID. For example, if you use the FindById() method, LightSpeed will check the internal first level cache first and, if found, return that and save a database call. If an explicit ID is not provided (for example, a query that would return all entities from a table) then as LightSpeed receives the result and looks to hydrate an entity it will first check if it already has an entity in the first level cache and, if found, return that entity. This type of logic applies when fetching explicitly as well as when lazy loading occurs. Mindscape LightSpeed User Guide 259 The second level cache, if enabled, will also be checked in all of the same situations however it is more likely to contain objects as they will live across the unit of work boundaries. To summarise when a cache hit would occur: You fetch an entity by Id You fetch a collection of entities You lazy load a collection or entity Note: If you are dealing with projections then no cache hits will be possible, even if the projection contains the entity identifier. Collection Caching If you wish to cache an entire entity set in memory (e.g. for reference data) you can use FindAll caching. To apply this to an entity open the LightSpeed Model Explorer (View > Other Windows > LightSpeed Model) and in the tree view locate the entity that you want to cache. Right-click it and choose Add New Caching Options. Then expand the entity, select the Caching Options node, and you will see the option for Cache Find All Result in the Properties grid. Caching Considerations Caching can sound like a wonderful way to easily improve performance of your application however it is not a silver bullet. Primary concerns to consider is cache item expiry and if stale data could potentially impact your system. Cache expiry is a complex topic and worth reading more about if you are seriously considering second level caching options for a production application. Consider why you are trying to cache data more aggressively and if LightSpeed caching is the best solution. For example, with web applications it may be far more efficient to enable output caching on the web pages. The initial load of a page may cause several database hits however subsequent requests will not cause any queries to fire and will not invoke the page creation process with .NET. Mindscape LightSpeed User Guide 260 Working with the Cache Manually Second level cache items can be interacted with directly by accessing the UnitOfWork.Context.Cache object. Through this object cache items can be added, updated and removed. Mindscape LightSpeed User Guide 261 Database Hints When you send a query to a database, the database engine works out how to execute that query using various heuristics. This usually results in an extremely efficient plan. However, sometimes you can work out a better plan based on your knowledge of the database or of the broader application context. For example, you might have found through testing that the database engine is not using an index which could improve performance. Or you might know that it doesn’t matter if a particular query reads rows that are in the process of being written, meaning the database engine can skip the safety overhead of a lock. In such situations, some databases allow you to pass hints to the database engine on how to execute the query. This section shows how to specify these hints on LightSpeed queries. In all cases, it’s important to remember that hints are exactly that – hints. The query planner doesn’t have to obey them. You are providing advice on how to execute the query: the database engine is free to overrule that advice. Index Hints An index hint advises the database to use a particular index. You can pass an index hint using the WithIndexHint operator, providing the name of the index you want to use. Providing an index hint var orders = unitOfWork.Orders .WithIndexHint("IX_OrderDueDate") .Where(o => o.DueDate < today) .ToList(); You can pass multiple index names to WithIndexHint if required. At the time of writing, index hints are supported on Oracle and SQL Server. Using an index hint on another database does not cause an error – since hints are advisory anyway – but is ignored. Mindscape LightSpeed User Guide 262 Table Hints A table hint advises the database about the way the query uses the table being queried. Table hints can be used for a variety of tasks. For example, the SQL Server NOLOCK table hint indicates that the table need not be locked during the query, trading improved performance for the risk of dirty reads. You can pass a table hint using the WithTableHint operator. Providing a table hint var orders = unitOfWork.Orders .WithTableHint("NOLOCK") .Where(o => o.DueDate < today) .ToList(); Table hints are database-specific – LightSpeed simply passes the raw hint text to the database. At the time of writing, table hints are supported only on SQL Server. Using a table hint on another database does not cause an error – since hints are advisory anyway – but is ignored. Database Hints Using Query Objects If you are using query objects instead of LINQ, you can provide hints through the Query.Hints object. Providing hints using query objects Query query = new Query(Entity.Attribute("DueDate") < today); query.Hints.Indexes.Add("IX_OrderDueDate"); query.Hints.TableHints.Add("NOLOCK"); var orders = unitOfWork.Find<Order>(query); Mindscape LightSpeed User Guide 263 Measuring Performance Although you can use standard .NET tools such as profilers to measure performance in LightSpeed applications, the LightSpeed logging interface allows you to carry out simple measurements of query performance. For each database access, you can have LightSpeed log the SQL sent to the database, and how long it took for the database to respond. You can view this information through the console or Visual Studio Output window, or capture or analyse it by processing CommandLog records in a custom logger. See Logging in the Testing and Debugging chapter for more information. Mindscape LightSpeed User Guide 264 Intercepting Queries LightSpeed provides a facility to intercept interactions with the ADO.NET provider and hooks within the querying pipeline to allow you to either inspect the query and ADO.NET objects for logging or profiling purposes or to manipulate them to apply security or filtering concerns. Intercepting ADO.NET interactions If you require an awareness of the interactions that LightSpeed takes with ADO.NET Firstly a generic interceptor class which if set will give you access to the ADO.NET interactions. To create an interceptor you must derive from the Interceptor base class in the Mindscape.LightSpeed.Profiling namespace and then implement override methods as required. Here is an example of using this approach to couple with the ASP.NET Mini Profiler: Implementing a custom Interceptor class public class MiniProfilerInterceptor : Interceptor { public override IDbConnection CreateConnection(Func<IDbConnection> baseCreator) { var realConnection = baseCreator(); return ProfiledDbConnection.Get((DbConnection)realConnection, MiniProfiler.Current); // or return new ProfiledDbConnection(...) depending on which version you are using } public override IDbCommand CreateCommand(Func<IDbCommand> baseCreator) { var realCommand = baseCreator(); return new ProfiledDbCommand((DbCommand)realCommand, null, MiniProfiler.Current); } } To attach an interceptor either assign a new instance to the Interceptor property on your LightSpeedContext instance, or set it via the context configuration. Mindscape LightSpeed User Guide 265 Assigning an interceptor by configuration <lightSpeedContexts> <add name="Development" interceptorClass="MyAssembly.MyInterceptor,MyAssembly" /> </lightSpeedContexts> Intercepting LightSpeed queries Sometimes entities come with a ‘natural’ filter — a filter that should be applied by default to all queries, because the database contains stuff that users shouldn’t normally see. The classic example, which is built into the LightSpeed framework, is soft deletion: entities can be kept in the database but marked as logically deleted, and ‘deleted’ entities shouldn’t be returned from queries. But there are other common use cases for this which aren’t built into LightSpeed, including liveness, currency and security filters. For example: In a membership system, you might want queries to consider only active members (liveness). In a system where data has a time range during which it is valid, you might want queries to consider only data that are valid at a particular time (currency). In a case management system, users should be able to see only cases that they are working on (security). To solve these requirements you can implement query based interception filters, which allow you to add filter criteria declaratively at the entity level. Declaring how an entity type should be filtered To declare that an entity type should be automatically filtered, apply the QueryFilterAttribute. QueryFilterAttribute takes a type, which must implement the IQueryFilter interface. Mindscape LightSpeed User Guide 266 [QueryFilter(typeof(IsActiveFilter))] partial class Member { ... } public class IsActiveFilter : IQueryFilter { ... } Filtering by interface In models where implicit filters make sense, it’s likely that many entities in the model will need similar filters. For example, if several entity types have time ranges during which they’re valid, you’ll probably want to apply the same currency filter to all of them. To support this, LightSpeed allows you to specify QueryFilterAttribute on an interface, and applies the filter to all types that implement that interface. Here’s an example: [QueryFilter(typeof(IsCurrentFilter))] public interface IValidFromTo { DateTime ValidFrom { get; } DateTime ValidTo { get; } } // Both these entity types inherit IsCurrentFilter from the interface partial class Product : IValidFromTo { ... } partial class Sku : IValidFromTo { ... } With this idiom the interface becomes a powerful declarative tool for specifying query behaviour, and the query filter implementation can assume the properties in the interface are available for querying on. Implementing a query filter Now we know how to specify an implicit filter on an entity, either directly or indirectly through an interface, how do we turn that into an actual query? A filter must implement the IQueryFilter interface which is described as: Mindscape LightSpeed User Guide 267 IQueryFilter interface definition public interface IQueryFilter { QueryExpression Filter { get; } } To implement this you must return a LightSpeed QueryExpression object that consults the appropriate entity property. For example if the database table contains an IsActive flag which we want to filter on then you might write a query expression such as the following: public class IsActiveFilter : IQueryFilter { public QueryExpression Filter { get { return Entity.Attribute("IsActive") == true; } } } If you didn’t explicitly model IsActive, but wanted to implicitly archive all members who hadn’t logged in for, say, 180 days, your filter would look a bit more complex, but it would still be a familiar query expression object: public class IsActiveFilter : IQueryFilter { public QueryExpression Filter { get { DateTime cutoff = DateTime.UtcNow.AddDays(-180); return Entity.Attribute("LastLogin") < cutoff; } } } Mindscape LightSpeed User Guide 268 Injecting values into implicit filters By default, when LightSpeed runs a query on an entity with an implicit filter, it instantiates the IQueryFilter object using its default constructor. This is fine for the IsActive case where there’s nothing to parameterise, but it may be more problematic for the security scenario, where the query filter will depend on the logged-in user. In most cases, this will be available through some global mechanism — in a Web application, for example, the filter could use the HttpContext.Current.User — but sometimes it will be useful to inject data into a filter or otherwise control its behaviour on a per-instance basis. To do this, you need to use an Interceptor and override the CreateQueryFilter method. This allows you full control over the creation and initialisation of the IQueryFilter object Turning off filtering Occasionally you will want to turn off an implicit filter, for example in an admin interface where you want to be able to work with inactive users or have permission to view all cases. To do this, just return null from your IQueryFilter.Filter implementation. You’ll need to implement some way of signalling to your IQueryFilter when it should return null — currently you can do this using a global flag or via an ADO.NET based interceptor. Mindscape LightSpeed User Guide 269 Implementing Storage Policies with LightSpeed A storage policy refers to a decision about how entities should be stored. LightSpeed supports a number of storage policies through convention-based mappings. Mindscape LightSpeed User Guide 270 Entity Tracking Storing Creation and Update Times To have LightSpeed automatically store the time when entities of a particular type are created and updated, select the entity and set Track Create Time and/or Track Update Time to true. The Track Create Time option creates a field named CreatedOn. LightSpeed automatically populates this field when the entity is first saved. You can access this field normally and use it in queries. The Track Update Time option creates a field named UpdatedOn. LightSpeed automatically populates this field whenever the entity is saved. As with CreatedOn, you can access this field normally and use it in queries. The database must contain the corresponding backing columns for whichever options are selected. If you use Update Database or Create Migration in the designer, it will create these columns for you. If you create these columns manually, they must be nonnullable columns of date-time type. Storing Creating and Updating Users To have LightSpeed automatically store the user who created or last updated entities of a particular type, add CreatedBy and/or UpdatedBy string fields to the entity, and set the LightSpeedContext.AuditInfoMode. If AuditInfoMode is set, LightSpeed automatically populates the CreatedBy field when the entity is first saved, and automatically updates the UpdatedBy field each time the entity is saved. Like the timestamp fields, you can access these fields normally and use them in queries. You should normally mark the fields as Load Only so that application code does not try to modify them. For compatibility reasons, the CreatedBy and UpdatedBy fields are not used for automatic storage by default: you must enable the policy through the AuditInfoMode. Mindscape LightSpeed User Guide 271 Because different applications identify users in different ways, the user id written into the CreatedBy and UpdatedBy fields depends on the AuditInfoMode. See below for further information. Mindscape LightSpeed User Guide 272 Soft Deletion By default, when you delete an entity through LightSpeed, it is deleted from the database – that is, LightSpeed sends a SQL DELETE statement. However, you can specify that entities of a particular type should be soft deleted – that is, instead of being deleted, they should be left in the database but marked with a ‘deleted’ flag. To have LightSpeed use soft deletion for an entity type, select the entity and set its Soft Delete option to true. The Soft Delete option creates a field named DeletedOn. The DeletedOn field is a nullable DateTime, which is null by default, but which LightSpeed automatically populates when the entity is deleted. The database must contain a corresponding DeletedOn column. If you use Update Database or Create Migration in the designer, it will create this column for you. If you create the column manually, it must be a nullable column of date-time type. Storing Which User Deleted an Entity To have LightSpeed automatically store the user who deleted a soft-deleted entity, add a DeletedBy string fields to the entity, and set the LightSpeedContext.AuditInfoMode. If AuditInfoMode is set, LightSpeed automatically populates the DeletedBy field when the entity is deleted. You should normally mark the field as Load Only so that application code does not try to modify it. For compatibility reasons, the DeletedBy field is not used for automatic storage by default: you must enable the policy through the AuditInfoMode. Because different applications identify users in different ways, the user id written into the DeletedBy field depends on the AuditInfoMode. See below for further information. Obviously, DeletedBy is not meaningful for hard-deleted entities. Mindscape LightSpeed User Guide 273 Loading Soft Deleted Entities Soft deleted entities remain in the database after deletion, and can be retrieved using LightSpeed. To include deleted entities in a query, apply the IncludeDeleted operator to the query: Loading soft deleted entities var allOrders = unitOfWork.Orders.IncludeDeleted(); (If using query objects, set Query.IncludeDeleted to true.) IncludeDeleted does not limit the query to deleted entities – it will return both deleted and non-deleted entities. You can use the DeletedOn field to distinguish deleted entities. LightSpeed does not provide a way to purge or restore soft deleted entities. These are database administration tasks and should be handled as such. Mindscape LightSpeed User Guide 274 Timestamps for Entity Tracking and Soft Deletion By default, the times stored by LightSpeed for the CreatedOn, UpdatedOn and DeletedOn fields are the current local time of the computer where LightSpeed is running. You may want to override this. For example, if a database is shared across multiple time zones, you may want to use UTC or some other standard time zone so that the sort order is consistent regardless of which site created or updated a record. Or you may want to force a ‘fake’ time for unit testing purposes. To do this, set LightSpeedContext.AutoTimestampMode in code, or the autoTimestampMode attribute in configuration. The available values are defined by the AutoTimestampMode enumeration. Setting the timestamp strategy in configuration <add name="Test" autoTimestamps="Utc" /> Built-In Timestamp Strategies There are two built-in timestamp strategies, Local and Utc. Local: The timestamp is the local time of the computer where LightSpeed is running. Utc: The timestamp is UTC/GMT. In both cases the time comes from the clock of the machine where LightSpeed is running. If clocks are not well synchronised then the errors will be reflected in the stored timestamps. Using a Custom Timestamp Strategy If you use the Custom mode, you must also implement IAutoTimestampStrategy, and set LightSpeedContext.CustomAutoTimestampStrategy to an instance of that implementation. Mindscape LightSpeed User Guide 275 Implementing IAutoTimestampStrategy for unit testing public class FixedAutoTimestampStrategy : IAutoTimestampStrategy { private DateTime _timestamp; public FixedAutoTimestampStrategy(DateTime timestamp) { _timestamp = timestamp; } public AutoTimestampMode Mode { // Custom strategies must always return AutoTimestampMode.Custom get { return AutoTimestampMode.Custom; } } public DateTime GetTime() { return _timestamp; } } Using the custom strategy _context.AutoTimestampMode = AutoTimestampMode.Custom; _context.CustomAutoTimestampStrategy = new FixedAutoTimestampStrategy(Tests.NominalTime); You can also specify a custom timestamp strategy in configuration using its assembly-qualified type name. In this case the strategy class cannot take constructor parameters. Setting a custom timestamp strategy in configuration <add name="Test" autoTimestamps="MyApp.MyTimestampStrategy, MyApp" /> Mindscape LightSpeed User Guide 276 Database-side Timestamping LightSpeed 5 also includes support for database-side timestamps. With your custom timestamp strategy implementation, have it also implement the IAutoTimestampExpressionStrategy interface. Your GetTimeExpression() method should return the desired SQL expression for the column, or null to use the client-side timestamp returned from IAutoTimestampStrategy.GetTime(). Here’s one implementation: Setting a custom timestamp strategy in configuration public object GetTimeExpression(Type type, TimestampUsage usage, LightSpeedContext context) { if (usage == TimestampUsage.DeletedOn) return null; return "DEFAULT"; // or "GETDATE()" or "GETUTCDATE()" etc. } LightSpeed will not read back the timestamp generated by the database. After saving changes, therefore, the entity will contain the timestamp returned from IAutoTimestampStrategy.GetTime(). If your application needs to know the timestamp generated by the database, you will need to reload the entity. If your application needs to be portable across different database engines, you can use the LightSpeedContext.DataProvider value to determine which database is being targeted. Mindscape LightSpeed User Guide 277 User Ids for Entity Tracking and Soft Deletion Different applications identify users in different ways. There is no default way of identifying a user. So in order to store user names in the CreatedBy, UpdatedBy and DeletedBy fields, you must specify to LightSpeed how it should identify users. To do this, set LightSpeedContext.AuditInfoMode in code, or set the auditInfo attribute in configuration. The available values are defined by the AuditInfoMode enumeration. Setting how users are identified in configuration <add name="Test" auditInfo="HttpContext" /> Built-In User Identification Strategies There are two built-in user identification strategies, HttpContext and WindowsIdentity. HttpContext: The user id is taken from the HttpContext.Current.User.Identity. The application should ensure that users are authenticated. WindowsIdentity: The user id is the Windows login of the active user. Using a Custom Identification Strategy Some applications have their own means of identifying users, such as a custom user database or directory. To make LightSpeed use such a custom identification method, specify the Custom AuditInfoMode, implement IAuditInfoStrategy, and set LightSpeedContext.CustomAuditInfoStrategy to an instance of that implementation. Mindscape LightSpeed User Guide 278 Implementing IAuditInfoStrategy for unit testing public class FixedAuditInfoStrategy : IAuditInfoStrategy { private string _user; public FixedAuditInfoStrategy(string user) { _user = user; } public AuditInfoMode Mode { // Custom strategies must always return AuditInfoMode.Custom get { return AuditInfoMode.Custom; } } public string GetCurrentUser() { return _user; } } Using the custom strategy _context.AuditInfoMode = AuditInfoMode.Custom; _context.CustomAuditInfoStrategy = new FixedAuditInfoStrategy(Tests.NominalUser); You can also specify a custom audit information strategy in configuration using its assembly-qualified type name. In this case the strategy class cannot take constructor parameters. Setting a custom audit info strategy in configuration <add name="Test" auditInfo="MyApp.MyAuditInfoStrategy, MyApp" /> Mindscape LightSpeed User Guide 279 Concurrent Editing LightSpeed uses optimistic concurrency: that is, it allows users to edit an entity without taking a lock, optimistically assuming that it is okay for users to edit the same entity at the same time. When each user commits their changes, their version of the entity is persisted. LightSpeed doesn’t check whether there has been a concurrent edit. This is effectively a ‘last one wins’ policy. LightSpeed also supports concurrency checking, which results in an exception being thrown if a user tries to save an entity when that entity has been modified since the user first loaded it. This is effectively a ‘first one wins’ policy. To enable concurrency checking for entities of a particular type, select the entity and set Optimistic Concurrency Checking to true. The Optimistic Concurrency Checking option creates a field named LockVersion. LightSpeed automatically increments this field each time the entity is saved, and verifies that the version of the entity in the database is still the version that was originally loaded. If not, it means that somebody else has changed the entity in the meantime, and LightSpeed raises an OptimisticConcurrencyException. The database must contain the corresponding backing column for the LockVersion field. If you use Update Database or Create Migration in the designer, it will create this column for you. If you create the column manually, it must be a non-nullable column of integer type. Guidance for Optimistic Concurrency Checking If a concurrency check fails, LightSpeed raises an OptimisticConcurrencyException. However, the exception does not indicate which entity the error relates to. You should therefore normally use optimistic concurrency checking only in scenarios where users will be editing one entity at a time. If you must allow batch editing, then if an exception occurs you will need to query the database to figure out which entities have changed and are causing the error. LightSpeed does not provide a way to revert entities or merge changes. To resolve an OptimisticConcurrencyException, you will typically need to load the latest versions of Mindscape LightSpeed User Guide 280 the entities you want to save into a fresh unit of work, and merge the changes from the stale unit of work (where the original edits took place) into the fresh unit of work. You can use entity property change tracking and the metadata API to help with this. Mindscape LightSpeed User Guide 281 Implementing Policies in Hand-Coded Entities As indicated above, LightSpeed’s storage policies are implemented using specially named fields. In fact, storage policies are determined by the presence of these specially named fields. In hand-coded entities, therefore, you can implement a storage policy just by declaring a field with the appropriate name. Implementing time tracking in a hand-coded entity public class Document : Entity<int> { // "Field is never assigned to" - LightSpeed assigns these fields internally #pragma warning disable 649 private readonly DateTime _createdOn; private readonly DateTime _updatedOn; #pragma warning restore 649 public DateTime CreatedOn { get { return _createdOn; } } public DateTime UpdatedOn { get { return _updatedOn; } } } Special fields should normally be marked readonly as they should be modified only by LightSpeed, not by application code. This will cause a compiler warning because there is no way to set the fields: you should disable this warning as shown above. ASP.NET medium trust environments do not allow LightSpeed to modify readonly fields: if you plan to use your model in such an environment, implement the special fields as read-write. Column Names for Policy Fields The LightSpeed fields for storage policies must have the names listed above. If you need to use different column names in the database, you can map them using ColumnAttribute or LightSpeedContext.NamingStrategy. This option is only available when hand-coding entities. Mindscape LightSpeed User Guide 282 Change Tracking It is sometimes useful to be able to get a list of changes between the originally loaded version of an entity and the in-memory version. For example, this could be used for confirmation or auditing purposes. LightSpeed supports this through the Entity.ChangeTracker property. Change tracking is not enabled by default because it can add a significant overhead to the size of each entity. To enable change tracking, set ChangeTracker.TrackingMode to ChangeTrackingMode.ChangesOnly. (Only changes made after you have enabled change tracking are recorded.) To get the list of changes, use the ChangeTracker.Changes property. Saving Only Changed Fields Enabling change tracking does not change LightSpeed’s default behaviour of saving all fields. This is because change tracking does not in itself support optimistic concurrency checking. To save only changed fields you have two options available. Either set Optimistic Concurrency Checking to true in the designer (or create a LockVersion field in your entity), as described under Concurrent Editing above or you can apply the [UnversionedPartialUpdate] attribute to your entity to indicate that only changed fields should be persisted if the values in the database match the original values when the entity was loaded. Mindscape LightSpeed User Guide 283 Working with Legacy Databases LightSpeed is primarily designed around a ‘convention over configuration’ approach, in which your code maps naturally to your database schema. In a greenfield application, this produces simple, well normalised database schemas that are well aligned with the domain code, in accordance with the principle of a ‘ubiquitous language’ used across the system. When you have an existing database, though, you may not be able or willing to modify the database to follow LightSpeed’s conventions, because of technical risk, compatibility constraints with other applications or simple lack of time. This chapter describes some techniques for working with such databases using LightSpeed. Mindscape LightSpeed User Guide 284 Invoking Stored Procedures Legacy databases often make extensive use of stored procedures to adapt a complex table structure or to wrap business logic up in the database. For information about invoking stored procedures, see the chapter Controlling the Database Mapping. Mindscape LightSpeed User Guide 285 CRUD Stored Procedures Some environments mandate that all database access be through stored procedures. The reasons vary, but usually centre around either performance – the architects believe that stored procedures are faster than table accesses – or security – denying application code access to tables prevents SQL injection. The result of such policies is that every table comes with a set of stored procedures which do nothing more than perform CRUD actions. The best way to handle such an environment is to overthrow the ‘stored procedures only’ policy. In most modern large-scale databases, the query planner is so efficient that the performance argument no longer holds water. And LightSpeed always uses parameters to pass values in SQL statements, preventing SQL injection through unsanitised data. The inconvenience, limitations and inefficiency of CRUD procedures dwarf whatever marginal benefits they may have. If it is not possible to get direct table access, you can force LightSpeed to use stored procedures for its CRUD operations. To do this, select each entity and change its Access Method to StoredProcedures. The designer displays a new section in the property grid named Access Procedures: fill this section out with the names of the CRUD procedures. Mindscape LightSpeed User Guide 286 In addition, you will need to tell LightSpeed to use stored procedures to load child collections in associations. To do this, select the association arrow and fill in the Select Procedure option. CRUD Procedure Conventions You can define five access procedures for entity CRUD operations. Select Procedure: Selects all entities of this type. Select By Id Procedure: Selects a single entity by Id. The procedure should take a single parameter, whose name is the Identity Column Name for the entity, and whose value is the entity Id to select. Insert Procedure: Inserts a single entity. The procedure should take a parameter for each column, with the same name as that column. Update Procedure: Updates a single entity. The procedure should take a parameter for each column, with the same name as that column. Delete Procedure: Deletes a single entity. The procedure should take a single parameter, whose name is the Identity Column Name for the entity, and whose value is the entity Id to delete. You can also define an access procedure on an association, for looking up child collections: Select Procedure: Selects all entities of the child type whose foreign key is equal to the parameter value. The procedure should take a single parameter, whose name is the column name of the foreign key of the association, and whose value is the Id of the parent. CRUD procedures must also adhere to any database-specific conventions for LightSpeed stored procedures. This specifically affects Oracle databases. See Working with Database Providers for details. CRUD Procedure Limitations CRUD procedures prevent you from using many aspects of LightSpeed. For example, you cannot use ad hoc queries or eager loading with entities that are loaded through Mindscape LightSpeed User Guide 287 CRUD procedures. You will need to create and invoke specific stored procedures for any queries you want to use. Mindscape LightSpeed User Guide 288 Using Natural Keys LightSpeed adopts the convention that entities are identified by surrogate keys; that is, opaque identifiers with no business meaning, whose sole purpose is to be unique within a table. Because surrogate keys aren’t derived from application data, their actual values are not important, so LightSpeed can assign values using one of the identity methods discussed under Identity Generation in the chapter Controlling the Database Mapping. Many existing databases don’t use surrogate keys. Instead, they identify entities using a piece of application data which is presumed to be unique, but has business meaning. Such pieces of application data are called natural keys. Natural keys can be problematic, because as applications and business environments change, application data can turn out to be not as unique, or not as immutable, as originally assumed. So if you have a database which uses natural keys, it’s worth evaluating whether you could change it over to use surrogate keys. Unfortunately, this often isn’t possible for reasons of application compatibility or technical risk. Assuming you have to use natural keys, this means LightSpeed can no longer assign its own Ids to entities, because the Id has to be derived from the business data in some entity-specific way. Instead, you must implement your own Id generation logic. Implementing the GeneratedId Method When LightSpeed needs to assign an Id to an entity, it calls the entity’s GeneratedId method. By default, this hands off to the identity method specified in the LightSpeedContext or on the entity. When you need to use a natural key, you can override GeneratedId to return an item of business data. Mindscape LightSpeed User Guide 289 Implementing GeneratedId protected override object GeneratedId() { return _productCode; // See remarks below } Natural Keys and Column Mappings LightSpeed maps databases columns to fields. Each column is mapped at most once. Consequently, a natural key column, which is mapped to the implicit Id field, cannot also be mapped to another persistent field. Therefore, assuming you need to store the natural key in the entity while waiting for LightSpeed to call GeneratedId, the temporary field you store it in must be transient. Implementing GeneratedId public partial class Product : Entity<string> { [Transient] private string _productCode; // Must be transient protected override object GeneratedId() { return _productCode; } } If the _productCode field were not transient, LightSpeed would try to map it to a ProductCode column in the database. If there was no ProductCode column, this would cause an error. If ProductCode was the natural key (Id) column, then that column would now be mapped twice, once to Id and once to _productCode – also an error. (If there was a ProductCode column, independent of the natural key, then there would be no error, but this is unlikely, as it would mean the product key was stored in two places. However, something like this could happen if the natural key were derived from other columns, but not identical to those columns.) Mindscape LightSpeed User Guide 290 When the Natural Key is Assigned LightSpeed assigns an Id to an entity when that entity first joins a unit of work. Your GeneratedId implementation will be called at this point. Therefore, you must make sure that any business data you need for the natural key is ready before the entity joins a unit of work. Ids are immutable, so you do not get a second chance! An entity joins a unit of work when you call IUnitOfWork.Add for that entity or any associated entity, or when you associate it with another entity that is already part of a unit of work (by using an association setter or by calling EntityCollection<T>.Add). It is important to remember that entities can be implicitly added to a unit of work through an association. It is strongly recommended that you initialise the natural key data immediately after creating a new entity. This minimises the chance that you might accidentally add the entity to a unit of work – and thereby trigger a call to GeneratedId – before the natural key data is available. It is also a good idea to put a check, such as a Debug.Assert, in GeneratedId to verify that it is not called before the data is available. Natural Keys and the IdentityColumn Identity Method A natural key is never a database autoincrement/identity column. You must therefore never specify the IdentityColumn identity method when using natural keys. Any other identity method will be ignored, because your GeneratedId override takes precedence, but using IdentityColumn will result in an error because LightSpeed performs special handling for it during an INSERT. (Specifically, it prevents LightSpeed sending the Id to the database in the INSERT.) Mindscape LightSpeed User Guide 291 Using Composite Keys Composite keys are similar to natural keys in many ways, except that a composite key comprises multiple database columns, and must therefore be mapped as a value object. Composite Key Types Every entity in LightSpeed has an Id property. In the case of a composite key, the Id property has to contain multiple values. This happens by making the identity type a composite type – a struct (.NET value type) with a field for each column of the composite key. The columns of the composite key will be mapped to the fields of this identity type. To refer to composite key columns, therefore, you must refer to entity.Id.field_name; the columns are members of the composite Id, not distinct properties on the entity. Composite Keys in the Designer When you drag a table with a composite key onto the designer, LightSpeed infers the key columns from the database primary key, and displays identity fields for the key columns. The identity type is automatically generated. Its name is the entity name followed by Id. For example, an entity named InternationalProduct will have an identity type named InternationalProductId. Mindscape LightSpeed User Guide 292 You can create composite keys in a model-first approach, by using the LightSpeed Model Explorer to add identity properties to an entity. You should not normally do this: if you are defining new entities rather than mapping existing tables, you should use a surrogate key instead. Composite Keys in Hand-Coded Entities If you are hand-coding an entity with a composite key, you must provide an identity type, either by locating an existing type that fits the composite key columns or (more usually) by coding a specific identity type. The identity type must be a struct (value type) and should be immutable (all fields marked readonly). A hand-coded composite key type public struct InternationalProductId { private readonly int _productId; private readonly string _languageId; public InternationalProductId(int productId, string languageId) { _productId = productId; _languageId = languageId; } public int ProductId { get { return _productId; } } public string LanguageId { get { return _languageId; } } // Overrides of Equals and GetHashCode omitted for clarity. // You should always provide these methods as they can markedly // affect efficiency. } Mindscape LightSpeed User Guide 293 You can then specify this type as the TId type parameter when deriving from Entity<TId>: Declaring a composite keyed entity public class InternationalProduct : Entity<InternationalProductId> Assigning Composite Keys Composite keys are assigned in the same way as other natural keys: by overriding the GeneratedId method. Similar considerations apply in terms of storing Id generation data in transient fields and ensuring the Id generation data is available before the entity joins a unit of work. The GeneratedId override must return an instance of the composite key type. Generating the value for a composite key [Transient] private InternationalProductId _pendingId; public InternationalProduct(int productId, string languageId) { _pendingId = new InternationalProductId(productId, languageId); } protected override object GeneratedId() { return _pendingId; } Composite Foreign Keys When another table has a foreign key to a composite-keyed table, the foreign key field must be of the composite key type, the foreign key columns must be mapped to the fields of the composite key type, and the foreign key field must be marked as a value object. Mindscape LightSpeed User Guide 294 An association with a composite foreign key public class Advert : Entity<int> { [ValueObject] [ValueObjectColumn("ProductId", "PRD_ID")] [ValueObjectColumn("LanguageId", "LANG_ID")] private InternationalProductId _productId; public InternationalProductId ProductId { get { return _productId; } set { Set(ref _productId, value, "ProductId"); } } private readonly EntityHolder<InternationalProduct> _product = new EntityHolder<InternationalProduct>(); public InternationalProduct Product { get { return Get(_product); } set { Set(_product, value); } } } If you are using the designer, it can generate this code for you, but it may not be able to infer the column mappings from the database. You may therefore need to manually set up the association and map columns. Mindscape LightSpeed User Guide 295 Foreign Keys That Are Part of a Composite Key In some cases, one of the columns of a composite key may also be the foreign key for an association. For example, in the InternationalProduct example, InternationalProduct.Id.ProductId might be the foreign key for an association to a Product entity. This runs counter to the LightSpeed convention that an association’s foreign key is represented by a field with the name association_nameId. To handle this case, select the association arrow and enter the path to the key field in the Key Property Reference box. (In code, apply ForeignKeyFieldAttribute to the EntityHolder.) In the example above, the path would be Id.ProductId. Composite Foreign Keys That Overlap the Primary Key The Key Property Reference technique works when you have a simple foreign key which happens to be a column in the composite primary key. In some cases, you will have a composite foreign key, some of whose fields are columns in the primary key. In this case, you cannot use Key Property Reference, because there is no single field which acts as the foreign key; nor can you provide a composite foreign key field, because that would require the overlapping columns to be mapped twice. Instead, you must provide a custom association resolver. This is a type which implements IAssociationResolver and returns a QueryExpression describing how to look up an associated entity or collection. Using a custom resolver brings certain limitations particularly around eager loading and cascade delete. It is strongly recommended that you try to modify your database schema to avoid the use of overlapping foreign and primary composite keys. If using a custom resolver is unavoidable, contact Mindscape technical support for a sample. Many-to-Many Associations Represented As Composite Keys A common idiom for many-to-many associations, even in databases that otherwise use surrogate keys, is to omit the surrogate key on the through table. That is, the through table contains foreign keys to the tables being joined, and its primary key is a composite key on these two columns rather than a separate Id column. For Mindscape LightSpeed User Guide 296 information about implementing this in LightSpeed, see the online article Many-tomany associations and composite keys in LightSpeed. Mindscape LightSpeed User Guide 297 Mapping Database Types to Domain Types Many legacy databases have their own ways of storing data – some systematic conventions, some ad hoc. Often, these storage formats aren’t the way you want to represent the data in the domain model. For example, many databases without a Boolean type tend to store Boolean values using single-character strings, “Y” for true and “N” for false. You would not want to surface these columns as strings in the domain model, because that would be confusing to users of the domain model, and could lead to errors if users tried to enter arbitrary strings. Rather, you would prefer to surface these columns as Boolean fields, and translate between the database storage format and the domain type within the entity. There are two ways of doing this in LightSpeed: custom wrappers, and field converters. Custom Wrappers As previously noted, LightSpeed maps .NET fields to database columns, and ignores .NET properties. This means you are free to define wrapper properties or methods with different types from the underlying field. These wrappers can convert between the domain type, which they present in the public interface of the entity, and the database representation, which is encapsulated in the private fields. To implement this, select the field you want to wrap and set its Generation option to FieldOnly. Then open or create a partial class file for the entity, and implement the wrapper property or methods yourself. Mindscape LightSpeed User Guide 298 Surfacing a database string as a domain Boolean property partial class Member { // _isModerator string field is in designer generated code public bool IsModerator { get { return _isModerator == "Y"; } set { string dbIsModerator = value ? "Y" : "N"; Set(ref _isModerator, dbIsModerator, "IsModerator"); } } } Remember to use the Entity.Set method to set the value of persistent fields. A crucial pitfall of this approach is that the translation logic is not applied to queries. Users must write queries in terms of the underlying database format, not the domain format. This is confusing when using the query objects syntax, and is not possible at all in LINQ: Entity.Attribute("IsModerator") == "Y" Entity.Attribute("IsModerator") == true // works // fails from m uow.Members where m.IsModerator == "Y" select m // compiler error, can’t compare bool and string Therefore, if you use a custom wrapper, and anticipate users wanting to query on the wrapped field, be sure to encapsulate all querying logic behind a repository API which can perform the necessary translations. Field Converters A field converter encapsulates the translation between database and domain as a reusable object. This is useful for two reasons: first, it allows that translation to be applied to queries; and second, it allows the same translation to be applied to many Mindscape LightSpeed User Guide 299 different fields without needing to write a custom wrapper for each. There are also specialised cases which are problematic for custom mapping, such as LightSpeed 3-style data transfer objects, or TimeSpan fields where LightSpeed’s built-in handling may clash with a desired database-specific mapping. To implement a field converter, implement the IFieldConverter interface, or derive from the FieldConverter<TDatabase, TModel> base class. Converting a database string to a Boolean value public class YesNoConverter : FieldConverter<string, bool> { protected override bool ConvertFromDatabase(string databaseValue) { return "Y".Equals(databaseValue, StringComparison.OrdinalIgnoreCase); } protected override string ConvertToDatabase(bool value) { return value ? "Y" : "N"; } } The LightSpeed designer represents columns requiring conversion using a user-defined type. For example, you might represent the Y/N Boolean idiom as a YesNo type. This saves you applying the converter separately to each field that needs it – instead, the user-defined type bundles up the domain type, the database type and the mapping between them. Note that the user-defined type will still surface in the domain model as (in this case) a Boolean. To create a user-defined type that represents a custom format, open the LightSpeed Model Explorer, right-click the root model node, choose Add New User Defined Type, and apply the following settings: Name: The name to appear in the Data Type drop-down, e.g. YesNo. CLR Type Name: How this type should be surfaced in the domain model, e.g. System.Boolean. This should be a namespace-qualified CLR type name (see Enums and Other User-Defined Types in the chapter Working with Models in the Visual Designer for more information). Mindscape LightSpeed User Guide 300 Is Value Type: Set this to True or False depending on whether the CLR type is a struct or a class. Data Type: How this type is represented in the database, e.g. String. Converter Type: The name of the class which converts between the database representation and the CLR type, e.g. MyProject.YesNoConverter. Is Standard Data Type: You can usually leave this as True. However, if the storage format uses a database-specific type such as the MySQL time type, set it to False, and enter the database type name in the Database Type Name box. Mindscape LightSpeed User Guide 301 Once you have defined a user-defined type to represent a custom storage format, you can use it just like any other data type, by selecting it from the Data Type drop down: LightSpeed will now generate a boolean property, but map it to a string column. Furthermore, now that the user-defined type has been set up, you can apply it to as many properties as you want, which makes it very convenient for mapping patterns that are used widely across a database. Field Converters for Hand-Coded Entities You can also use field converters with hand-coded entities. This is done in a slightly different way from the designer. Instead of creating a user-defined type, you simply define each field as being of the desired domain type, then apply ColumnAttribute to the field, specifying the ConverterType property. Mindscape LightSpeed User Guide 302 Using a field converter in a hand-coded entity [Column(ConverterType = typeof(YesNoConverter))] private bool _isFullOfGreeks; // domain type public bool IsFullOfGreeks { get { return _isFullOfGreeks; } set { Set(ref _isFullOfGreeks, value, "IsFullOfGreeks"); } } Field Converters and Querying When you use a field that has a field converter in a query, the field converter is used to translate the comparand to its database format. For example, consider the following query: var woes = from h in uow.Horses where h.IsFullOfGreeks == true select h; The YesNoConverter is applied to the comparand true, resulting in SQL such as: SELECT ... FROM Horse WHERE IsFullOfGreeks = 'Y' // parameter substituted for clarity This enables users of the entity to write queries in domain terms, and to use LINQ without suffering compiler type errors. Querying Considerations for Field Converter Design Some care is required if the conversion is not one-to-one. For example, consider a database convention which represents Booleans as integers, with 0 representing false Mindscape LightSpeed User Guide 303 and any non-zero value representing true. When converting Booleans back to integers, the converter has to pick a value to represent true, say 1. A converter which is not one-to-one public class IntBoolConverter : FieldConverter<int, bool> { protected override bool ConvertFromDatabase(int databaseValue) { return databaseValue != 0; } protected override string ConvertToDatabase(bool value) { return value ? 1 : 0; // danger! } } Suppose now you wrote the LINQ query where o.SomeIntBoolField == true. The converter translates the comparand true to the database value 1. So LightSpeed would emit the SQL clause WHERE SomeIntBoolField == 1. This would not match values such as 2 or -1, even though these are meant to be considered true. You would therefore need to be careful to write the query where o.SomeIntBoolField != false, as this would translate to WHERE SomeIntBoolField <> 0. This is only an issue if your mapping is not one-to-one. Mindscape LightSpeed User Guide 304 Working with Database Providers Although LightSpeed tries to abstract you away from the underlying database engine, different databases do have different capabilities and characteristics and it’s important to be aware of these when using LightSpeed, whether to avoid using unsupported features or to ensure that you adhere to database-specific conventions. Furthermore, in some cases LightSpeed provides special features for certain databases. This chapter lists specific considerations for each database supported by LightSpeed. Mindscape LightSpeed User Guide 305 DB2 LINQ Support The ToString() method is not supported on DB2. The coalesce (??) operator cannot be used with strings on DB2. Some grouping operations fail on DB2. Tools Support LightSpeed offers only limited support for migrations on DB2. Mindscape LightSpeed User Guide 306 MySQL Designer Support In order to drag tables and views onto the designer, you will need MySQL Connector/Net. This is a free download from the MySQL Web site. LightSpeed has not been tested with third-party Visual Studio/MySQL integration add-ins – please contact us if you encounter problems when using such add-ins. Although MySQL Connector/Net is capable of displaying stored procedures, at the time of writing it does not support dragging stored procedures from Server Explorer. This is a limitation of MySQL Connector/Net, not of LightSpeed. You can still add stored procedures to your model using the Toolbox, or invoke them by constructing a ProcedureQuery object in code. Creating Tables from the Designer If you use the LightSpeed designer’s Update Database feature to create MySQL tables, these tables will use the default storage engine for the database. The default storage engine can be set at the database level using MySQL administration tools. For Linux MySQL installations, the default setting is MyISAM; it is normally recommended that you change this to the transactional InnoDB engine. You can override the default storage engine when creating tables by adding a MySQL Roundtripping Policy to your model. To do this, open the LightSpeed Model Explorer (View > Other Windows > LightSpeed Model), right click the model in the tree view, and choose Add New MySQL Roundtripping Policy. You can then set the engine to be whichever engine you would prefer LightSpeed to use for new tables. Mindscape LightSpeed User Guide 307 Oracle Using Oracle Stored Procedures with LightSpeed Oracle stored procedures return row set results through parameters. In order to locate the row set, LightSpeed adopts the convention that the results should be returned through an out parameter of type ref cursor named “results”. ODP.NET Versioning Considerations Depending on your installed version(s) of the Oracle client, when using the Oracle9Odp provider you may receive assembly binding errors for the Oracle.DataAccess DLL of the form: Could not load file or assembly ‘Oracle.DataAccess, Version=10.2.0.100, Culture=neutral, PublicKeyToken=89b483f429c47342’ or one of its dependencies. The system cannot find the file specified. This can be solved using a binding redirect in the application configuration file, of the form: Binding redirect for Oracle ODP.NET <configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Oracle.DataAccess" publicKeyToken="89b483f429c47342" culture="neutral"/> <!-- oldVersion should be the version that was not found --> <!-- newVersion should be the version you have installed --> <bindingRedirect oldVersion="10.2.0.100" newVersion="2.102.2.20" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration> Mindscape LightSpeed User Guide 308 Designer Support Some third-party add-ins which integrate Oracle into Visual Studio (such as the Devart DotConnect add-in) use a connection string format which is not understood by LightSpeed. If you encounter problems when using such an add-in, create a new Server Explorer connection using the .NET Framework Data Provider for Oracle or the Oracle Data Provider for .NET. Mindscape LightSpeed User Guide 309 PostgreSQL Designer Support At the time of writing, the free PostgreSQL driver, Npgsql, does not provide Visual Studio integration to enable drag and drop onto the LightSpeed designer. If you want to import PostgreSQL tables into the designer, there are a number of options: Mindscape has created a free add-in which allows you to display PostgreSQL databases in Server Explorer and drag them onto the designer. Please post in the support forum if you would like to use this add-in. Some commercial PostgreSQL libraries include Visual Studio integration. These have not been tested with LightSpeed but some at least are known to work. Please post in the support forum if you need advice on using a specific commercial library with the LightSpeed designer. You can use the lsgen.exe command line tool with the /l:lsmodel option to generate a .lsmodel file from your database. Mindscape LightSpeed User Guide 310 SimpleDB Connection String Format When using Amazon SimpleDB, LightSpeed expects a connection string in the following format: Access Key=your_access_key;Secret Access Key=your_secret_access_key You can also specify the following additional connection string options: Url: By default, LightSpeed will connect to Amazon’s default SimpleDB endpoint (US East). Provide a Url element in the connection string to connect to another SimpleDB endpoint, such as US West, Europe or Singapore, or to a compatible store such as a local M/DB instance. Consistent Read: By default, LightSpeed uses standard (eventually-consistent) reads. To perform consistent reads, add the option Consistent Read=true to the connection string. Enable Batch Insert: By default, LightSpeed performs inserts one at a time. To enable batching of inserts, add the option Enable Batch Insert=true to the connection string. Limitations Amazon SimpleDB supports only a limited subset of the features of traditional relational databases. Consequently, the following limitations apply when using LightSpeed on SimpleDB: No eager loading. Eager loads are silently translated into lazy loads and no warning is given. No cross-table queries. A query of the form Entity.Attribute(“Contributor.UserName”) == “jd” will not work. No batching of updates and deletes. Each operation is a separate request to SimpleDB. At present, LightSpeed makes no attempt to perform these operations in parallel, so a large number of updates may take a long time. Insert batching must be turned on explicitly in the connection string. Mindscape LightSpeed User Guide 311 No queries involving server-side operations, e.g. SQL functions such as SUM or LOWER or LINQ equivalents such as Sum() or ToLower(). Eventual Consistency and Consistent Reads SimpleDB has an ‘eventually consistent’ storage model. That is, when you make a change, it is guaranteed to propagate through SimpleDB eventually, but it may not be immediately visible on the next read. If you need to ensure that a query retrieves the latest saved data, you can request a consistent read. This trades off performance for currency. By default, LightSpeed uses standard reads. To request consistent reads with LightSpeed, include the Consistent Read=true element in the connection string. This forces consistent reads on all queries. To override the consistent-read setting on a per-query basis, use the WithProviderOptions method and pass a suitable AmazonSimpleDBProviderOptions object. (If you are using query objects, set Query.ProviderOptions.) Set ConsistentRead to true to force a consistent read, to false to force a standard read, and to null to use the default behaviour (standard or consistent according to the connection string). Overriding the consistent read setting for a single query var forceConsistent = new AmazonSimpleDBProviderOptions { ConsistentRead = true } var products = unitOfWork.Products .WithProviderOptions(forceConsistent); Data Storage SimpleDB has no concept of data types: everything is treated as a string. The most important impact of this is on sorting: SimpleDB always performs lexical comparisons. For example, ‘45’ sorts before ‘123’. This is usually not desirable for numeric values. Mindscape LightSpeed User Guide 312 Therefore, when LightSpeed stores data in SimpleDB, it always stores it in a sortable format. Numeric values are padded with leading zeroes to the maximum length of the numeric type, e.g. 0000012345. DateTime values are stored using the ISO 8601 sortable format, e.g. 2001-01-01T01:02:03.004. For maximum efficiency you should therefore use the smallest suitable numeric type (e.g. store a year as a short rather than an int). We also strongly advising using floats or decimals instead of doubles, as a double takes 300+ characters to store! Because negative numbers cannot be range-compared using lexical ordering, you should not use signed types in comparisons unless you are confident that neither the value used in the query, nor any value in SimpleDB, is actually negative. Tools Support In order to drag domains onto the designer, you will need Mindscape SimpleDB Management Tools. You can use the free edition if you do not require SimpleDB management capability. Because SimpleDB treats all data as strings, the designer always infers the string type. Schema round-tripping is not currently available for SimpleDB. Migrations are not supported on SimpleDB. LightSpeed 5 Changes FindById() now has the option to return null for non-existent IDs - this is instead of returning the spurious item SimpleDB usually returns. This option is available by setting the MaterialiseZeroAttributeEntities property on the SimpleDBDataProviderAdapter. Mindscape LightSpeed User Guide 313 SQLite There are no known special considerations for SQLite. Mindscape LightSpeed User Guide 314 SQL Server SQL Server 2008 Spatial Data Types To enable spatial data types at runtime, choose the SqlServer2008 or SqlServer2012 DataProvider in your LightSpeedContext configuration. If you choose this provider, you must distribute Microsoft.SqlServer.Types.dll with you application, even if you do not use the spatial data types. In order to use spatial functions in LINQ queries, you will need to map the appropriate SqlGeography and SqlGeometry methods to the equivalent SQL Server geography and geometry member functions. For information about doing this, see ‘Mapping Member Functions’ in the chapter Advanced Querying Techniques. Time Data Type LightSpeed 5 now has direct support for time data type translation. This is both in the core and in the designer. Stored Procedures Table value parameters can be used in SQL Server 2008 stored procedures. HierarchyId HierarchyId is supported as data type when using the SQL Server 2008 or SQL Server 2012 providers. SQL Server 2012 If you are using SQL Server 2012, you must be using Visual Studio 2010 or Visual Studio 2012 to connect using the Server Explorer. This provider supports using the new Sequence identity type added in SQL Server 2012. Mindscape LightSpeed User Guide 315 SQL Server 2000 Limitations LightSpeed provides only limited support for SQL Server 2000. In particular paging operations (including LINQ operations such as Skip that require paging) are not supported. SQL Server 2000 support will be deprecated in a future version of LightSpeed. SQL Server 2000 Designer Support The LightSpeed designer defaults to using SQL Server 2005 APIs for extracting the database schema. Therefore, if you drag tables from SQL Server 2000 onto the designer, you will get an error. To work around this, click on the model background and set Database Provider to SqlServer2000 before dragging the tables on. If you are using Visual Studio 2010, the only way to connect to SQL Server 2000 through Server Explorer is to use the OLE DB provider. You can still drag tables from an OLE DB connection onto the designer. This is a special workaround for the VS2010 SQL Server 2000 issue: you cannot drag tables from OLE DB for any other database. Note that at runtime you must still use the normal SqlConnection connection string format, not the OLE DB connection string format. SQL Server 2000 support will be deprecated in a future version of LightSpeed. Mindscape LightSpeed User Guide 316 SQL Server Compact Versions SQL Server Compact 3.5 is supported in the main LightSpeed DLL. SQL Server Compact 4 is supported via the supplementary DLL Mindscape.LightSpeed.Providers.SqlServerCe4.dll. If you are using SQL Server Compact 4, you must deploy this to the same directory as the main LightSpeed DLL. Paging and Ordering SQL Server Compact 3.5 does not support paging. Although SQL Server Compact 4 provides some support for paging, this is not comprehensive. Some paging and ordering operations may still fail. LINQ Support Some string operations (notably concatenations with ToString()) fail on SQL Server Compact. The coalesce (??) operator cannot be used with strings on SQL Server Compact. There are several engine limitations which can affect complex LINQ queries, specifically around joining, grouping, composition (e.g. Intersect), subselects and the Distinct operator. Tools Support Migrations have only limited support on SQL Server Compact. Mindscape LightSpeed User Guide 317 VistaDB 4 Paging and Ordering VistaDB does not support paging, except for the specific case of limiting. (That is, you can use the Take operator, but not the Skip operator.) In addition, VistaDB does not support expressions in an OrderBy clause: you can order only by primitive properties, not computed expressions or properties of associated entities. LINQ Support There are several engine limitations which can affect complex LINQ queries, specifically around joining, grouping, composition (e.g. Intersect), subselects and the Distinct operator. Tools Support The designer does not currently support dragging views from VistaDB. Migrations have only limited support on VistaDB. Mindscape LightSpeed User Guide 318 Low Level Database Access It is sometimes useful to send SQL commands directly to a database, or for other reasons to drop down to ADO.NET. LightSpeed supports this through the IDataProviderObjectFactory interface and IUnitOfWork.PrepareCommand and FindBySql methods. Creating ADO.NET Objects with LightSpeed The LightSpeedContext.DataProviderObjectFactory property returns an IDataProviderObjectFactory for the context’s data provider. You can use this to create ADO.NET connection, command and command parameter objects suitable to the database in use. Note that connection objects are not automatically initialised with the context connection string. Using ADO.NET Objects with LightSpeed To associate an ADO.NET command with the connection and transaction of a unit of work, call IUnitOfWork.PrepareCommand. This does not execute the command – you must still call ExecuteReader, ExecuteScalar or ExecuteNonQuery – but it prepares the command to run over the unit of work’s connection, and enrols it in the unit of work’s transaction if one is in progress. If you want to materialise the results of a SQL command as entities, call IUnitOfWork.FindBySql. In this case, the results of the command must include an Id column. Mindscape LightSpeed User Guide 319 Windows Azure Table Service The Windows Azure Table Storage Service is supported in LightSpeed 5, with the Azure Table Service data provider. To get this working, you must reference the provider DLL explicitly, as it isn’t baked into the LightSpeed DLL like most providers. The connection string format is: “Account Name=acct_name;Key=kev_base64_string;Use HTTPS=false”; You must also use the Guid identity method. There is no current drag-and-drop designer support from the Server Explorer. You can still create your models in the designer, however you need to use the toolbox instead of the aforementioned explorer. After you have created the tables, the first additions to the table service will create any schema needed. Mindscape LightSpeed User Guide 320 Database Migrations Migrations are a solution to the problem of managing database versions. A migration captures the set of actions required to update a database from one version to the next—which might include schema changes such as widening or adding columns and/or data changes such as computing defaults for new columns—and to roll that same database back to its original state. Capturing migrations as artifacts makes it easier and more reliable to apply the changes to multiple physical databases (development, test, staging and production) with high confidence that the same set of changes is being applied each time—because it worked in staging, it will work in production. Being able to roll migrations backwards as well as forwards is important for acceptance testing and staging environments. For example, if staging fails, you will want to revert it to its previous status. But you may not want to rebuild the entire environment from scratch. Running migrations in reverse enables this. LightSpeed’s migrations framework provides you with: A way to specify migrations in code using a database-agnostic API and the full flexibility of the .NET Framework. A tool that creates migrations for you as you make changes to your LightSpeed model. Command-line and GUI tools for running migrations, or for generating SQL scripts that can be run later by a DBA. Mindscape LightSpeed User Guide 321 Creating Migrations Writing Migration Code A migration is implemented as a class which derives from Migration. The actions to take are specified in the class’ overrides of the Migration.Up and Migration.Down methods. The Migration class provides a number of methods that you can call to specify the actions to take in the migration. This section lists a few of the key methods; see the Migration class documentation for specific overloads, more details and other methods. Method Action Typical SQL Equivalent AddTable Adds a new table to the database CREATE TABLE DropTable Drops a table from the database DROP TABLE RenameTable Changes the name of an existing table (varies) AddColumn Adds a column to a table ALTER TABLE ADD AddForeignKeyColumn Adds a column with a foreign key constraint to a table ALTER TABLE ADD DropColumn Drops a column from a table ALTER TABLE DROP ChangeColumn Change the data type or nullability ALTER TABLE ALTER of a column RenameColumn Changes the name of a column (varies) ExecuteNativeCommand Executes arbitrary SQL. You can use this to make changes that are not supported through the migrations API, but it may make your migration code non-portable across database providers. (as specified) (SQL equivalents are approximate; the exact SQL syntax varies from database to database.) Mindscape LightSpeed User Guide 322 Creating Migrations from a Model The LightSpeed designer can create migrations for you. Migrations are created as source code which you can edit to reflect additional actions, conditional logic and overridden behaviour. Your model will give rise to a sequence of migrations as it evolves over time. Migrations are collected together in a migrations project separate from your application and model code. Because migrations are created as C# or Visual Basic source code, they are stored in a Visual Studio project. You need to specify on your model which project it should store its migrations in. To do this: Open your model, select the Migrations menu and choose Set Migrations Project. LightSpeed lists the existing projects in your solution. Choose a project and click OK, or click the New Project button. If you create a new project, LightSpeed prompts for the project file. Create a new folder and enter the desired project file name. Don’t use the same migrations project for multiple models. To create a migration in the migration project, select the Migrations menu and choose Create Migration. LightSpeed displays the actions required in the Up method to migrate the database from the last migration to the current model design, and the actions required in the Down method to reverse the process. You can de-select any actions that you don’t want generated for you. Enter a migration name and optionally a description. The migration name will be the name of the generated class and must be a valid class name in the migration project language. Click OK. LightSpeed generates and displays the migration class. You can now edit this class if required. LightSpeed stores snapshots of your model in the migrations project, and uses these to work out what has changed. Do not move, delete or rename these snapshots! Mindscape LightSpeed User Guide 323 Create Migrations from Scratch Introduced in LightSpeed 5, empty migrations can be created for use with custom migration code. Data Types You will normally specify data types using the ModelDataType class. This abstracts your code from the database representation. For example, you would write ModelDataType.String rather than VARCHAR, NVARCHAR or VARCHAR2. When you require fine control over data types, you can instead specify a literal string. For example, if you want a string to be represented as a fixed-size field, you could specify “CHAR” instead of ModelDataType.String. Such data types are database specific. Identity Generation The Migration class also provides APIs to create the database resources used by the LightSpeed identity generation methods. See the AddKeyTable API. Initial Value When Adding a Table Introduced in LightSpeed 5, the AddTable method now includes an overload to create a key table with an initial value. This will be used by default when generating migrations from the designer. Migrating Entity Framework EDMX Model Files LightSpeed now includes support for migrating EDMX V2 files, from Entity Framework, to the LightSpeed Model format. This allows you to import domain models created in the Entity Designer into LightSpeed. Injecting Custom User Code In LightSpeed 5, Before and After hooks after available, allowing custom user code to be injected into the workflow when deriving. Mindscape LightSpeed User Guide 324 Running Migrations Running Migrations from Visual Studio To run migrations from Visual Studio: Open your model. From the Migrations menu, choose Run Migrations. The Migrations window is displayed. The Migrations window shows the available migrations and the current database version. (If you see a “migrations project does not build” message, open the Errors window and fix the problems, then click Refresh to try again.) Check that the Method is set to Apply to connected database and that the database and connection string are correct. If not, choose Change to change your settings. (See below for more info. You will always need to do this the first time you run a given migration project.) Select the target version that you’d like to migrate the database to. Select Apply Now. LightSpeed will figure out whether to run the Up or Down migrations depending on the current database version. Before you first run a migration from Visual Studio, you need to choose the target database. To do this: Choose the Change button next to the database connection information. LightSpeed displays the Migration Settings wizard. Choose Apply migrations directly to a database and click Next. Either select a Server Explorer connection, or choose a database type and enter a connection string. Click Finish. If you subsequently change the migration target, to run the migrations against a different database, choose the Change button again and re-run the wizard. Mindscape LightSpeed User Guide 325 Running Migrations from the Command Line To run migrations from the command line, run the lsmigrate.exe program. You can find this in the installation folder, under the Migrations subfolder. lsmigrate compiles migration source code files from the current directory. (You do not need to have a migrations project and you do not need to build the migrations yourself.) The syntax for lsmigrate is: lsmigrate provider_name connection_string [to_version] provider_name: The type of database. See the Appendices for permitted values. connection_string: The database connection string. to_version: The version to which to migrate the database. If omitted, the latest version will be selected. LightSpeed will figure out whether to run the Up or Down migrations depending on the current database version. Running Migrations from Your Application To run migrations from your application, call the Migrator.Migrate method. You will normally call the overload that takes an IMigrationLoader, passing an AssemblyMigrationLoader for the DLL containing your mgirations. Running migrations from code var source = new AssemblyMigrationLoader(typeof(MyMigration).Assembly); Migrator.Migrate(source, ProviderType.MySql5, connectionString, null); If you want to monitor migration progress, for example to provide an interactive display or to log SQL for diagnostic purposes, use Migrator.CreateMigrator to create a Migrator instance, then call Migrator.Execute to run the migrations. You can set the MigrationLogger property to log migrations, and handle the MigrationExecuting and MigrationExecuted events to provide interactivity. Mindscape LightSpeed User Guide 326 Note that the migrations assembly has several dependencies which are not part of the normal LightSpeed redistributable. Mindscape LightSpeed User Guide 327 Creating SQL Scripts from Migrations To create SQL scripts from Visual Studio: Open your model. From the Migrations menu, choose Run Migrations. The Migrations window is displayed. Check that the Method is set to Generate SQL script and that the database is correct. If you want LightSpeed to work out the starting version for the script by connecting to the database, check that the connection string is correct; if you want to specify the starting version for the SQL script yourself, check that the connection string shows Not connected. If you need to change any of these settings, choose the Change button. (See below for more info.) The Migrations window shows the available migrations and the current database version. (If you see a “migrations project does not build” message, open the Errors window and fix the problems, then click Refresh to try again.) If you’re connected to a database, select the target version which you’d like to generate a SQL script for migrating to. If you’re not connected to a database, select the start and end versions for which you’d like to generate a SQL script for migrating between. Select Generate SQL. LightSpeed generates the SQL script and displays it in Visual Studio. You can save this SQL script to the folder of your choice. Choosing the Generation Settings Before you first generate a SQL script, you need to choose the target database type; optionally, you can also choose a database instance from which LightSpeed will work out the current version. To do this: Choose the Change button next to the database connection information. LightSpeed displays the Migration Settings wizard. Choose Generate SQL that I can later apply to a database and click Next. If you’d like LightSpeed to work out the current version automatically each time you generate a script, choose I’m connected to the database from this Mindscape LightSpeed User Guide 328 computer. Otherwise, choose I’m not connected to the database from this computer. Click Next. If you chose connected operation, either select a Server Explorer connection, or choose a database type and enter a connection string. If you chose disconnected operation, choose a database type. Click Finish. If you subsequently need to change the migration settings, choose the Change button again and re-run the wizard. Mindscape LightSpeed User Guide 329 Database Support Migrations are not supported on Amazon SimpleDB. Some databases do not support all migration actions. For example, SQLite does not support adding a foreign key column to an existing table. In addition, some features are not currently implemented on DB2, SQL Server Compact Edition and VistaDB. Mindscape LightSpeed User Guide 330 Appendices Mindscape LightSpeed User Guide 331 Configuration Reference The following configuration settings are available through the LightSpeedContext class and the configuration file. Property (Configuration Attribute) Description More Information AuditInfoMode (auditInfo) How to generate user names for CreatedBy, Implementing UpdatedBy, etc. Storage Policies in LightSpeed AutoTimestampMode (autoTimestamps) How to generate timestamps for CreatedOn, UpdatedOn, etc. Implementing Storage Policies in LightSpeed Cache (cacheClass) Cache implementation for cached entities Performance and Tuning CascadeDeletes (cascadeDeletes) Whether delete operations cascade by default Basic Operations CommandTimeout (commandTimeout) How long LightSpeed should allow commands to run before failing them. (Config entry is in seconds.) ConnectionString (connectionStringName) The database connection string. (Config entry refers to the <connectionStrings> section.) Basic Operations CustomAuditInfoStrategy (auditInfo) IAuditInfoStrategy for user names for CreatedBy, UpdatedBy, etc. Implementing Storage Policies in LightSpeed CustomAutoTimestampStrategy IAutoTimestampStrategy for timestamps (autoTimestamps) for CreatedOn, UpdatedOn, etc. Implementing Storage Policies in LightSpeed DataProvider (dataProvider) The type of database. Basic Operations DetectPropertyNames (detectPropertyNames) Enables the Entity.Set overload which does not take a property name. True by default. Mindscape LightSpeed User Guide 332 Property (Configuration Attribute) Description More Information DisplayNamingStrategy (displayNamingStrategyClass) IDisplayNamingStrategy for localising property names for display in validation messages. Building Applications with LightSpeed EntityFactory IEntityFactory to be used when materialising entities. IdentityBlockSize (identityBlockSize) When using the KeyTable identity method, the number of Ids to reserve per database query. When using the Sequence or MultiSequence method, the increment amount of the sequence. Controlling the Database Mapping IdentityMethod (identityMethod) How LightSpeed assigns Ids to entities. Controlling the Database Mapping IdentityMethodOptions Additional configuration for the IdentityMethod Controlling the Database Mapping Logger (loggerClass) ILogger for SQL and debug logging Testing and Debugging NamingStrategy (namingStrategyClass) INamingStrategy for database mapping. Controlling the Database Mapping PluralizeTableNames (pluralizeTableNames) Whether table names in the database use the plural or singular form of the entity class name (e.g. Person table or People table). Controlling the Database Mapping QuoteIdentifiers (quoteIdentifiers) Whether to quote identifiers (e.g. table names) in generated SQL. Avoids conflicts with SQL reserved words, but can cause case sensitivity issues on some databases. Basic Operations Schema (schema) The default database schema (can be overridden on a per-entity basis). SearchEngine (searchEngineClass) The type of full-text search engine, if any. Mindscape LightSpeed User Guide Advanced Querying Techniques 333 Property (Configuration Attribute) Description More Information SearchEngineFileLocation (searchEngineFileLocation) Location of full text search index file(s). Advanced Querying Techniques UpdateBatchSize (updateBatchSize) Maximum number of update statements per command batch. Performance and Tuning UseMediumTrustCompatibility Runs LightSpeed in a mode that is compatible with ASP.NET medium trust, at the expense of some performance. Building Web Applications with LightSpeed VerboseLogging (verboseLogging) Shows additional detail in log displays. Testing and Debugging Mindscape LightSpeed User Guide 334 Tips, Tricks and Troubleshooting Use a Short-Running Unit of Work Long-running units of work introduce issues of concurrency and stale data. A particularly common pattern in Web applications is “unit of work per (HTTP) request” – never keep a unit of work across requests (this isn’t just a matter of concurrency or stale data, but storing a unit of work across requests is unreliable: for example a unit of work can’t be serialised to session state). See the chapters Building Web Applications with LightSpeed and Building WPF and Windows Forms Applications with LightSpeed for more discussion. Use a Single LightSpeedContext The LightSpeedContext contains essentially static configuration information. There’s usually no need to have more than one unless you’re talking to multiple databases. Furthermore, because ID allocation in sequence or key table configurations is handled at a context level, using multiple contexts can lead to increased database load and ID fragmentation. See Building Applications with LightSpeed for more discussion. Use Configuration Files Where Possible Prefer configuration files to code-based configuration. You can set up the LightSpeed context in code by setting properties, or in configuration via the web.config or app.exe.config file. Using a configuration file makes it easier for operations staff or other users to configure the application to different environments. Partial Classes Use partial classes to add behaviour to generated entity classes. You can also add properties in partial classes, but if those properties introduce additional state (as opposed to being wrapper or adapter properties around the existing LightSpeed fields), be sure to mark the backing fields with the TransientAttribute so LightSpeed doesn’t try to persist them. Mindscape LightSpeed User Guide 335 Never use automatic properties in a LightSpeed entity class – the C# compiler generates a backing field which doesn’t map to a database column, and you can’t get at the field to mark it transient. Use Eager Loading and Named Aggregates to Tune Loading Performance Eager loading and named aggregates allow you to tune loading performance for difference scenarios. Eager loading means that LightSpeed queries the database for an entity and its associated entities in a single database round-trip. This can massively improve performance, avoiding the so-called “N+1” problem. If you need finer control, to be able to choose whether to eager-load an association or not on a per-query basis, you can use a named aggregate: this allows you to say “I want to load this Customer with all their Orders” or “I want to load just the Customer” depending on the task at hand. You can also apply this to fields, for example eager-loading a large binary only if the “with high-resolution picture” aggregate is specified in the query. Avoid Needless Change Tracking Don’t turn on change tracking unless you need it. Change tracking has a memory cost, which can grow significant if you are making many changes to lots of entities. Don’t incur that cost unless you have a reason for doing so. Measure the Performance Impact of Changing UpdateBatchSize Tweaking UpdateBatchSize can improve save performance – or worsen it. When LightSpeed saves a unit of work, it sends the SQL statements to the database in batches. The number of statements per batch is controlled by LightSpeedConfiguration.UpdateBatchSize. The default value is 10. Increasing this figure reduces the number of round-trips to the database, which can improve performance. However, it also means that LightSpeed has to build, and the database has to parse, much larger blocks of SQL – which can worsen performance. (Also, some databases limit the number of parameters in a single SQL batch, so very large batch sizes may cause database errors.) Don’t increase UpdateBatchSize too far, and always measure the performance impact rather than just assuming that bigger is better! Mindscape LightSpeed User Guide 336 Consider Changing IdentityBlockSize Increasing IdentityBlockSize can improve performance – at a cost. When using a key table identity method, LightSpeed has to query the database to get the next “block” of IDs to allocate to entities. LightSpeedContext.IdentityBlockSize determines how many IDs it blocks out on each query. Increasing the IdentityBlockSize from its default of 10 therefore means LightSpeed has to query the key table less often, improving performance. For example, if you’re doing a bulk insert of tens of thousands of items, then increasing IdentityBlockSize from 10 to 1000 saves thousands of allocation calls and can make the application run measurably faster. But a large block size can result in a lot of unused IDs where the block has not been exhausted, so don’t increase IdentityBlockSize beyond the point of diminishing returns. As of LightSpeed 5, the identity block case can now be set on a per-table basis. Keep Sequences in Sync with IdentityBlockSize When using sequence identity methods, IdentityBlockSize must equal the sequence increment amount. In a sequence identity method, the size of the ID block is determined by the database, not by LightSpeed. In this case, IdentityBlockSize must be the block size specified in the sequence definition, i.e. the INCREMENT BY amount. An incorrect IdentityBlockSize can lead to duplicate IDs being allocated, which will result in database constraint violations when you come to save. ObjectDisposedException in SystemTransactionCompletedEvent Some customers have reported experiencing ObjectDisposedException when disposing a system TransactionScope than encapsulated two nested units of work. This appears to be a bug in the .NET Framework running on Windows XP. The solution is to use a single unit of work, or to dispose the first unit of work before creating the second unit of work. Mindscape LightSpeed User Guide 337 Command Line Tools Reference LightSpeed includes the following command line tools. lsgen – Create Entity Classes from Database The lsgen tool creates entity classes from a database. It can also be used to create a .lsmodel file which can then be loaded into the designer for further development. Syntax: lsgen /p:provider /c:connection_string /l:language /n:namespace /o:output_dest provider: The type of database. See below. connection_string: The database connection string. language: The language to generate the entity classes in. namespace: The namespace into which to generate the classes. output_dest: Where to save the generated files. When generating C# or Visual Basic files, this must be the name of a folder. The folder is created if it does not already exist. Existing files at this location are overwritten. lsgen also supports the following optional switches: /linq: Generate a strong-typed LINQ unit of work class. /contracts: Generate WCF data contracts. /m:model_name : Specifies the model name for LINQ or WCF class names /include:tables : Include only the specified tables in the generated model. /exclude:tables : Exclude the specified tables from the generated model. /stp:prefix : The prefix to be stripped from table names if present (e.g. tbl). /src:prefix : The prefix to be stripped from column names if present (e.g. col). /template:template_file : Use a custom code generation template. The tables argument to the /include and /exclude switches is either a commaseparated list of table names, or the @ character followed by the name of a file containing the list of tables, one per line. E.g. /include:Cars,Engines or /exclude:@CarTables.txt. Associations are generated regardless of whether the Mindscape LightSpeed User Guide 338 association target type is generated, so care is required to avoid “dangling” associations. Individual columns can be excluded using table.column notation, e.g. MyTable.UnwantedColumn, or *.column to exclude columns of that name no matter which table they are in. To create a designer model using lsgen, specify /l:lsmodel. In this case, the output destination (/o) must be a file, not a folder. lsgen does not generate any layout for the generated model: it is up to you to make the diagram look good by loading it into Visual Studio and dragging the entities into a suitable layout. You can also generate C# or Visual Basic files from a LightSpeed designer model using lsgen. This is not normally necessary because Visual Studio automatically generates the appropriate code, but it may be useful in certain automation scenarios. To do this, specify /p:lsmodel, and pass the model (.lsmodel) filename as the /c (connection string) option. lsmigrate – Apply Migrations The lsmigrate tool applies migrations, supplied as source code, to a database. Syntax: lsmigrate provider connection_string [to_version] provider: The type of database. See below. connection_string: The database connection string. to_version: The version to which to migrate the database. If omitted, the latest version will be selected. Database Providers in Command Line Tools The ‘provider’ argument for command line tools can take the following values: Value Database Abbreviation MySql5 MySQL mysql Oracle9 Oracle oracle PostgreSql8 PostgreSQL pg Mindscape LightSpeed User Guide 339 Value Database Abbreviation Sqlite3 SQLite sqlite SqlServer2000 Microsoft SQL Server 2000 sql2000 SqlServer2005 Microsoft SQL Server 2005 sql2005 SqlServer2008 Microsoft SQL Server 2008 sql2008 SqlServer2012 Microsoft SQL Server 2012 sql2012 SqlServerCE Microsoft SQL Server Compact 3.5 sqlce SqlServerCE4 Microsoft SQL Server Compact 4 sqlce4 VistaDB4 VistaDB vdb4 Languages in Command Line Tools The ‘language’ argument for command line tools can take the following values: Value Language Abbreviation CSharp C# cs VisualBasic Visual Basic vb Mindscape LightSpeed User Guide 340 LINQ Support Limitations The LINQ API was designed by Microsoft to represent a wide range of set-oriented operations, not all of which are meaningful or practical in the context of a relational database or the SQL query language. Some LINQ queries that are syntactically valid are therefore not supported in LightSpeed, and there are some limitations on the terms that can be used in a LINQ query to LightSpeed. This section summarises these limitations. Unsupported LINQ Operators The following operations are not supported by the LightSpeed LINQ Query Provider, and no support is planned. SequenceEqual: Compares two sequences for equality SkipWhile: Skips elements while a condition remains true TakeWhile: Takes elements while a condition remains true If you need to use one of these operators, you must materialise the query results using AsEnumerable or ToList. You can then process it using LINQ to Objects, which does support the operators mentioned above. For example: Using LINQ to Objects operators with a LightSpeed query // Runtime error: can't translate TakeWhile to SQL var millionaires = unitOfWork.Employees .OrderByDescending(e => e.Salary) .TakeWhile(e => e.Salary >= 1000000); // Success: translates first two lines to SQL, then runs TakeWhile on the results var millionaires = unitOfWork.Employees .OrderByDescending(e => e.Salary) .AsEnumerable() // subsequent operators run client-side .TakeWhile(e => e.Salary >= 1000000); Mindscape LightSpeed User Guide 341 Comparisons to an Associated Entity In a LINQ query, you can compare values from one entity with values from an associated entity. However, you must write the query so that the associated entity appears on the left of the comparison operator. Comparing attributes of associated entities in a LINQ query // Runtime error: association must appear on left where order.Price > order.Customer.CreditLimit // Corrected version where order.Customer.CreditLimit <= order.Price CLR Methods in a LINQ Query The translation of CLR methods to SQL is dependent on the database provider. As a guideline: String members which correspond to the SQL LIKE operator such as Contains and StartsWith are generally supported on all databases. (Note, however, that the comparand must be a string known at query time; it cannot be another property of the range variable. For example, you can write where m.Name.Contains(filterBox.Text), but not where m.Name.Contains(m.Email). This is a restriction of the SQL LIKE operator.) Queries involving string concatenation may not be correctly handled on nonSQL Server databases. The extended patterns of the Visual Basic Like operator are supported only on SQL Server. Math.Min and Math.Max are supported on all databases. Other Math members such as Abs, Ceiling and Log are generally supported on all databases except SQLite. DateTime members relating to date or time parts (Year, Month, Day, Hour, Minute, Second) are generally supported on all databases. The DateTime.Date property is supported only on SQL Server 2008, Oracle and PostgreSQL. The DateTime.Time property is not supported on any database. Mindscape LightSpeed User Guide 342 Joining, Grouping and Combining Some complex joining and grouping scenarios are not supported. Support for some scenarios will be added in future builds; please contact Mindscape if there is a particular pattern you would like us to support. Unsupported scenarios include: Binary expressions used as grouping keys Group joins over more than two tables Queries that self-join in their criteria Combining a grouping with joins Some grouping with ordering or paging queries Set operations There is only partial support for the Concat, Intersect, Union and Except operators. These can be used to combine entity sets of the same type, but support for other scenarios (such as projections from multiple tables) is very limited. Many databases do not support the Intersect operator at all. Database Specific Limitations Some databases lack support for certain functions or operations that are supported elsewhere. See Working with Database Providers for database-specific information. Mindscape LightSpeed User Guide 343 Further Reading The following resources are not specific to LightSpeed, but provide background and more detail on the patterns and techniques that influenced the design of LightSpeed. Eric Evans, Domain Driven Design Martin Fowler, Patterns of Enterprise Application Architecture Mindscape LightSpeed User Guide 344 Index Add method, 60 adding entities, 60 ADO.NET, 307 transaction, 64 aliasing, 214 application, 88 creating and upgrading the database, 314 ASP.NET, 99 Dynamic Data, 113 medium trust, 112 ASP.NET MVC, 106 sample, 111 association and database first development, 172 creating in code, 30 creating in the designer, 23 custom resolver, 284 eager loading, 232 Get method, 31 mapping to foreign key, 68 metadata for, 226 one-way, 167 properties, 24 reverse, 30 Set method, 31 attributes and fields, 30 auditing, 259 auto through entity, 25 automatic properties, 28 shunned, 324 batch size, 243 batching, 243 performance, 324 BeginTransaction method, 64 binding. See data binding block allocation of Ids, 80 bulk operations, 240 cache, 245 bulk operations and, 242 manual access, 249 cascade delete, 62 change tracking, 271 class table inheritance, 158 ClickOnce, 122 Mindscape LightSpeed User Guide code first development, 28 code generation, 184 T4 templates, 188 templates, 184 using designer extensions, 186 column loading computed, 74 mapping, 70 mapping to association, 68 mapping to field, 67 mapping to Id, 67 value object mapping, 164 Column Name option, 70 ColumnAttribute, 71 command line tools, 326 composite key, 280 many-to-many association, 284 concrete table inheritance, 159 concurrency, 268 configuration, 45, 46, 89, 320 file, 46, 89 lightSpeedContexts section, 46 connection string, 48 SimpleDB, 299 ConnectionStrategy class, 95 controller, 106 convention over configuration, 66 Convert to Manual Implementation command, 197 creation time, 259 timestamp, 263 cross join, 216 cross-cutting keys, 284 CRUD, 43 stored procedures for CRUD operations, 274 custom association resolver, 284 custom attributes in the designer, 194 custom mapping data type, 175 custom property setter, 197 data binding ASP.NET MVC, 107 interfaces, 121 live data feed, 119 345 Web Forms, 103 data contract, 135 formatter for EntityCollections, 136 data format, 286 data provider, 47 data transfer object, 138 LightSpeed 3 compatibility, 141 sample, 142 data type conversion, 287 converting in property code, 286 custom mapping, 175 database-specific, 174 designer, 174 spatial, 303 database, 47, 293 converting data, 286 creating in the designer, 18 hints, 250 index, 250 legacy, 272 mapping, 66 migrations, 309 querying, 50 saving changes, 60, 62 synchronising to model, 170 view, 82 database connection and unit of work, 95 custom strategy, 95 database first development, 21, 170 and inheritance, 172 one-to-one association, 172 reorganising domain model, 172 value object, 172 database synchronisation configuring, 173 database-defined type, 174 DB2, 294 debugging logging, 149 delete bulk delete, 240 deleting entities, 62 cascade, 62 deployment ClickOnce, 122 designer, 13, 168 code generation, 184 creating migrations, 311 Mindscape LightSpeed User Guide defaults for creating entities, 183 enum, 174 extensibility, 186 filtering the view, 178 keyboard shortcut, 196 Model Explorer window, 169 PostgreSQL, 298 referencing an external class, 196 tips, 194 toolbox, 14 user-defined type, 174 detecting concurrent edits, 268 discriminator, 156 distinct query, 212 distributed applications, 128 client configuration, 133 samples, 136 service hosting, 130 DistributedUnitOfWork, 129 documentation in the designer, 27 Documentation command, 27 domain model, 10 capturing an image of, 195 creating from database, 21, 36 creating in code, 28 creating with the designer, 13 linked files, 181 synchronising to database, 170 viewing large models, 178 domain service, 124 DTO. See data transfer object Dynamic Data, 113 eager loading, 232 viewing load graph in designer, 179 entity, 44 adding new, 60 bulk update or delete, 240 classes, 18 composite key, 280 creating from database, 36 creating in code, 28 creating in the designer, 14 defaults for new entity types, 183 deleting, 62 distributed, 129 field loading, 236 Get method, 31 IsValid property, 37 loading strategy, 231 346 loading through stored procedure, 87 mapping to table, 67 mapping to view, 83 metadata for, 225 natural key, 277 OnValidate method, 41 renaming, 177, 197 saving creation and update details, 259 Set method, 29, 31 soft delete, 261 updating, 61 Validate method, 37 Entity.Attribute method, 56 EntityCollection, 30 WCF serialisation, 136 EntityDataBinder, 103 EntityHolder, 30 enum, 174 extension properties, 186 external class reference, 196 fake objects, 146 features, 7 field excluding from save, 74 lazy loading, 236 load only, 74 mapping to column, 67 metadata for, 226 Set method, 29 transient, 74 vs. property, 28, 68 field converter, 175, 287 queries and, 291 Find method, 56 flush changes to database, 60, 62 foreign key composite, 282 composite overlapping primary key, 284 mapping, 70 mapping to association, 68 naming convention, 68 part of primary key, 284 full text search, 200 bulk operations and, 242 function invoking SQL functions, 204 GeneratedId method, 277 Get method, 31 Get Started command, 195 getter Mindscape LightSpeed User Guide custom code in property, 197 custom property getter, 286 grouping using query objects, 217 GUID identity generation, 79 performance tradeoffs, 79 GuidComb, 79 hint, 250 IAssociationResolver interface, 284 Id, 76 assigning, 76 composite key, 280 identity type, 15 mapping to column, 67 natural key, 277 IDataErrorInfo, 121 IdColumnName, 71 identity column, 80 batching and, 244 Identity Column Name option, 70 identity generation, 76 block allocation, 80 identity map, 245 identity method, 76 per entity, 77 setting, 76 IdentityBlockSize, 325 for key table, 78 for sequences, 78 sequences and, 325 IdentityMethod enumeration, 76 IDisplayNamingStrategy interface, 93 IEditableObject, 121 IFieldConverter interface, 175, 287 ILogger interface, 150 import data transfer object, 140 INamingStrategy, 71 index hint, 250 inheritance, 156 choosing a mapping, 159 class table, 158 concrete table, 159 discriminator, 156 external base type, 196 hiding inheritance arrows, 196 moving properties between classes, 172 single table, 157 inner join, 215 InnoDB, 295 INotifyPropertyChanged, 121 347 inserting entities, 60 intersection, 220 IsValid property, 37 iterative development, 170 IUnitOfWork interface, 44, 56 IUnitOfWork.Add method, 60 IUnitOfWork.Project method, 211 IUnitOfWork.Remove method, 62 IUnitOfWork.SaveChanges method, 60, 62 join using query objects, 214 keyboard shortcut, 196 KeyTable identity generation, 77 block allocation, 80 naming convention, 68 keyword escaping, 73 large models, 178 large objects, 236 lazy loading, 236 legacy database, 272 LightSpeed, 4 LightSpeed Model Explorer, 169 LightSpeedContext, 45, 46, 90, 323 scope, 90 lightSpeedContexts section, 89 LightSpeedEntityModelBinder, 107 linked model, 181 LINQ, 50 invoking custom functions, 204 supported features, 329 loading controlling with named aggregates, 233 default, 231 eager, 232 localisation, 42, 93 logging, 149 custom logger, 150 logical delete. See soft delete long running unit of work, 119 lsgen, 36, 326 lsmigrate, 327 Lucene, 200 many to many association, 25 composite key, 284 creating in code, 33 designer, 191 hiding through entity, 172 mapping, 66 column, 70 Mindscape LightSpeed User Guide default, 67 non-default, 70 table, 70 value object, 164 medium trust, 112 metadata, 223 and field values, 228 Microsoft SQL Server. See SQL Server Migration class, 310 migrations, 309 creating SQL scripts, 316 running, 313 mock objects, 147 Model Explorer window, 169 model first development, 18, 170 MyISAM, 295 MySQL, 295 round-tripping policy, 173 N+1 problem, 232 named aggregate, 233, 239 filtering designer view by, 179 lazy loading fields using, 236 visualising, 239 naming convention column, 67 defining your own, 71 foreign key, 68 key table, 68 Oracle stored procedure results, 296 primary key, 67 table, 67 natural key, 277 NoReverseAssociationAttribute, 167 ObjectDisposedException, 325 ODP.NET could not load file or assembly, 296 one to many association, 23 one to one association, 25 and database first development, 172 OnValidate method, 41 optimistic concurrency, 268 OptimisticConcurrencyException responding to, 268 Oracle, 296 outer join, 216 paging using LINQ, 53 using query objects, 57 partial class creating, 177 348 performance, 230 measuring, 252 PerRequestUnitOfWorkScope, 100 persistence, 74 load-only fields, 74 transient fields, 74 pluralization, 67 polymorphism, 159 PostgreSQL, 298 primary key composite key, 280 mapping, 70 naming convention, 67 natural key, 277 profiling, 152 Project method, 211 projections using query objects, 211 property creating in the designer, 16 custom getter and setter, 197, 286 localising property name, 93 metadata, 226 renaming, 177, 197 Set method, 29 vs. field, 28, 68 query expression, 56 query object, 56, 211 subexpressions, 221 vs. LINQ, 59 QueryExpression class, 56 querying, 50, 199 query expression, 56 using query objects, 56 quoting identifiers, 73 rapid application development, 170 refactoring, 177 reference data, 167 release notes, 6 reminder note, 195 Removemethod, 62 removing entities, 62 Rename command, 197 renaming entities and fields, 177 reserved word, 73 reverse association, 30 RIA Services, 123 rich client, 116 sample ASP.NET MVC, 111 Mindscape LightSpeed User Guide data transfer objects, 142 distributed applications, 136 Web Forms, 105 SaveChanges method, 60, 62 saving changes, 60, 62 transactional, 64 schema, 92 migrating database schema, 309 scoping unit of work ASP.NET MVC, 106 Web Forms, 100 WPF and Windows Forms, 117 search full text, 200 second level cache, 245 sequence identity generation, 78 block allocation, 80 options, 80 sequence per table, 79 Set method, 29, 31 setter custom code in property, 197 custom property setter, 286 Silverlight, 123 SimpleDB, 299 single table inheritance, 157 soft delete, 261 sorting using LINQ, 53 using query objects, 57 spatial data SQL Server 2008, 303 SQL creating migration scripts, 316 invoking SQL functions, 204 logging, 149 timing, 152 SQL keyword escaping, 73 SQL Server, 303 SQL Server 2000, 304 SQL Server Compact, 305 SQLite, 302 stored procedure, 85 CRUD procedures, 274 Oracle, 296 subexpression using query objects, 221 surrogate key, 277 T4 templates, 188 349 table dragging into model, 21 mapping, 70 mapping to entity, 67 table hint, 251 Table Name option, 70 table per concrete class, 159 table per hierarchy, 157 table per subclass, 158 TableAttribute, 71 tag filtering designer view by, 178 testing, 144 through association, 25 auto through entity, 25 creating in code, 33 hiding through entity, 172 timestamp, 263 tips and tricks, 323 tracing, 149 track create time, 259 track update time, 259 tracking changes, 271 transaction, 64 ADO.NET, 64 and unit testing, 144 TransactionScope, 64 union, 220 unit of work, 44 adding entities, 60 bulk operations and, 242 class, 50 database connection, 95 deleting entities, 62 distributed, 129 long running, 119 saving changes, 60, 62 scoping in ASP.NET MVC, 106 scoping in Web Forms, 100 scoping in WPF and Windows Forms, 117 short running vs. long running, 323 unit testing, 144 update bulk update, 240 update batching, 243 Update Database command, 18, 170 Update From Source command, 22, 170 update time, 259 timestamp, 263 UpdateBatchSize, 243, 324 Mindscape LightSpeed User Guide updating entities, 61 user identifying, 266 track creating or updating, 259 track deleting user, 261 user-defined type designer, 174 Validate method, 37 validation, 37 advanced designer options, 38 ASP.NET Dynamic Data, 114 ASP.NET MVC, 109 customising automatic, 40 declarative in code, 40 designer, 37 localising messages, 42, 93 procedural, 41 Web Forms, 101 whole object, 41 validation attributes and fields, 30 value object, 161 and database first development, 162, 172 editing values, 163 mapping, 164 view database, 82, 213 filtering designer view, 178 mapping to entity, 83 querying through, 82 saving designer filter, 179 VistaDB, 306 Visual Basic, 35 visual designer. See designer Visual Studio designer, 168 mapping keyboard to designer, 196 migrations project, 311 running migrations from, 313 WCF, 128 custom entity service, 136 RIA Services, 123 samples, 136 service hosting, 130 Web, 99 Web Forms, 100 sample, 105 Web service hosting in WCF, 130 loose coupling, 138 350 Web services, 128 Windows Forms, 116 WithAggregate operator, 234, 239 Mindscape LightSpeed User Guide WPF, 116 XML documentation in the designer, 27 351