From 6ad46deeeda742f9e2fc53ad62cc249e2482dfa2 Mon Sep 17 00:00:00 2001 From: Jonathon Rossi Date: Thu, 4 Jun 2015 19:37:27 +1000 Subject: [PATCH] Import docs from the wiki --- docs/README.md | 60 +++ docs/accessing-more-than-one-database.md | 27 + docs/best-practices.md | 31 ++ docs/bytecode-generator.md | 116 +++++ docs/configuration-and-initialization.md | 153 ++++++ docs/creating-an-activerecord-class.md | 92 ++++ docs/enabling-lazy-load.md | 71 +++ docs/external-articles.md | 24 + docs/faq.md | 114 +++++ docs/framework-events.md | 11 + docs/getting-started.md | 465 +++++++++++++++++ docs/hooks-and-lifecycle.md | 5 + docs/images/castle-logo.png | Bin 0 -> 3801 bytes docs/markdownlint.rb | 6 + docs/native-sql-queries.md | 99 ++++ docs/nested-data.md | 129 +++++ docs/persistency-lifecycle.md | 159 ++++++ docs/primary-key-mapping.md | 345 +++++++++++++ docs/relations-mapping.md | 606 +++++++++++++++++++++++ docs/schema-generation.md | 73 +++ docs/simple-column-mapping.md | 146 ++++++ docs/troubleshooting.md | 75 +++ docs/tuning.md | 101 ++++ docs/type-hierarchy.md | 291 +++++++++++ docs/understanding-scopes.md | 125 +++++ docs/unit-testing.md | 163 ++++++ docs/using-hql.md | 263 ++++++++++ docs/using-imports.md | 33 ++ docs/using-scopes.md | 89 ++++ docs/using-the-activerecordmediator.md | 149 ++++++ docs/using-version-and-timestamp.md | 31 ++ docs/validation-support.md | 51 ++ docs/validators.md | 59 +++ docs/web-applications.md | 124 +++++ docs/xml-configuration-reference.md | 285 +++++++++++ 35 files changed, 4571 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/accessing-more-than-one-database.md create mode 100644 docs/best-practices.md create mode 100644 docs/bytecode-generator.md create mode 100644 docs/configuration-and-initialization.md create mode 100644 docs/creating-an-activerecord-class.md create mode 100644 docs/enabling-lazy-load.md create mode 100644 docs/external-articles.md create mode 100644 docs/faq.md create mode 100644 docs/framework-events.md create mode 100644 docs/getting-started.md create mode 100644 docs/hooks-and-lifecycle.md create mode 100644 docs/images/castle-logo.png create mode 100644 docs/markdownlint.rb create mode 100644 docs/native-sql-queries.md create mode 100644 docs/nested-data.md create mode 100644 docs/persistency-lifecycle.md create mode 100644 docs/primary-key-mapping.md create mode 100644 docs/relations-mapping.md create mode 100644 docs/schema-generation.md create mode 100644 docs/simple-column-mapping.md create mode 100644 docs/troubleshooting.md create mode 100644 docs/tuning.md create mode 100644 docs/type-hierarchy.md create mode 100644 docs/understanding-scopes.md create mode 100644 docs/unit-testing.md create mode 100644 docs/using-hql.md create mode 100644 docs/using-imports.md create mode 100644 docs/using-scopes.md create mode 100644 docs/using-the-activerecordmediator.md create mode 100644 docs/using-version-and-timestamp.md create mode 100644 docs/validation-support.md create mode 100644 docs/validators.md create mode 100644 docs/web-applications.md create mode 100644 docs/xml-configuration-reference.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..8036863 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,60 @@ +# Castle ActiveRecord Documentation + + + +The **Castle ActiveRecord** project is an implementation of the [ActiveRecord pattern](http://en.wikipedia.org/wiki/Active_record) for .NET. The ActiveRecord pattern consists on instance properties representing a record in the database, instance methods acting on that specific record and static methods acting on all records. + +**Castle ActiveRecord** is built on top of [NHibernate](http://www.nhibernate.org/), but its attribute-based mapping frees the developer of writing XML for database-to-object mapping, which is needed when using NHibernate directly. + +:warning: **Warning:** Although ActiveRecord makes using NHibernate easy it does not hide all details of NHibernate behaviour. You **need** to understand NHibernate flushing behaviour and how to work with text/ntext columns. + +## Reference Manual + +* [Understanding Scopes](understanding-scopes.md) - Explains the SessionScope and TransactionScope classes +* [Validators](validators.md) +* [XML Configuration Reference](xml-configuration-reference.md) + +## User's Guide + +* [Getting Started](getting-started.md) - A tutorial for "how", after the introduction explains "what" +* [Configuration and Initialization](configuration-and-initialization.md) - Explains the configuration options and schema, and provides some illustrative examples as well +* [Creating an ActiveRecord class](creating-an-activerecord-class.md) +* [The Persistency Lifecycle](persistency-lifecycle.md) +* [Primary Key Mapping](primary-key-mapping.md) +* [Simple Column Mapping](simple-column-mapping.md) +* [Relations Mapping](relations-mapping.md) + * [BelongsTo](relations-mapping.md#belongsto) + * [HasMany](relations-mapping.md#hasmany) + * [HasAndBelongsToMany](relations-mapping.md#hasandbelongstomany) + * [OneToOne](relations-mapping.md#onetoone) + * [Any and HasManyToAny](relations-mapping.md#any-and-hasmanytoany) +* [Schema Generation](schema-generation.md) +* [Unit Testing](unit-testing.md) +* [Type Hierarchy](type-hierarchy.md) - Discuss approaches to achieve inheritance within your object model and map it correctly to the underlying database +* [Nested Data (NHibernate Components)](nested-data.md) +* [Using HQL (Hibernate Query Language)](using-hql.md) - Illustrates the usage of HQL +* [Native SQL Queries](native-sql-queries.md) +* [Using Scopes](using-scopes.md) +* [Enabling Lazy Load](enabling-lazy-load.md) +* [Validation Support](validation-support.md) - Presents the ActiveRecordValidationBase that is able to validate properties with predefined validators +* [Best Practices](best-practices.md) - Some recommendations +* [Web Applications](web-applications.md) +* [Troubleshooting](troubleshooting.md) - Something went wrong? Check what problems we had and how we work around them +* [Frequently Asked Questions](faq.md) +* [External Articles on ActiveRecord](external-articles.md) + +## Advanced Usage + +* [Using the ActiveRecordMediator (avoiding a base class)](using-the-activerecordmediator.md) +* [Using Version and Timestamp](using-version-and-timestamp.md) +* [Using Imports](using-imports.md) +* [Hooks and Lifecycle](hooks-and-lifecycle.md) +* [Framework Events](framework-events.md) +* [Accessing more than one database](accessing-more-than-one-database.md) +* [Tuning (Performance Improvements)](tuning.md) +* [ByteCode Generator and Scopeless Lazy Loading](bytecode-generator.md) + +## Integrations + +* [MonoRail ActiveRecord Integration](https://github.com/castleproject/MonoRail/blob/master/MR2/docs/activerecord-integration.md) +* [MonoRail ActiveRecord Scaffolding](https://github.com/castleproject/MonoRail/blob/master/MR2/docs/getting-started-with-activerecord-integration.md) \ No newline at end of file diff --git a/docs/accessing-more-than-one-database.md b/docs/accessing-more-than-one-database.md new file mode 100644 index 0000000..18f1994 --- /dev/null +++ b/docs/accessing-more-than-one-database.md @@ -0,0 +1,27 @@ +# Accessing more than one database + +You can use more than one database with ActiveRecord. In order to do so you must create base classes that define, based on the hierarchy, which database is being used. Those are called *Root types*. If you use just one database, the *root type* is `ActiveRecordBase`. + +## Adding a different database + +Let's analyze the steps involved in getting ActiveRecord to work with more than one database. + +### First: Create your root type + +You must create an abstract class. It is recommended that this class extends ActiveRecordBase, because all *ActiveRecord types* bound to the second database must use it as the base class. This class can be empty. + +However, it is not necessary to extend ActiveRecordBase. When ActiveRecord is used without base types (using ActiveRecordMediator), there is no gain from extending ActiveRecordBase, and inheriting from it can be safely omitted. + +```csharp +using Castle.ActiveRecord + +public abstract class LogisticDatabase : ActiveRecordBase +{ +} +``` + +The class must not create a table by its own. It cannot be used as direct base class for single table or class table inheritance as described under [Implementing Type hierarchies](type-hierarchy.md). + +### Second: configure the second database + +On the existing configuration, you must use add another config set bound to the abstract class you have just created. For more information on it, see [XML Configuration Reference](xml-configuration-reference.md). \ No newline at end of file diff --git a/docs/best-practices.md b/docs/best-practices.md new file mode 100644 index 0000000..f5c6f68 --- /dev/null +++ b/docs/best-practices.md @@ -0,0 +1,31 @@ +# Best Practices + +After eating our own dog food for a while, we think we might have some valuable tips for newcomers. + +## Create test cases for your domain model + +Having test cases for your domain model allow you to easily identify what change broke some functionality. This is a general rule for test cases but some people tend to neglect test cases for things that involve databases. + +We think that testing each layer individually is important, including the data access layer. Just make sure there is a separated databases to be used exclusively by the test cases. + +As the application grows big, you might end up with a complex object model with lots of interconnections. So testing become a complex issue, for example, imagine that you want to test an Order class, but to test it you must create a Product, and a Supplier and an Consumer. + +We recommend using [ObjectMother pattern](http://martinfowler.com/bliki/ObjectMother.html) for such cases. + +## Test interactions and relations + +The interactions among objects are especially important. When they are meaningful, create special methods for them that assert some conditions. + +For example, suppose you have a `Pool` and `Vote` classes. The `Pool` might expose a `RegisterVote` method. Have your test asserting the correct behavior for it. + +## Use relations wisely + +There are a number of things ofter overlooked when relations are defined. The Inverse property for example, defines which side of the relation manages the collection. The types used (`List`, `List`) have an implied semantic which often is not considered. + +The best source of information about relations is (and will ever be) the [NHibernate documentation](http://nhforge.org/doc/nh/en/index.html). + +## Define a Cascade behavior for your relations + +The cascade defines how NHibernate should handle children/parent associations when something is changed or deleted. Castle ActiveRecords always defaults to `None` so it is up to you to specify the behavior you want. + +One more time the best source of information about cascades is the [NHibernate documentation](http://nhforge.org/doc/nh/en/index.html). \ No newline at end of file diff --git a/docs/bytecode-generator.md b/docs/bytecode-generator.md new file mode 100644 index 0000000..a4bc524 --- /dev/null +++ b/docs/bytecode-generator.md @@ -0,0 +1,116 @@ +# ByteCode Generator and Scopeless Lazy Loading + +ActiveRecord provides a custom ByteCode generator, based off of the Castle ByteCode packaged with NHibernate. The ActiveRecord ByteCode provides additional session management functionality not found in the standard implementation. + +A ByteCode provides the object proxies used when lazy loading entities from the database. Please see [enabling lazy load](enabling-lazy-load.md) for additional information on how to use ActiveRecords lazy functionality. + +ActiveRecord can lazy load three types of objects: + +* ActiveRecord objects +* Properties +* Relationships + +The ActiveRecord ByteCode will provide session management for lazy ActiveRecord objects. As a result, you do not need to provide a session scope when accessing these objects. + +:warning: **Warning:** Session management of lazy properties and relationships is not currently supported. These must still be accessed from within the original SessionScope. + +For example, if you have the following classes defined: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord(Lazy=true)] +public class Customer : ActiveRecordBase +{ + private int id; + private string name; + + [PrimaryKey] + public virtual int Id + { + get { return id; } + set { id = value; } + } + + [Property] + public virtual string Name + { + get { return name; } + set { name = value; } + } +} + +[ActiveRecord] +public class Order : ActiveRecordBase +{ + private int id; + private Customer owner + + [PrimaryKey] + public virtual int Id + { + get { return id; } + set { id = value; } + } + + [BelongsTo(Lazy = FetchWhen.OnInvoke)] + public Customer Owner + { + get { return owner; } + set { owner = value; } + } +} +``` + +Using the ActiveRecord ByteCode, you may access a Customer like this: + +```csharp +Customer customer = Customer.Find(1); + +Console.WriteLine(customer.Name); // loads data +``` + +The standard Castle ByteCode would throw a LazyInitializationException when customer.Name was accessed. In other words, you don't need a underlying opened SessionScope when you replace the standard bytecode provider with the AR one. + +Lazy objects can also be accessed through a BelongsTo relationship. + +```csharp +Order order = Order.Find(1); + +Console.WriteLine(order.Owner.Name); +``` + +The ActiveRecord ByteCode will also allow you to move lazy objects between sessions without error. + +```csharp +Order order; +using(new SessionScope()) +{ + order = Order.Find(1); +} + +//...snip... + +using(new SessionScope()) +{ + Console.WriteLine(order.Owner.Name); +} +``` + +## Configuration + +To use the ActiveRecord ByteCode you must reference it when configuring ActiveRecord. Set the proxyfactory.factory_class attribute to "**Castle.ActiveRecord.ByteCode.ProxyFactoryFactory, Castle.ActiveRecord**". + +For instance, you may have the following in your XML configuration file: + +```xml + + + + + + + + + +``` \ No newline at end of file diff --git a/docs/configuration-and-initialization.md b/docs/configuration-and-initialization.md new file mode 100644 index 0000000..f870dff --- /dev/null +++ b/docs/configuration-and-initialization.md @@ -0,0 +1,153 @@ +# Configuration and Initialization + +Before using an ActiveRecord class in runtime you must properly initialize the framework. This must happen only once for the entire application lifetime. + +In order to initialize the framework, you must supply some obligatory information. + +* What kind of database you are using +* How to connect to it + +Optionally you can turn debug on, turn on caching and make fine tune adjustments. The sections below explain how to start the framework and give it all information it needs to initialize itself properly. + +## Initializing + +Before using ActiveRecord your application must invoke the method `Initialize` on `ActiveRecordStarter`. This method has a few overloads. Ultimately it needs an implementation of `IConfigurationSource` and a set of ActiveRecord types to inspect. It basically configures NHibernate, inspects the types for semantic errors and constructs the mapping for each of them. + +In this section we will postpone the construction or obtention of an `IConfigurationSource` and focus the attention on the overloads. + +### Initialize(IConfigurationSource source, params Type[] types) + +This overload allow you to specify an IConfigurationSource and an array of ActiveRecord types. For example: + +```csharp +using Castle.ActiveRecord; + +IConfigurationSource config = ... ; + +ActiveRecordStarter.Initialize(config, typeof(Blog), typeof(Post)); +``` + +:information_source: When you use this overload you must remember that once you add one or more ActiveRecord types you must include the types on the call, otherwise the new types will not work. + +### Initialize(Assembly assembly, IConfigurationSource source) + +This overload allow you to specify an `IConfigurationSource` and an `Assembly` instance. ActiveRecord then iterates over all types on the Assembly and initializes the types it identifies as ActiveRecord types. The implementation checks whether the type uses the `ActiveRecordAttribute`. + +Using this overload saves you from updating the call everytime a new type is included to your domain model. + +Example: + +```csharp +using Castle.ActiveRecord; + +IConfigurationSource config = ... ; + +Assembly asm = Assembly.Load("Company.Project.Models") + +ActiveRecordStarter.Initialize(asm, config); +``` + +:warning: **Warning:** Try to create an `Assembly` exclusively for ActiveRecord types if you can. This overload will inspect all public types. If there are thousands of types, this can take a considerable amount of time. + +### Initialize(Assembly[] assemblies, IConfigurationSource source) + +Just like the overload above, this overload allow you to specify an `IConfigurationSource` and an array of `Assembly` instances. ActiveRecord then iterates over all types on the `Assembly` array and initializes the types it identifies as ActiveRecord types. The implementation checks whether the type uses the ActiveRecordAttribute. + +This overload is useful when you have reusable ActiveRecord classes and a project uses a combination of reusable classes and their own types. + +Example: + +```csharp +using Castle.ActiveRecord; + +IConfigurationSource config = ... ; + +Assembly asm1 = Assembly.Load("Company.Common.Models") +Assembly asm2 = Assembly.Load("Company.Project.Models") + +ActiveRecordStarter.Initialize(new Assembly[] { asm1, asm2 }, config); +``` + +### Initialize() + +This overload uses an assumption to initialize the framework. It assumes that: + +* The configuration can be obtained from the configuration associated with the AppDomain (more on that on the next section). +* All ActiveRecord types can be found on the executing assembly. + +As much as this is the simpler overload, it is also the one which imposes more restrictions. As a general rule, do not use it. + +```csharp +using Castle.ActiveRecord; + +ActiveRecordStarter.Initialize(); +``` + +## Configuring + +Configuring Castle ActiveRecord is necessary as it will never be able to guess the database you are using or the connection string. When you configure it, you end up configuring the NHibernate instance ActiveRecord uses, and some other configuration is specific to ActiveRecord. + +Basically you need to tell: + +* The driver +* The dialect +* The connection provider +* The connection string + +These entries are not optional and must be informed. Optionally you can configure caching, schema name, query substitutions. You can find more information about those on NHibernate documentation. + +Castle ActiveRecord abstracts the configuration from its source using the interface IConfigurationSource. You can construct an implementation yourself if you want to, or you can use one of the three implementations provided, which are listed below. + +### InPlaceConfigurationSource + +The `InPlaceConfigurationSource` allows you to hardcode the information (or at least part of it) required to configure. + +```csharp +using System.Collections; +using Castle.ActiveRecord.Framework.Config; + +IDictionary properties = new Dictionary(); + +properties.Add("connection.driver_class", "NHibernate.Driver.SqlClientDriver"); +properties.Add("dialect", "NHibernate.Dialect.MsSql2000Dialect"); +properties.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider"); +properties.Add("connection.connection_string", "Data Source=.;Initial Catalog=test;Integrated Security=SSPI"); + +InPlaceConfigurationSource source = new InPlaceConfigurationSource(); + +source.Add(typeof(ActiveRecordBase), properties); + +ActiveRecordStarter.Initialize(source, typeof(Blog), typeof(Post)); +``` + +`InPlaceConfigurationSource` is only recommended in two situations. You are getting acquanted with ActiveRecord or your system has a custom configuration support and you want to integrate wit it. + +### XmlConfigurationSource + +You can also read the configuration from a Xml file. The schema is documented in [XML Configuration Reference](xml-configuration-reference.md). + +```csharp +using Castle.ActiveRecord.Framework.Config; + +XmlConfigurationSource config = new XmlConfigurationSource("ARConfig.xml"); + +ActiveRecordStarter.Initialize(config, typeof(Blog), typeof(Post)); +``` + +:warning: **Warning:** If a non-absolute filename is use like in the example above, the file will be searched based on the working directory. Usually the working directory is the bin folder. + +The `XmlConfigurationSource` is the better approach if you want to externalize the configuration in a file with an exclusive purpose of holding the ActiveRecord configuration. + +### ActiveRecordSectionHandler + +With the `ActiveRecordSectionHandler` you can use the configuration file associated with the `AppDomain` (for example the web.config). The schema is documented in [XML Configuration Reference](xml-configuration-reference.md). + +```csharp +using Castle.ActiveRecord.Framework.Config; + +IConfiguration config = ActiveRecordSectionHandler.Instance; + +ActiveRecordStarter.Initialize(config, typeof(Blog), typeof(Post)); +``` + +:warning: **Warning:** If a section is not found an exception will be thrown. \ No newline at end of file diff --git a/docs/creating-an-activerecord-class.md b/docs/creating-an-activerecord-class.md new file mode 100644 index 0000000..497e4d6 --- /dev/null +++ b/docs/creating-an-activerecord-class.md @@ -0,0 +1,92 @@ +# Creating an ActiveRecord class + +Castle ActiveRecord acts on what is called ActiveRecord types. Those are classes that use the ActiveRecordAttribute. They can be also extended from one of the ActiveRecord base classes, although this is not strictly necessary. There is a generic base class and another one that supports [Validators](validators.md). + +## The ActiveRecordAttribute + +The `ActiveRecordAttribute` is used to define a class as an *ActiveRecord type* and to associate mapping information. + +The example below uses the parameterless constructor of the attribute: + +using Castle.ActiveRecord; + +```csharp +[ActiveRecord] +public class Product : ActiveRecordBase +{ + ... +``` + +For the situation above the programmer did not made explicit the table name nor the database schema. ActiveRecord then **assumes** the class `Product` is being mapped to a database table with **the same name**. The schema will be `null`. + +The following code snippet make the table name explicit: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("Products")] +public class Product : ActiveRecordBase +{ + ... +``` + +You can also use the Table and Schema properties to make the information more clear to newcomers: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord(Table="Products",Schema="dbo")] +public class Product : ActiveRecordBase +{ + ... +``` + +Please refer to the Reference Manual's Attributes article for further information. + +## The ActiveRecordBase class + +:information_source: An *ActiveRecord type* must have a parameterless contructor. + +The use of a base class is optional for using ActiveRecord. However, using one of base classes is a simple way to expose data operations at the entity class. + +The default base class is `ActiveRecordBase``. This class is generic by purpose. It defines multiple methods for fetching objects of the type from the database that use the generic class as return type. This allows the use of those methods without explicitly casting the results down to the required class. Example: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord] +public class Product : ActiveRecordBase +{ + // mapping omitted +} + +public class ClientCode +{ + public void UsesFindMethod(int id) + { + Product p = Product.Find(id); + // use p + } +} +``` + +If it is not possible to use a generic baseclass for some reason, the non-generic version of `ActiveRecordBase` can be used. The relevant find methods of this class are marked protected internal and must be overwritten to be used by client code: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord] +public class Product : ActiveRecordBase +{ + // mapping omitted + + public static Product Find(int id) + { + return (Product)FindByPrimaryKey(typeof(Product), id); + } +} +``` + +The `ActiveRecordBase` class also exposes instance members to `Save`, `Create`, `Update` and `Delete`. The `Save` operation is able to find out if a class needs to be created or updated. `Create` and `Update` do not perform this check. More information on these methods can be found under ActiveRecord operations + +:warning: **Warning:** If a class has an assigned primary key type (i.e. the primary key is not generated by the database) it is necessary to specify a value that marks unsaved instances or it is **not possible** to use `Save`. For those classes `Create` or `Update` are available only. See the section about primary key mappings for more information on this topic. \ No newline at end of file diff --git a/docs/enabling-lazy-load.md b/docs/enabling-lazy-load.md new file mode 100644 index 0000000..e3d59ff --- /dev/null +++ b/docs/enabling-lazy-load.md @@ -0,0 +1,71 @@ +# Enabling Lazy Load + +Lazy load is a well known pattern where data is only obtained when it is really needed. If you do not have lazy load enabled and you have a large and complex object graph, when you load one object all dependencies will be loaded in a chain, issuing several SQL statements. + +Lazy load can be enabled on a type or in a relation. + +## Enabling lazy on an ActiveRecord type + +If can enable lazy load for a type by using the `Lazy=true` on the `ActiveRecordAttribute`. For example: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord(Lazy=true)] +public class Customer : ActiveRecordBase +{ + // omitted for now +} +``` + +However, when you do this, NHibernate generates a dynamic proxy for you class. So whenever you load a `Customer` class you will get a `CustomerProxy` (the name is more complex than that). + +The proxy is necessary so NHibernate can intercept how you are using the instance. Once you invoke a method, NHibernate can identify it and load the data for you. If you never use the object, it will not waste time loading a data you will never use. + +You must be aware that all methods and properties on your class must now be declared as virtual. This is required as the proxy needs to inherit from your class and override the methods in order to make interception work. + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord(Lazy=true)] +public class Customer : ActiveRecordBase +{ + private int id; + private string name; + + [PrimaryKey] + public virtual int Id + { + get { return id; } + set { id = value; } + } + + [Property] + public virtual string Name + { + get { return name; } + set { name = value; } + } +} +``` + +:warning: **Warning:** NHibernate will throw an exception if it identifies an instance method not defined as virtual. + +Lazy will only work if the session that loaded the proxy is kept alive. This means that you **must** enclose the code that use types with lazy enabled in a `SessionScope`. Otherwise you will get an lazy initialization failure exception. + +```csharp +using(new SessionScope()) +{ + Customer customer = Customer.Find(1); + + Console.WriteLine(customer.Name); // loads data +} +``` + +## Enabling lazy on a relation + +All relation attributes (except `BelongsTo`) have a `Lazy` property that is `false` by default. You just need to enable Lazy along the same lines as described above. + +You do not need to mark anything as virtual when enabling lazy for relations, though. However the same rules applies regarding `SessionScope`. + +When lazy is enabled for a collection, NHibernate returns a lazy enabled collection. Once it is accessed, the items are loaded. \ No newline at end of file diff --git a/docs/external-articles.md b/docs/external-articles.md new file mode 100644 index 0000000..09ed241 --- /dev/null +++ b/docs/external-articles.md @@ -0,0 +1,24 @@ +# External Articles on ActiveRecord + +Hammett's introductory articles on GeeksWithBlogs: + +* [All you ever wanted to know about ActiveRecord I](http://geekswithblogs.net/hammett/articles/76697.aspx) +* [All you ever wanted to know about ActiveRecord II](http://geekswithblogs.net/hammett/articles/76809.aspx) + +Ayende made a Castle Demo App together with 12 blog posts about it: + +* [Castle Demo App: Midway Summary](http://ayende.com/blog/1113/casle-demo-app-midway-summary) (Midway is actually the last post I could find about the demo app) + +Paolo Corti has written a series of articles about building a website with AR and MR: + +* [Castle MonoRail and ActiveRecord Tutorial (Part 1)](http://www.paolocorti.net/2007/05/22/castle-monorail-and-activerecord-tutorial-part-1/) +* [MonoRail and ActiveRecord configuration (Castle MonoRail and ActiveRecord Tutorial - Part 2)](http://www.paolocorti.net/2007/05/27/monorail-and-activerecord-configuration-castle-monorail-and-activerecord-tutorial-part-2/) +* [Writing the Domain Model classes: implementing Identifiers and Properties (Castle MonoRail and ActiveRecord Tutorial - Part 3)](http://www.paolocorti.net/2007/07/10/writing-the-domain-model-classes-implementing-identifiers-and-properties-castle-monorail-and-activerecord-tutorial-part-3/) +* [Writing the Domain Model classes: implementing Relationships (Castle MonoRail and ActiveRecord Tutorial - Part 4)](http://www.paolocorti.net/2007/08/30/writing-the-domain-model-classes-implementing-relationships-castle-monorail-and-activerecord-tutorial-part-4/) +* [Writing the Domain Model classes: coding time (Castle MonoRail and ActiveRecord Tutorial - Part 5)](http://www.paolocorti.net/2007/09/13/writing-the-domain-model-classes-coding-time-castle-monorail-and-activerecord-tutorial-part-5/) + +Markus Zywitza on [Morts Like Us](http://mortslikeus.blogspot.com): + +* [Understanding ActiveRecord Sessions 1](http://mortslikeus.blogspot.com/2009/04/understanding-activerecord-sessions-1.html) +* [Understanding ActiveRecord Sessions 2](http://mortslikeus.blogspot.com/2009/04/understanding-activerecord-sessions-2.html) +* [Mapping Collections of simple objects](http://mortslikeus.blogspot.com/2009/06/understand-activerecord-mapping.html) \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..c324ec0 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,114 @@ +# Frequently Asked Questions + +This page has a list of frequently asked questions. + +## General questions + +### What is ActiveRecord? + +ActiveRecord is a well-known pattern described in [Patterns of Enterprise Application Architecture](http://www.amazon.com/exec/obidos/tg/detail/-/0321127420/qid=1119217017/sr=8-1/ref=sr_8_xs_ap_i1_xgl14/103-5320883-5187853). Basically all static methods act on the whole set of records, while instances represents each row. + +Read more about the pattern: + +* [Patterns of Enterprise Application Architecture Catalog - Active Record pattern](http://www.martinfowler.com/eaaCatalog/activeRecord.html) +* [Wikipedia Active Record article](http://en.wikipedia.org/wiki/Active_record) + +### What is Castle ActiveRecord? + +Castle ActiveRecord is an implementation of the ActiveRecord Pattern inspired by [Rails' ActiveRecord](http://api.rubyonrails.com/?u=ar.rubyonrails.com), which relies on NHibernate to perform the actual mapping (as you see we don't suffer from not-invented-here anti-pattern). + +### How does it differ from pure NHibernate usage? + +Castle ActiveRecord was built on top of NHibernate. It offers: + +* Fast development (it handles the mapping and infers as much as it can so you don't have to dig into documentation or deal with tons of xml files every time something changes on your schema) +* Predefined common methods like `Create`, `Update`, `Save` and `Delete` +* Easy to implement method like `Find`, `FindAll` and `FindByName` (predefined if you use `ActiveRecordBase`) +* Session and transaction scopes that abstracts the `ISession` offering a more natural idiom +* By using pure NHibernate, you have more control over more complex mappings. However, using Castle ActiveRecord is a guarantee to boost your productivity + +### My text columns are being truncated + +Add `ColumnType="StringClob"` parameter to your Property attribute. For example: + +```csharp +[Property(ColumnType="StringClob")] +public String Contents +{ + get { return _contents; } + set { _contents = value; } +} +``` + +This tells NHibernate to map the string to a `Text` type rather than a `nvarchar(4000)` type. + +### My property name is a reserved word in my database + +Add explicit column name parameter to your Property attribute and quote it with backticks. For example: + +```csharp +[Property("`User`")] +public String User +{ + get { return _user; } + set { _user = value; } +} +``` + +This tells NHibernate to quote the column name when querying the database. + +### ActiveRecord throws an exception saying: Ambiguous column name 'Status' + +A column named Status is returned internally by NHibernate. Try renaming your column in the database. + +## Database related + +### Gaining access to the underlying database connection + +This is possible using the session holder: + +```csharp +using Castle.ActiveRecord; + +... + +// Expects a root type +ISession sess = ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(ActiveRecordBase)); + +// Now you can use sess.DbConnection + +// Release the session when finished (ie. try, catch, finally) +ActiveRecordMediator.GetSessionFactoryHolder().ReleaseSession(sess); +``` + +Try to use a finally block to release the session. Do not invoke `ISession.Close` or `ISession.Dispose`. + +## Lazy loading + +### How to enable lazy loading + +Check the documentation (trunk or v1rc1) on lazy load. + +### What 'Failed to lazily initialize a collection - no session' error means? + +Means that there is no session available. Check the [Enabling lazy load|documentation on lazy load] in order to know how to make it work properly. + +### Lazy loading in web applications + +Check the documentation on [Web applications|ActiveRecord in web applications]. + +### Lazy loading in desktop/winforms applications + +Start the ActiveRecord during application start up. You can create a readonly SessionScope and flush it in appropriate moments using `SessionScope.Current.Flush`. Check the manual for further reference. + +## Changes to objects are persisted without an explicit call to the Save() method + +This commonly happens if you are using SessionScope per request pattern. Basically this is an expected NHibernate behavior, although the first time you experience it, it can seem very confusing. + +From: [NHibernate Users FAQ](http://www.hibernate.org/359.html#A7) + +(((When an object is loaded by NHibernate the ISession keeps a snapshot of the state of your object. When you `Flush()` the ISession NHibernate compares that snapshot to the current state of the object. The appropriate changes are written to the database.))) + +So when the Session is `Flush()`ed any changes to an objects state are written to the database. The next question is when does a `Flush()` occur? Well according to [Tobins' NHibernate FAQ](http://www.tobinharris.com/2007/2/3/nhibernate-faq) it can occur at a number of different (and possibly unexpected) places. + +If this is really bothering you, you can create a readonly SessionScope. In this case it is up to you to Flush it. You can read more about the SessionScope on ActiveRecord documentation. \ No newline at end of file diff --git a/docs/framework-events.md b/docs/framework-events.md new file mode 100644 index 0000000..dc79332 --- /dev/null +++ b/docs/framework-events.md @@ -0,0 +1,11 @@ +# Framework Events + +ActiveRecord exposes some events that you might use to integrate it with a top level framework. + +## ActiveRecordStarter.SessionFactoryHolderCreated + +You can subscribe to this event to get notification about the creation of an `ISessionFactoryHolder` instance implementation. + +## ISessionFactoryHolder.OnRootTypeRegistered + +You can subscribe to this event to get notification when a *root type* is registered. A *root type* defines the database being used. If you are using only one database the *root type* will be `ActiveRecordBase`. \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..cc2d7ad --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,465 @@ +# Getting Started + +So you've decided to try ActiveRecord but don't know where to start. We can definitely help you on this matter. Just follow the next sections. + +## A crash course + +This tutorial assumes that you have created a simple Console Application to try ActiveRecord (not a windows app, not a web app yet, let's stick with the simplest for now). + +We also assume that you know, or at least got a gist of what ActiveRecord is for, so we won't waste your time with fluf. + +### Necessary assemblies + +You must reference the following set of assemblies to use ActiveRecord: + +* `Castle.ActiveRecord.dll` +* `Castle.Core.dll` +* `NHibernate.dll` +* `NHibernate.ByteCode.Castle.dll` (or the NHibernate ByteCode generator of your choice) +* `log4net.dll` +* `Iesi.Collections.dll` + +Additional assemblies are necessary for further functionality: + +* Validation: + * `Castle.Components.Validation.dll` +* Full Text Search: + * `Lucene.Net.dll` + * `NHibernate.Search.dll` +* In-Memory-Testing: + * `System.Data.SQLite.dll` + * `nunit.framework.dll` (or the testing framework of your choice) + +### A simple database structure + +Now suppose you have the following table structure: + +```sql +CREATE TABLE Blogs ( + blog_id int IDENTITY(1, 1) PRIMARY KEY, + blog_name varchar(50), + blog_author varchar(50) +) + +CREATE TABLE Posts ( + post_id int IDENTITY(1, 1) PRIMARY KEY, + post_title varchar(50), + post_contents text, + post_category varchar(50), + post_blogid int FOREIGN KEY REFERENCES Blogs (blog_id), + post_created datetime, + post_published bit +) +``` + +### Creating the classes + +Now the fun part begins. Just create a new class named Blog that extends ActiveRecordBase: +(You should have imported Castle.ActiveRecord) + +```csharp +public class Blog : ActiveRecordBase +{ +} +``` + +However this is not enough for ActiveRecord to understand what table this class is mapped to, so if you also need to decorate the class with the ActiveRecordAttribute: + +```csharp +[ActiveRecord("Blogs")] +public class Blog : ActiveRecordBase +{ +} +``` + +:information_source: Note that if the table name was 'Blog', then you wouldn't have to specify the name. + +The next step is to specify the primary key (and you must provide a primary key). In this case our primary key is auto generated by the database (column blog_id): + +```csharp +[ActiveRecord("Blogs")] +public class Blog : ActiveRecordBase +{ + [PrimaryKey(PrimaryKeyType.Native, "blog_id")] + public int Id {get; set; } +} +``` + +Also, this could be made simpler if you the column was simply 'id': + +```csharp +[PrimaryKey] +public int Id{get; set; } +``` + +Finally, map the properties. This couldn't be simpler: + +```csharp +[ActiveRecord("Blogs")] +public class Blog : ActiveRecordBase +{ + [PrimaryKey(PrimaryKeyType.Native, "blog_id")] + public int Id {get; set; } + + [Property("blog_name")] + public String Name {get; set; } + + [Property("blog_author")] + public String Author {get; set; } +} +``` + +That's it. You're now able to create Blog instances (Create, Update and Delete methods are public and inherited from ActiveRecordBase). + +If you didn't inherit from `ActiveRecordBase`, but from the non-generic `ActiveRecordBase` or a custom base class, you must add more methods in order to query the database, or delete the rows: + +```csharp +public static void DeleteAll() +{ + DeleteAll( typeof(Blog) ); +} + +public static Blog[] FindAll() +{ + return (Blog[]) FindAll( typeof(Blog) ); +} + +public static Blog Find(int id) +{ + return (Blog) FindByPrimaryKey( typeof(Blog), id ); +} +``` + +As you see the code is very straightforward. We mapped the class to a table, the fields, and the primary key. Now, before you go and run some test code, we must start the framework properly. In order to do that you must provide some configuration information. + +### Starting the framework + +Before you use ActiveRecord, you need to provide the information about which database are you using. You can keep this information in the AppDomain configuration file, or just hardcode it in the application - for the sake of clarity: + +```csharp +// The following requires: using Castle.ActiveRecord.Framework.Config; + +InPlaceConfigurationSource source = new InPlaceConfigurationSource(); + +Hashtable properties = new Hashtable(); + +properties.Add("hibernate.connection.driver_class", "NHibernate.Driver.SqlClientDriver"); +properties.Add("hibernate.dialect", "NHibernate.Dialect.MsSql2000Dialect"); +properties.Add("hibernate.connection.provider", "NHibernate.Connection.DriverConnectionProvider"); +properties.Add("hibernate.connection.connection_string", "UID=sa;Password=mypass;Initial Catalog=test;Data Source=."); + +source.Add( typeof(ActiveRecordBase), properties ); + +ActiveRecordStarter.Initialize( source, typeof(Blog) ); +``` + +If you want to use an external configuration file, it will look like the following: + +```xml + + + + + +
+ + + + + + + + + + + + + + +``` + +In this case, you can just initialize like this: + +```csharp +IConfigurationSource source = System.Configuration.ConfigurationManager.GetSection("activerecord") as IConfigurationSource; +ActiveRecordStarter.Initialize( source, typeof(Blog) ); +``` + +:information_source: Important: you need add Post class in ActiveRecordStarter.Initialize + +You can then perform your operations with the objects: + +```csharp +Blog.DeleteAll(); + +Blog blog = new Blog(); +.. set fields .. +blog.Save(); // or blog.Create(); +... more operations ... +blog.Save(); // or blog.Update(); +... tired of this blog? +blog.Delete(); +``` + +## Adding relations mapping + +One of the beauties of having a DomainModel implemented using ActiveRecord is how it makes simple to create interelations within your object model. In this case we have created a Blog class, and it's fair enough to provide a Post class and the relation between them. In plain english the relation is + +* A Post belongs to a Blog +* A Blog has many Posts + +### Using HasManyAttribute and BelongsToAttribute + +To express these relations you might use these attributes. For example: + +```csharp +[ActiveRecord("Blogs")] +public class Blog : ActiveRecordBase +{ +... + private List _posts; + +... + + [HasMany(typeof(Post), Table="posts", ColumnKey="post_blogid")] + public IList Posts + { + get { return _posts; } + set { _posts = value; } + } +} + +[ActiveRecord("Posts")] +public class Post : ActiveRecordBase +{ +... + private Blog _blog; +... + [BelongsTo("post_blogid")] + public Blog Blog + { + get { return _blog; } + set { _blog = value; } + } +} +``` + +In this case, as the Post class has a BelongsTo association, ActiveRecord can collect the information to create the correct HasMany, so you really don't need to specify all information: + +```csharp +[ActiveRecord("Blogs")] +public class Blog : ActiveRecordBase +{ +... + private List _posts; +... + [HasMany] + public IList Posts + { + get { return _posts; } + set { _posts = value; } + } +} +``` + +If you're wondering, this is how the Post class should look like: + +```csharp +[ActiveRecord("Posts")] +public class Post : ActiveRecordBase +{ + public Post() + { + Created = DateTime.Now; + } + + public Post(String title) : this() + { + Title = title; + } + + + public Post(Blog blog, String title, String contents, String category) : this() + { + Blog = blog; + Title = title; + Contents = contents; + Category = category; + } + + [PrimaryKey(PrimaryKeyType.Native, "post_id")] + public int Id {get; set; } + + [Property("post_title")] + public String Title {get; set; } + + [Property("post_contents",ColumnType="StringClob")] + public String Contents {get; set; } + + [Property("post_category")] + public String Category {get; set; } + + [BelongsTo("post_blogid")] + public Blog Blog {get; set; } + + [Property("post_created")] + public DateTime Created {get; set; } + + [Property("post_published")] + public bool Published {get; set; } +} +``` + +### Using the relation + +A big source of confusion is how to use the relation (at least with the cascading defaults), for example, the following code **is not going to work** + +```csharp +Blog blog = Blog.Find(1); + +blog.Posts.Add( new Post("This is my first post") ); + +blog.Save(); // Exception! +``` + +That's because the Post instance in this case would be a transient class, not persisted. It must be persisted first. So this is the correct code: + +```csharp +Blog blog = Blog.Find(1); + +Post post = new Post("This is my first post"); +post.Save(); // Saving it first + +blog.Posts.Add( post ); + +blog.Save(); // Now it's OK +``` + +You can also associate the Post with the blog using the other side of the relation: + +```csharp +Blog blog = Blog.Find(1); + +Post post = new Post("This is my first post"); +post.Blog = blog; // Linking them +post.Save(); +``` + +But of course, don't assume that when you do that the blog.Posts will automatically be notified and will then have one element, you must "refresh" it if you want it to reflect the latest changes. + +You can also change the relation cascading settings (on the Blog class): + +```csharp +[HasMany(typeof(Post), Table="Posts", ColumnKey="post_blogid", Cascade=ManyRelationCascadeEnum.SaveUpdate)] +public IList Posts +{ + get { return _posts; } + set { _posts = value; } +} +``` + +On this case the following code is going to work: + +```csharp +Blog blog = Blog.Find(1); + +blog.Posts.Add( new Post("This is my first post") ); + +blog.Save(); // Ok +``` + +## Finding records + +The following methods are exposed by `ActiveRecordBase`, but not by the non-generic version of the base class. In case you do not **directly** inherit from `ActiveRecordBase`, you have to provide the methods yourself, which wont be nothing more than delegating to `ActiveRecordBase` protected methods. + +### FindByPrimaryKey + +A `Find` method will usually be implemented like this: + +```csharp +public static Blog Find(int id) +{ + return (Blog)FindByPrimaryKey(typeof(Blog), id); +} +``` + +In this case, if the record is not found an exception will be the thrown. If you dont want that, and expected only a null return, then change to that: + +```csharp +public static Blog Find(int id) +{ + return (Blog) FindByPrimaryKey(typeof(Blog), id, false); +} +``` + +### FindAll + +The `FindAll` allows you to defined an order by and a criteria, which can go from simple to complex: For most cases, instead of using `FindAll` you can go with `FindAllByProperty`: + +```csharp +public static Blog[] FindByName(String name) +{ + return (Blog[]) FindAllByProperty(typeof(Blog), "Name", name); +} +``` + +Please note that we're using a property name, not a column name. + +If you just want all records: + +```csharp +public static Blog[] FindAll() +{ + return (Blog[] FindAll(typeof(Blog)); +} +``` + +With a criteria (You should have imported NHibernate.Expression): + +```csharp +public static Blog[] FindByAuthor(String author) +{ + return (Blog[]) FindAll(typeof(Blog), Expression.Eq("Author", author)); +} +``` + +With an order and criteria: + +```csharp +public static Blog[] FindByAuthor(String author) +{ + return (Blog[]) FindAll(typeof(Blog), Expression.Eq("Author", author)); +} +``` + +### FindFirst and FindOne + +Sometimes you want the first record found the a criteria, and sometimes you're expecting only one record or none to exists on the database. In those cases use `FindFirst` or `FindOne`: + +```csharp +public static Blog FindFirstBlogByAuthor(String author) +{ + return (Blog) FindFirst(typeof(Blog), Expression.Eq("Author", author)); +} +``` + +## Batching changes + +If you know NHibernate, you know that every set of operation must be enclosed within a valid session. ActiveRecord encapsulates and even hides operations with the session, but even so you can gain access to it (see [Using HQL]) and more importantly, batch operations to be performed together. Using an earlier example: + +```csharp +using (new SessionScope()) +{ + Blog blog = Blog.Find(1); + Post post = new Post("This is my first post"); + post.Save(); + + blog.Posts.Add( post ); + + blog.Save(); +} // The changes will be sent to the DB when the session is disposed here +``` + +## Final thoughts + +This was an introductory tutorial. There are plenty to be said about [Active Record.MainPage|ActiveRecord] and we encourage you to try, bend and push it. In a few hours you'll probably have mastered it. \ No newline at end of file diff --git a/docs/hooks-and-lifecycle.md b/docs/hooks-and-lifecycle.md new file mode 100644 index 0000000..4b7be87 --- /dev/null +++ b/docs/hooks-and-lifecycle.md @@ -0,0 +1,5 @@ +# Hooks and Lifecycle + +The `ActiveRecordBase` class implements NHibernate's `ILifecycle` interface. For more information on the methods involve, consult the [NHibernate documentation](http://nhforge.org/doc/nh/en/index.html). + +Additionaly all `ISession` instances are linked to an `IInterceptor` implementation which ultimately invokes your ActiveRecord class instance. This is a fairly advanced usage. \ No newline at end of file diff --git a/docs/images/castle-logo.png b/docs/images/castle-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6ca6bfbc1dd7d6e941d8feac66617b33c1e57815 GIT binary patch literal 3801 zcmb7{XEfYVx5ob%GddAnBwDoSy|>YO&1fO&C}9wZE+cvg5z)<86iT29p``)|OUB9)@^YQHcJZqh`PlAb&HYFJg82|v3x;h$W*NXgCB;f0_N7-@s zT1b6$tRDja`OSYt0OaN~0RWk?2NY^z;*JPFJa$L;vg<;j?7sd8H;+fI01z~fGKX83 z?=UFgmyXr-}KjoNV+s00S;0cy?Bm-IeOEs8=9IR)QA!Fjnch^8 zB9UwsvcXZ967@Ddap-I{^sU!S8-8o_vhkbpcI6SOVI0y)LXoK>VJd~BC{w?EV! zw|{j*ST&N2+ZUiAX>j3qe8EKkoJJ@r-s9|qbN~cFQ{*H-r(s^F=&iUb>Rt65I|5`h zL1#dktUd{n4p0qAQ7;BmwFr=?Y#ws}0|FinJ3FldCPKhN-e+4=01|bY6G{NszUH7O zz@!1}H(ld30CxqTa{PXxCSWBB(7EaN$^o-NfT%9aO$VrL20F)R$m;ky{+>s~d@w!pPhIrWMy+;Wo<2^cQM?%M3 zRpIX79A@!IN$QoO5dgrX(O*w{bP+U0RXsK)ozg_*Ahi1le8uVHw0^lcUg4_>0ILBJ z6PJP_4J=3nFw*<7@GcJIZcmPRf=_a*r&etQFgx?GBj11CXcoL|nVZ|(*qGJpRkw8< zf`wngyB#`V7mxl#D4yd_Ryx+WBgG#?YJ*N!x<`%;if)a+B8_%h*h4*-X)h_0`EBt%H}sMYbns{@q_ZOmN&>8AV24*={nxP>i-8dL^I06+s1DNw7% zdfde%*a^PXMZC~Oe*RE8TAjPMTb)Lo%msPN{~>?nb9KJh(ONcvhaz89*(5tn-DA^z zsf4>NKTs+8QvY=%L3i=BMUsNl21#x@a?HmQI3-$frI0Y@#GG<;>40dHxY<9))0uMW zr%CCEnI&4VTk5bKD)|%2#o1|nOj8&H#GjwXwrPvKj;uFT`byCVZ7G#uiPkD4|KbuL zmYd3e$@}8}o<%8Hx?uZDg*)|psxovywDUU?M56bRsBnLedY!BwX}a@p9cLv)ML$dJ z{Ys+NgcIlSxG=-SM`Xj+KFx(X37>`z>zC8}pR z&GK!o*eAUrdZq=-SHkZ-vd70Sj@O> zl=THRIlQC&J?lHsaUca{EPd2KJe-d?j|svAXAWel$u=lvo=RdKun?6I+soe0q0T{A zS_w1>gwdxBk!9#*@MN$Gh**@B7nd)UlUp8H4qK#_*_($~YgtN|ua}X2&8)C2N15!I zvzo)p6iVaD;iU)*m3I-=7YShN9)liZ=sQzPox^%tKRdZIb3>3IhZkiMvU4vpEF-9AuC}p zRFXfb7B2THVl8OZ2>j$zg3CsC*dx&fh?1q?_U*s7!bL7DF1-JioM=;6fg32e!B~nh zCdED}KZGKi$N=`2DfB^)DzwNk!!c_$I;~|w@$IzM3oYtokAAy#?se~e@BX(uU>*)0 zeZyTtT&|g6OEG`3f`Nj;!05eg@A z6kFq(+I(VcoBLncp9 zc@25p`=!_#oHWh@7q{?cMDh(k+FIiAm!3iBis|Nr3i+E1QLp972xF*mB)oB(OT<|u z4OaE7wz(*<9PfC*sQg>m|INDcx}waI_>#55h)iemVDqqX4+8BFvkZO4b>(suay0>z zAny{ui4r2!B5euJPD8>tKBcJ0V_kpO&+V%vV_MYc)l#2VJ>TrA;&31Q@v@2oznwjn zHs(PUV-oRw<_pAe%Q2}Zf`T_DE(R8>lHwb0#WEq5B^5Za0DZiy@mC{LV<`_VW-M1D zq$eeyTrTXXI4|>2@}o+>u$%SBv=z!q_mTcBIbnfnX`e?O%jf$?acihG^7T;0Y>~kS zzQF}!-N!K{3}wQ!g7TTF2JuF+uYao12$V7;-#xq;csGSCxSOpT&M7h>JrO?(wG+5) zbf_a>81m{zTANfbcb`O&;ATrSZ`sQ>14@I%Jb5%j?o)PeaV7b7v8Y#RFa6ZZISkXA z;JffGx2MCEt}*qOeLhLeBDXUjpS>DhRuWqaku|9of48vV`z8RXhr7buK8+>}$z@ic z?~U${Gz?*1%H$usx!R}grd@s2R1~WZ6JoR4YOnN^t5@_3#*(em6W@!y53W@Fkk};P zJ#AdE56h~KXy*wr^oq3a8hYNm5B9nqvPLL6st0QLSi)? zmNAR|*(~2e&?ajfV(3%O!t3{C6}>Gk<8G5~J6lUaXj`hx@~o{aDj&+Z`S+B|_uucI z{MN*ZWR3D+TcetwEpI<5erR(o`8+nNpkndP;@IM^g*0w*vC;9{sm}zCHMH%wlEBh%+HKnPO!uq-uTq^*zH^#+=KY|`$lGs!fM}BF0>VYr^sVLVG~1+- zg<0y%y*+KJfjb?SsDnTuN@+^V7y12bT)rB?8#{Q?W&CYQ`T3{Gp_QwUsXI^E;A|qE z>bZxxT6yNVXSPnZAB{(pCuVk+Le90Zcc}9v6|IByjx0WV%*5eFY>0#+wAuadSE+7!m@4nG7O)?+ts!al%qYvdigcLR16FVm-t z2U_!6bE{kIyjpM}?Tw*s_#IZX%-G^_d+;xYtC?xEoYG?ZQ3wLxhd)_nSd8%Jy%-yA zoeSi}RkaznIUK9*tIa_dToP& z>>gTHSx#_n13lbOJ`0`zXB0uADuf( z2w|`HO?GD;GeZCfxqH2bMghQ|t83i>fG45=u=@}IQ5%J2QPYVD*AYBbLi=g=* zsPQCL%Uhi)Q!cHBdzmq2Dkgq=$jloYUHd#H?dH4Cd*tpk}&@N zU~p*SR)_8@?x59uBN>9HNODy!lRBjpA9PXZ&E0lz8~72VyaDhby0c*&f-peAkTS>t zqUm5Dj5sVGzT@}3gV_k=sm1Vv?5AC{f8aa{w{1x|g$s~DHWMD$5-Jk45wYTC$ztSTTF&FN2)PKCrpb*P-aB(#$y>9G>Hh1UK;JKtuZ~kCN3ee8~TX zf&OEo6MI6mt1k>;^Yr?Qw6RB$9HsKaf$hJ|z; zfvCv`DC5uxoJ>T6yCkBi^rcQS!IE+EXTyS>4v*cl&o=LYKP8h>apAV9hr{T7S!Jp1 zD8lG2|Gs?tiyWLT3pFeeK&lL1lsVlw1qJc@isJA7^!bW$mQ|ANfB#e9x($yNL!uVF zr_2Tr2>bY?J9N0RIHrc{iF{T3+>Sl*zW@f`RUpOR)c8E|z4k3_lvNQ~9o~sHDDgs< zVtUxi{JA>&BZ$Ie_b_gn3K2n3dj`n*kWQhYWj&)8VH1Eo?}A}2Ba;hQKV0}GPUZ!} zPYX*5-`t-qVc6dfXvV<8HLf%tmbl5-ufYKSHSmSi6U5QejEXi5jP$;`9x+r|5 z0hzHtfSV6gJ5BqQW0IQMHI6 zs0e;!1iGkib_AoS@5}=mdi@$QRq!ve=(lp>{rM1kVl)pfU75uIf2%-gMPUU`jF@UL zxO!R1h(+NXH}+d$;O`J#XJm6^g@pWGe%{*E7=a8jHM_^t<|-W&RM?;MO}D`iT{;*J)ekxIo7@>LvjVA*KY@)t7)WBt^V-& Fe*l4j +{ + private int _id; + private string _key; + + public Word() {} + + [PrimaryKey] + private int Id + { + get { return _id; } + set { _id = value; } + } + + [Property] + public string Key + { + get { return _key; } + set { _key = value; } + } + + public IList FindSynonyms() + { + string query = @" +select synonym.key +from word, synonym +where + synonym.word = word.id and + word.key = :key"; + + return (IList) ActiveRecordMediator.Execute( + delegate(ISession session, object instance) + { + return session.CreateSQLQuery(query, "synonym", typeof(Word)) + .SetParameter("key", this.Key) + .SetMaxResult(10) + .List(); + }, this); + } +} +``` + +Here you have a more complex query. There is a Menu model to store menu items, there is a MenuItemTranslation model to store items translations and the last model is the Language one, to store languages. + +```csharp +private const string translationQuery = @" +select menuitemtranslation.translation +from menu, language, menuitemtranslation +where + menuitemtranslation.menu = :menuid and + menuitemtranslation.lang = language.id and + language.englishname = :lang +"; + +public string FindTranslation(string lang) +{ + if ((lang == null) || (lang.Length == 0)) return Description; + + IList translations = (IList) ActiveRecordMediator.Execute( + delegate(Isession session, object instance) + { + return session.CreateSQLQuery(translationQuery) + .SetParameter("menuid", this.Id) + .SetParameter("lang", lang) + .SetMaxResults(1) + .IList(); + }, null); + if ((translations != null) && (translations.Count > 0)) + { + return translations[0]; + } + else + { + return Description; + } +} +``` \ No newline at end of file diff --git a/docs/nested-data.md b/docs/nested-data.md new file mode 100644 index 0000000..55e2fd2 --- /dev/null +++ b/docs/nested-data.md @@ -0,0 +1,129 @@ +# Nested Data (NHibernate Components) + +You can use different classes to map specific chunks of data. For example, a Customer table might have address related column. Instead of having the address on the Customer ActiveRecord class, you could map them to an Address class. + +## Using a separated class + +For the example stated above, we could have `Customer` ActiveRecord class declared as the example below: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord] +public class Customer : ActiveRecordBase +{ + private int id; + + private string street; + private string city; + private string state; + private string zipcode; + + [PrimaryKey] + private int Id + { + get { return id; } + set { id = value; } + } + + [Property] + public string Street + { + get { return street; } + set { street = value; } + } + + [Property] + public string City + { + get { return city; } + set { city = value; } + } + + [Property] + public string State + { + get { return state; } + set { state = value; } + } + + [Property] + public string ZipCode + { + get { return zipcode; } + set { zipcode = value; } + } +} +``` + +We can then extract the address related mapping to an `Address` class: + +```csharp +using Castle.ActiveRecord; + +public class Address +{ + private string street; + private string city; + private string state; + private string zipcode; + + [Property] + public string Street + { + get { return street; } + set { street = value; } + } + + [Property] + public string City + { + get { return city; } + set { city = value; } + } + + [Property] + public string State + { + get { return state; } + set { state = value; } + } + + [Property] + public string ZipCode + { + get { return zipcode; } + set { zipcode = value; } + } +} +``` + +Now we can simplify the `Customer` class code: + +```csharp +[ActiveRecord] +public class Customer : ActiveRecordBase +{ + private int id; + private Address address; + + [PrimaryKey] + private int Id + { + get { return id; } + set { id = value; } + } + + [Nested] + public Address Address + { + get { return street; } + set { street = value; } + } + +} +``` + +You can optionally specify a column prefix using the `ColumnPrefix` attribute. + +Please refer to the Reference Manual's Attributes article for further information. \ No newline at end of file diff --git a/docs/persistency-lifecycle.md b/docs/persistency-lifecycle.md new file mode 100644 index 0000000..bfad5cb --- /dev/null +++ b/docs/persistency-lifecycle.md @@ -0,0 +1,159 @@ +# The Persistency Lifecycle + +More important than knowing how to map ActiveRecord-Classes is knowing how to use them. The underlying NHibernate is mostly abstracted by ActiveRecord, however there are basic concepts about NHibernate persistence knowledge of is required to use Castle ActiveRecord. + +## Understanding Sessions + +NHibernate uses the concept of a session. Castle ActiveRecord abstracts using the session by doing the session management for the using code, but the session concept is necessary to grasp the concepts behind object persistence with NHibernate and Castle ActiveRecord. + +A **session** is a unit of work boundary of NHibernate. Objects that are **connected** to a session, will be persisted to the database specified in the session. + +A session will not write to the database everytime something is changed. In order to achieve better performance, database access is usually delayed until the last possible moment. This avoids unnecessary database calls, i. e. when multiple fields of a persistent objects are changed within a session. This synchronization with the database is called **flushing**. + +Sessions can use **transactions** that offer the ability to create all-or-nothing change sets that span multiple flushes. Such a transaction is distinct from a database transaction, although it uses the latter internally. + +## The Attributes of Objects + +With respect to object-relational mapping there are different attributes of objects to look at. Each of these attributes describes a certain aspect of an object that is important to persistence. These attributes will be put together into a lifecycle consisting of several states that are defined by combining the attributes. + +Most simply, an object can be **existant** or **non-existant**. Since there will be a lot of objects that live only as database records, but not in memory, non-existant is an important object state in O/R. Another naming convention differs between **activated** and **passivated** objects, but this makes only sense to persistant objects. + +Regarding persistence, an object can be **transient** or **persistant**. A persistent object is backed by a database record, a transient object is not. + +In any session, an object can be **attached** or **detached**. An attached object is connected to the session. It will be monitored by that session and saved to the database. + +An object can be **unchanged** or **changed**. A changed object's state has been altered and is not synchronized to it's database record if it is persistant. + +## Entity States + +The states presented here deal with entities. The word **entity** is used here to describe the combination of object and database record. + +State | Description +----- | ----------- +Non-existant | The entity does not yet exist, neither in memory nor in the database. +Transient | The entity has been created and lives in memory. It has no connection to a database record because it either wasn't saved to the database before or it has been already deleted. +Unsaved | The entity has been created and lives in memory. There is not yet any databased record associated with it, but the creation of the record has already been scheduled. +Persistant | The entity lives both in memory and at the database. +Unchanged | A persistant entity that has no unsaved changes to its state. +Changed (Dirty) | A persistant entity that has unsaved changes to the objects internal state. The next time, changes are sent (flushed) to the database, the changes will be written to the associated database record, synchronizing the objects internal state with the database record. +Detached | A persistant entity is normally attached to a session. The session tracks changes to the entity and takes care for flushing that changes back to the database. If changes should not be saved or the object shall leave the scope of its session, it can be detached from the session. The entity is still persistant, but the object is temporarily transient. It can be reattached to another session. A common examples for detaching and reattaching is keeping an object stored in an ASP.NET session store and save changes back when the object is back in a consistant state several pages later. This technique is used when creating wizard-like web pages for supporting complex workflows. +Deleted | The entity is persistant, but the database record is scheduled for deletion. The object may still exist in memory, but it will become transient after the next flush. +Stale | When a persistant entity's database record has been changed by another session, changes of the objects internal state cannot be synchronized anymore with the database. This entity's state is called stale. + +## State Transitions + +The figure below shows the state transition diagram. + +TODO + +The transitions will be explained below, when stepping through common usage scenarios for ActiveRecord. In the diagram, all transitions that must be explicitly triggered by the user, are methods of the entity type. These transitions are capitalized and postfixed with parentheses. + +The other transitions are defined in the table below: + +Transition | Description +---------- | ----------- +new | Creation of an entity, either by directly constructing or by using a factory. +change | Change of an entity's internal state by any method,i.e. calling business logic or changing attributes. +flush | A flush either initiated by the code itself by calling scope.Flush() or XxxAndFlush or by the session. The latter happens for example when the session is requested to perform queries or has to load an entity. +ext. change | External changes are when another sessions change an entity's database record. + +## Common Scenarios + +This section deals with common scenarios that use ORM to persist changes with different requirements. The scenarios show how the different states of entities can be used to satisfy the special limitations of the scenario. + +Throughout the scenario discussion, the necessary state transitions are explained when they first appear. It is therefore advisable to read the scenarios completely, even when looking for how to support only one of the application types. + +### CRUD + +This scenario discusses the basic usage of persistant entities. Although there is not much business logic involved, almost all applications contain code to **C**reate, **R**ead, **U**pdate and **D**elete entities. + +CRUD functionality is commonly used for administration purposes, allowing operations to rectify data when an error is discovered in an application that leads to data corruption. Other types of applications consist mostly of CRUD screens. Among these, most prominent are MS Access databases. If such an application has to be replaced with an ActiveRecord application, the biggest part of the application will handle CRUD. + +At first, an entity must be created. An object of the entity class is instantiated. This object is now transient. Its properties and fields will be populated. + +To store the object in the database, either the method entity.Save() or entity.Create() must be called. This call tells ActiveRecord that the entity should be persisted. It isn't yet, but creation is scheduled for the next flush. This state is called unsaved. + +When the session flushes, all changes are written to the database. This includes creating database rows for unsaved objects. After the flush, the entity is persistent and unchanged, since it has been just synchronized with the datastore. + +:warning: **Warning:** Depending on the mapping of the primary key, it is possible that ActiveRecord hits the database immediately after calling `Save()` or `Create()`. This is the case if a native primary key (for example `IDENTITY` in SQLServer) is used. This action will insert the record but not perform a full flush and can lead to unexpected results. It is therefore recommended not to use native primary keys. Code that is cluttered with explicit calls to `scope.Flush()` heavily suffers from native primary keys. More information on mapping primary keys is found in the section about [primary key mapping](primary-key-mapping.md). + +Now that the entity is created, it must be shown to the user, either with other entities as part of a list or aggregation or on its own. If the object is not already present in the session because it has just been created, it must be loaded from the database. This is the **R** in CRUD. + +Fetching entities from the datastore is accomplished by calling `Entity.Find(key)`. Other methods read more than one entity and allow querying for specific objects. All of these methods are static methods of the entity class and start with Find. + +The Find method reads the data from the record and creates an object, populating it with the data read from the database. This method is persistant and unchanged. The object can now be displayed to the user and changed. + +Supposed that the object has been displayed with widgets that allow updating its properties, the user can change the object by editing the data. After the user submits the form in a web application, the data is sent back to the server, starting the **U** in CRUD. + +The server has just started a new session with the new request (this is strongly recommended). It will fetch the entity from the database, and the object is created and unchanged. The server process now changes the objects properties according to the data submitted by the user. The objects state will change from unchanged to changed. When the session flushes, the changes will be synchronized automatically with the database record. There is **no** need to call `Save()` or `Update()` (details are given below). + +It is also possible but not recommended to build a transient object from the screen's data and call `Save()` or `Update()` to persist the object. This means that one Select is avoided, but it works only if all data is present in the screen or request. If the entity is changed later, there might be data loss, if the editing screen is not updated! + +The last operation in CRUD is **D**eletion. There are two possibilities for deletion: The entity can be loaded and then `entity.Delete()` is called. The other possibility is calling `Entity.Delete(key)`. The latter deletes the entity from the database without loading it into memory before. + +The entity now enters the deleted state and will be removed from the database when the session flushes. If an object has been loaded into memory before, its state will be deleted after calling `Delete()`. After the flush the entity is transient again. If it is planned to resave the object again, its primary key property must be either reset to the unsaved value or `Create()` must be explicitly used for saving. + +### Wizards and Workflows + +Wizards and workflows have one characteristic in common: Entities are edited over a series of screens and over multiple sessions or unit of works. The entity is valid before the editing process starts and after it has ended, but the data is normally not valid in between. + +:information_source: Regardless of the actual type of application, the scope of a unit of work has to be considered carefully. All changes to a database should be part of unit of work. In web applications this is obviously a single request. Rich clients (Win32, Windows Forms or WPF applications) have better control over the application lifecycle, but they do also profit from carefully defined units of work. + +To support this type of operations, the entity in question must be exempted from synchronization with the database record before its state is valid again. This also allows to save all changes from the workflow in a single operation, keeping the entity consistent. + +This operation is called **detaching**. After it has been loaded with `Entity.Find()`, the object can be detached by calling `scope.Evict(entity)`. The entity will be removed from the session cache which is responsible for determining whether an entity is changed and for synchronizing entity state. + +The detached object must be saved internally by the application, for example in the `HttpSession`-cache. During the process, parts of the object are changed and it may become temporary invalid by these changes. After the process has completed, the object can be reattached to another session by calling `entity.Save()` or `entity.Update()`. The changes will be written back to the database when the session flushes. + +### Concurrent Editing + +A scenario that only few programmers plan with but all should be prepared to, is concurrent access to entities. There are but a few applications that are used by only a single user. Most applications are used by tens, hundreds, thousands or even more users concurrently. + +The consequence is that the developer has to prepare for an entity being edited from multiple users at the same time. These preparations include choosing a locking strategy and a strategy from recovering from concurrent change. + +:information_source: Theoretically, one can choose to use pessimistic or optimistic locking. When locking pessimistically, all changes to a record are disallowed when it is edited by another user already. Optimistic locking uses version numbers or time stamps to detect changes to a record before saving. That means that the object can be edited by multiple users simultaneously, but the users are only allowed to save the record when it has not changed in between by other users. ActiveRecord supports pessimistic locking by using serializable transactions, although it is most often not suitable to the application. Application types that are inherently stateless, like web applications should never use pessimistic locking. Optimistic locking can be implemented with special fields in the ActiveRecord classes. See the section on using Version and Timestamp attributes for further information. + +This scenario resembles CRUD, but it is now assumed that an optimistic locking strategy is used and that there are multiple users editing an entity simultaneously. In this example, both John and Paul work with the same entity. + +Paul opens the page for editing and walks away from his workplace to fetch coffee. John opens the entity meanwhile and edits it. He saves his changes while Paul is talking to a coworker with the coffee in his hand. + +Now Paul returns. His page still displays the old values before John's changes. Paul edits the entity and wants to save it. Now, what should happen. Paul made his changes based on an outdated version of the entity. If this is not checked, Johns changes are completely lost, even if Paul edited only data that John didn't change. + +The problem in this case is that Paul's object is stale. ActiveRecord can detect the stale state by using a version number or a timestamp property within the entity. If for example a version number had been used, ActiveRecord would have tried to overwrite only the record with Paul's primary key **and** version number. The object would have not been updated and the database would have reported a row count of 0. ActiveRecord would have known by this that the object is in a stale state and have thrown an exception. + +ActiveRecord guards against overwriting changes in this case, but it doesn't provide any measures to recover from the situation. The client code has to decide itself how the situation must be handled. A common strategy is to show the user both the updated and his own version and let him decide which information to save. + +## Save, Create and Update + +### To Save Or Not To Save + +Many users believe that `Update()` has to be used to notify ActiveRecord that an entity has changed. This is only partly true. When ActiveRecord is used without a `SessionScope` changes must be saved by calling `Save()` or `Update()`. This is no contradiction to the state model above but rather a special case. When no scope is used, each session spans only the actual call. Consider the following code: + +```csharp +Blog blog = Blog.FindFirst(); +blog.Name = "new Name"; +blog.Save(); +``` + +Immediately after the blog object has been created, the session ends. The object is now de-facto detached. After the change, `Save()` is called. The object is attached to a new session and immediately flushed because the session ends before the call to `Save()` returns. + +If a `SessionScope` or a `TransactionScope` is used, the situation is different: + +```csharp +using (new SessionScope()) +{ + Blog blog = Blog.FindFirst(); + blog.Name = "new Name"; + // blog.Save(); // Unnecessary +} +``` + +Now the object is attached to a session after being created, that won't end until the scope is disposed. Because of this, the object is flushed without a call to `Save()` or `Update()`. + +`SessionScope`s are sometimes used implicitly, for example in MonoRail web applications with the recommended session-per-request-pattern. The session scope is then created transparently at the begin of the web request and disposed immediately before the response is sent to the user. + +Since both type of scopes can be nested, it is recommended to always use scopes. This guarantees that the behaviour of for example services that use ActiveRecord is always consistent, no matter whether used from a batch job or from a web application. + +### When to call Save or Create/Update + +The `Create()` and `Update()` methods are used when an entity has a natural primary key. In this case, ActiveRecord cannot infer whether to `INSERT` or `UPDATE` by looking at the primary key of the entity, thus it has to be told explicitly. If surrogate keys are used, ActiveRecord knows by looking at the key whether the entity exists at the database. In this case, `Save()` is sufficient to persist a transient entity. \ No newline at end of file diff --git a/docs/primary-key-mapping.md b/docs/primary-key-mapping.md new file mode 100644 index 0000000..8a54797 --- /dev/null +++ b/docs/primary-key-mapping.md @@ -0,0 +1,345 @@ +# Primary Key Mapping + +Regular ActiveRecord types must have a primary key, a key that uniquely indentifies any row in a table. Single surrogate keys are favoured over composite keys, but both are supported. When having control over the database schema, adding a surrogate primary key to the tables is the recommended way of implementing primary keys. + +## Single Primary Key + +A single primary key is a column used as a row identifier. If it has no business meaning and can be chosen freely among unused values, the key is called a **surrogate primary key**. Primary keys can be assigned by the using code, auto generated by the database or by ActiveRecord using one of the strategies to generated non duplicate values. + +To declare a primary key in a class a property to hold it must be created and decorated with the PrimaryKeyAttribute. This attribute holds information for mapping the primary key column. Most importantly, it determines the generation strategy which defaults to native, using an auto generation method supported by the used database. The following table script shows a simple entity: + +```sql +CREATE TABLE Entity ( + [id] [int] IDENTITY (1, 1) NOT NULL + -- payload ommitted for clarity +) ON [PRIMARY] +``` + +This table would be easily mapped to an ActiveRecord class: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord] +public class Entity : ActiveRecordBase +{ + private int id; + + [PrimaryKey(PrimaryKeyType.Native)] + private int Id + { + get { return id; } + set { id = value; } + } + + // payload ommitted for clarity +} +``` + +For this case, the `PrimaryKeyType` could be omitted as it will default to `Native` anyway. ActiveRecord will correctly **assume** that the column name is Id. If the column had a different name, for example EntityId, it must be explicitly specified: + +```csharp + private int id; + + [PrimaryKey(PrimaryKeyType.Native, "EntityId")] + private int Id + { + get { return id; } + set { id = value; } + } +``` + +A setter is not needed for the primary key, but ActiveRecord needs to set the value somehow. For example the key can be directly set by using the backing field. This requires that the `Access` is specified: + +```csharp + private int id; + + [PrimaryKey(Access=PropertyAccess.FieldCamelcase)] + private int Id + { + get { return id; } + } +``` + +### Key Generation strategies + +ActiveRecord supports all strategies implemented by NHibernate for creating primary keys. The possible strategies are listed below with a short explanation. For some more details please refer to [this posting](http://stackoverflow.com/questions/575069/castle-activerecord-seeding-primary-key-value/575469#575469). + +Name | Enum Value | Description +---- | ---------- | ----------- +Identity | PrimaryKeyType.Identity | Uses an identity column if available. Objects will be instantly inserted when saved. +Sequence | PrimaryKeyType.Sequence | Uses sequences where supported by the database. Two database calls are necessary for saving an object. The sequence name can be specified with the sequence-parameter or the SequenceName- property. +Hi/Lo | PrimaryKeyType.HiLo | Uses a Hi/Lo algorithm. Hi values are stored in a special database table. A hi value is fetched once and incremented locally. table, column and max_lo can be specified as parameters, with max_lo specifying the number of values locally incremented before fetching a new high value. +Sequence Hi/Lo | PrimaryKeyType.SeqHiLo | Uses a Hi/Lo algorithm. The hi values are fetched from an Oracle-style sequence and incremented locally. Parameters are sequence and max_lo. +UUID (hex representation) | PrimaryKeyType.UuidHex | This strategy uses a guid and converts it into a hex representation, creating a readable string, customized by format and separator specification. This allows to use guids on databases that do not support GUIDs. format and separator must be specified as parameters. +UUID (compact representation) | PrimaryKeyType.UuidString | This strategy uses a guid and converts it into a byte-array, casting it into a string. It is written to the database as a CHAR(16), using up less space than UuidHex, but creating unprintable representations. +GUID | PrimaryKeyType.Guid | Uses GUIDs where supported by the database. +Combined GUID | PrimaryKeyType.GuidComb | Uses GUIDs where supported by the database. The GUIDs are created by taking system time into account. The risk for a key collision is therefore slightly higher, though still neglectable. The resulting GUIDs are already sorted after creation, increasing database performance. +Increment | PrimaryKeyType.Increment | Increments values locally. This is neither safe in clusters nor in applications with multiple clients. +User Assigned | PrimaryKeyType.Assigned | The key has a business meaning and is not generated but must be assigned by the user before Create() or Update() is called. + +Some strategies require more parameters, which can be specified by using the `Params` property. The parameters needed are specified as `name=value`-pairs separated by commas as shown in the following example: + +```csharp + private String id; + + [PrimaryKey(PrimaryKeyType.UuidHex, Params="format=D,seperator=-")] + public String Id + { + get { return id; } + set { id = value; } + } +``` + +### The Identity Problem + +Despite being the default generator, using `IDENTITY` keys is discouraged. The reason for this is an exception to the regular lifecycle when identity keys are used. + +When an entity is saved, a primary key must be assigned. However, the only possibility to determine a key using a database assigned identity value is inserting a row into a table. As a consequence, the entity is instantly saved to the database outside of any coordinated database flush. + +This is not perceived in the code unless a `SessionScope` is used which is mandatory for lazy loading and a common pattern in web applications. Since the scope can be defined far away from the code that actually performs the call to `Save()`, this code behaves differently based on the context it is executed in. This is a maintenance nightmare which should be avoided by using other key generation strategies. Also `IDENTITY` generates an overhead of database calls, as described in [this blog article](http://fabiomaulo.blogspot.com/2009/02/nh210-generators-behavior-explained.html). + +### Further Information + +Please refer to the Reference Manual's Attributes article for further information. + +## Composite Primary Keys + +Composite keys, also known as natural keys, consist of a set of columns that define the identifier of a row. + +:information_source: Composite keys are highly discouraged and should not be used unless there is no other alternative. + +To use composite keys with ActiveRecord two things are necessary: + +1. Creating a class to hold the properties and fields for the columns that make up the key. + * The class must be `Serializable` + * `Equals` and `GetHashCode` must be overridden +1. Declaring the property on the ActiveRecord type, using the `CompositeKeyAttribute`. + +This is shown for the following table script: + +```sql +CREATE TABLE Users ( + [OrgID] [int] NOT NULL, + [UserID] [int] NOT NULL, + [Name] [varchar] (50) NULL, + [Address] [varchar] (50) NULL, + [City] [varchar] (50) NULL, + [State] [varchar] (50) NULL +) ON [PRIMARY] +``` + +The following is the definition of the composite key class `ProductSupplierKey` and next is the ActiveRecord type: + +```csharp +using Castle.ActiveRecord; + +[Serializable] +public class UserKey +{ + private int orgID; + private int userID; + + [KeyProperty] + public int OrgID + { + get { return orgID; } + set { orgID = value; } + } + + [KeyProperty] + public int UserID + { + get { return userID; } + set { userID = value; } + } + + public override int GetHashCode() + { + return orgID ^ userID; + } + + public override bool Equals(object obj) + { + if (this == obj) + { + return true; + } + UserKey key = obj as UserKey; + if (key == null) + { + return false; + } + if (orgID != key.orgID || userID != key.userID) + { + return false; + } + return true; + } +} + +[ActiveRecord("Users")] +public class User : ActiveRecordBase +{ + private UserKey key; + + [CompositeKey] + public UserKey Key + { + get { return key; } + set { key = value; } + } +} +``` + +:warning: **Warning:** There are implications on using composite keys discussed below. As was mentioned above, Composite keys are discouraged. However, if they must be used, additional complexity in how the model is mapped to the database must be addressed. + +### Implications of using composite keys + +An assigned identifier (like all CompositeKeys and assigned single PrimaryKeys) cannot be used to determine whether an instance is detached or transient - since its value is assigned by the application, it is never null. Therefore, one of the strategies below must be used or NHibernate will misbehave around the way it persists the instance to the database. + +To ensure that the data is persisted properly, two methods for managing persistence are available: + +1. Using the `VersionAttribute` to set the `UnsavedValue`. Normally, the `UnsavedValue` is used with the `PrimaryKeyAttribute`, where the `UnsavedValue` is checked by NHibernate to determine the state of the instance: if the field or property is equal to the `UnsavedValue`, then the object has not yet been persisted. However, because the field or property marked with the `CompositeKeyAttribute` cannot have an `UnsavedValue` that is understood by NHibernate, another field or property must be used - the one that was marked by the `VersionAttribute`. This allows the use of the `Save()` method. +1. Not using the `Save()` method. `Create` and `Update` can be used to force NHibernate to correctly persisting the objects. + +### Relations with composite keys + +Because a composite key is by nature multi-field, there are additional requirements when building the relations between objects that include these keys. The largest part of those requirements is that the `HasMany`, `BelongsTo` and `HasAndBelongsToMany` attributes will use different properties to determine the `Column`s, `ColumnKeys` and `ColumnKeyRefs`. + +To continue the example used above, the User class will be redefined and Org and Group classes added including their relationships. + +First, some DDL is necessary to create the Org and Group tables, as well as the association table for the many-to-many relationship between `Users` and `Groups`. + +```sql +CREATE TABLE Orgs ( + [ID] [int] NOT NULL, + [Name] [varchar] (50) NULL +) ON [PRIMARY] + +CREATE TABLE Groups ( + [ID] [int] NOT NULL, + [Name] [varchar] (50) NULL +) ON [PRIMARY] + +CREATE TABLE UserGroups ( + [OrgID] [int] NOT NULL, + [UserID] [int] NOT NULL, + [GroupID] [int] NOT NULL +) ON [PRIMARY] +``` + +Next, the `User` class will be redefined, adding the appropriate markup to maintain the mapping relationships. + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("Users")] +public class User : ActiveRecordBase +{ + private UserKey key; + private ISet groups; + private Org org; + + public User() + { + groups = new HybridSet(); + } + + public User(UserKey userKey) : this() + { + key = userKey; + } + + [CompositeKey] + public UserKey Key + { + get { return key; } + set { key = value; } + } + + [HasAndBelongsToMany(typeof(Group), + Table="UserGroups", + ColumnRef="GroupID", + CompositeKeyColumnKeys=new string[]{"OrgID","UserID"}, + Lazy=true, + Cascade=ManyRelationCascadeEnum.SaveUpdate) + public ISet Groups + { + get { return groups; } + } + + [BelongsTo("OrgID", Insert=false, Update=false) + public Org Org + { + get { return org; } + set { org = value; } + } +} +``` + +Note the `CompositeKeyColumnKeys` array, these are the fields that make up the foreign composite key in the association table. + +Another interesting item when dealing with composite keys is building a relationship using only a single field of the composite key as the foreign key in a traditional one-to-many or many-to-one relationship. The "one" side of that mapping in the definition of the "Org" property has set `Insert/Update` to `false`. This is **not** optional. It prevents the other side of the relation from attempting to insert or update a portion of the composite key (in this case, the "OrgID" field). + +Next step is defining the other two classes, illustrating the other side of the relationships. + +```csharp +[ActiveRecord("Orgs") +public class Org : ActiveRecordBase +{ + private int id; + private ISet users; + + public Org() + { + users = new HybridSet(); + } + + [PrimaryKey(PrimaryKeyType.Native)] + public int ID + { + get { return id; } + set { id = value; } + } + + [HasMany(typeof(User), Lazy=true) + public ISet Users + { + get { return users; } + } +} + +[ActiveRecord("Groups")] +public class Group : ActiveRecordBase +{ + private int id; + private ISet users; + + public Group() + { + users = new HybridSet(); + } + + [PrimaryKey(PrimaryKeyType.Native)] + public int ID + { + get { return id; } + set { id = value; } + } + + [HasAndBelongsToMany(typeof(User), + Table="UserGroups", + CompositeKeyColumnRefs=new string[]{"OrgID","UserID"}, + ColumnKey="GroupID", + Lazy=true, + Inverse=true, + Cascade=ManyRelationCascadeEnum.SaveUpdate) + public ISet Users + { + get { return users; } + } +} +``` + +On the side of the relationship **without** the composite key, an array of column refs is used, while on the side of the relationship **with** the composite key, it is an array of column keys. \ No newline at end of file diff --git a/docs/relations-mapping.md b/docs/relations-mapping.md new file mode 100644 index 0000000..68a0f68 --- /dev/null +++ b/docs/relations-mapping.md @@ -0,0 +1,606 @@ +# Relations Mapping + +Relations is what we call the association among types or with themselves. + +Many-to-one associations are described using `BelongsToAttribute`. One-to-many uses `HasManyAttribute`. Many-to-many uses `HasAndBelongsToManyAttribute`. + +## BelongsTo + +A many-to-one relation can be mapped using the `BelongsToAttribute`. + +Consider the following table script: + +```sql +CREATE TABLE Blogs +( + [id] [int] IDENTITY (1, 1) NOT NULL, + [name] [varchar] (50) NULL +) ON [PRIMARY] + +CREATE TABLE Posts +( + [id] [int] IDENTITY (1, 1) NOT NULL, + [title] [varchar] (50) NULL, + [contents] [text] NULL, + [blogid] [int] NULL +) ON [PRIMARY] +``` + +The blogid on `Posts` is clear a foreign key to the `Blogs` table. You can map the reference on the `Post` to a `Blog`, and to do it you can use `BelongsToAttribute`: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("posts")] +public class Post : ActiveRecordBase +{ + private int id; + private string title; + private string contents; + private Blog blog; + + [PrimaryKey] + public int Id + { + get { return id; } + set { id = value; } + } + + [Property] + public string Title + { + get { return title; } + set { title = value; } + } + + [Property(ColumnType="StringClob")] + public string Contents + { + get { return contents; } + set { contents = value; } + } + + [BelongsTo("blogid")] + public Blog OwnerBlog + { + get { return blog; } + set { blog = value; } + } +} +``` + +Assigning a blog instance to this property - and obviously saving the post instance - will create the association. + +More information on the attribute can be found at Attributes article. + +## HasMany + +An one-to-many relation can be mapped using the `HasManyAttribute` + +Consider the following table script: + +```sql +CREATE TABLE Blogs +( + [id] [int] IDENTITY (1, 1) NOT NULL, + [name] [varchar] (50) NULL +) ON [PRIMARY] + +CREATE TABLE Posts +( + [id] [int] IDENTITY (1, 1) NOT NULL, + [title] [varchar] (50) NULL, + [contents] [text] NULL, + [blogid] [int] NULL +) ON [PRIMARY] +``` + +The blogid on `Posts` is clearly a foreign key to the `Blogs` table. You can make the `Blog` class have a set of `Posts` using the `HasManyAttribute`: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("blogs")] +public class Blog : ActiveRecordBase +{ + private int id; + private string name; + private IList posts = new ArrayList(); + + [PrimaryKey] + private int Id + { + get { return id; } + set { id = value; } + } + + [Property] + public string Name + { + get { return name; } + set { name = value; } + } + + [HasMany(typeof(Post), Table="Posts", ColumnKey="blogid")] + public IList Posts + { + get { return posts; } + set { posts = value; } + } +} +``` + +:information_source: If the other side of the relation (the `Post` class) had a `BelongsTo` relation to the `Blog` class, the you could omit the `Table` and `ColumnKey` properties. + +Now we can use the newly added relation: + +```csharp +Blog blog = new Blog(); +blog.Name = "hammett's blog"; +blog.Create(); + +Post post = new Post(); +post.Title = "First post"; +post.Contents = "Hello world"; +post.Create(); + +blog.Posts.Add(post); +blog.Update(); +``` + +By default this kind of relation is writable. You can control the behavior using the `Cascade` property on `HasManyAttribute`. You can also turn off the writable behavior by saying that the relation is only controlled by the other side. You can do this using the `Inverse` property. For example: + +```csharp +[HasMany(typeof(Post), Table="Posts", ColumnKey="blogid", Inverse=true)] +public IList Posts +{ + get { return posts; } + set { posts = value; } +} +``` + +More information on the attribute can be found at Attributes article. For an explanation of the `Inverse concept`, please refer to this [article at nhprof.com](http://nhprof.com/Learn/Alert?name=SuperfluousManyToOneUpdate). + +## HasAndBelongsToMany + +A many-to-many relation can be mapped using the `HasAndBelongsToMany`. As usual it requires an association table. + +Consider the following table script: + +```sql +CREATE TABLE Posts +( + [id] [int] IDENTITY (1, 1) NOT NULL, + [title] [varchar] (50) NULL, + [contents] [text] NULL +) ON [PRIMARY] + +CREATE TABLE Categories +( + [id] [int] IDENTITY (1, 1) NOT NULL, + [title] [varchar] (50) NULL, +) ON [PRIMARY] + +CREATE TABLE PostCategory +( + [postid] [int] NOT NULL, + [categoryid] [int] NOT NULL +) ON [PRIMARY] +``` + +The relation is that a post can have many categories and thus a category can be related to many posts. + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("posts")] +public class Post : ActiveRecordBase +{ + private int id; + private string title; + private string contents; + private Blog blog; + private IList categories = new ArrayList(); + + [PrimaryKey] + private int Id + { + get { return id; } + set { id = value; } + } + + [Property] + public string Title + { + get { return title; } + set { title = value; } + } + + [Property(ColumnType="StringClob")] + public string Contents + { + get { return contents; } + set { contents = value; } + } + + [BelongsTo("blogid")] + public Blog OwnerBlog + { + get { return blog; } + set { blog = value; } + } + + [HasAndBelongsToMany(typeof(Category), + Table="PostCategory", ColumnKey="postid", ColumnRef="categoryid")] + public IList Categories + { + get { return categories; } + set { categories = value; } + } +} +``` + +The other side of the relation can be mapped identically. The only change is the inversion of `ColumnKey` and `ColumnRef`. It is wise to choose one side of the relation as the owner. The other side, the non-writable, need to use `Inverse=true`. + +In the example above it would be semantically correct to have the `Post` class controlling the relation. The other side, `Category`, can optionally have a list of `Posts`, and will use `Inverse=true`. + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("categories")] +public class Category : ActiveRecordBase +{ + private int id; + private string title; + private IList posts = new ArrayList(); + + [PrimaryKey] + private int Id + { + get { return id; } + set { id = value; } + } + + [Property] + public string Title + { + get { return title; } + set { title = value; } + } + + [HasAndBelongsToMany(typeof(Post), + Table="PostCategory", ColumnKey="categoryid", ColumnRef="postid", Inverse=true)] + public IList Posts + { + get { return posts; } + set { posts = value; } + } +} +``` + +:information_source: We cannot stress enough how important it is to define a proper Cascade behavior for your relations in a real world application. + +More information on the attribute can be found at Attributes article. + +## Attributes on the association table + +More than often the association table in a many-to-many relation is used to hold association attributes. In the example used so far, the `PostCategory` could have some columns to hold some arbitrary data. + +It is desirable then to map this relation correctly to a class that represents the association table. So create a `PostCategory` ActiveRecord type. Now comes the trick part: ActiveRecord classes must have primary keys. So you have two options. Either you add a surrogate key to your association table or you use composite key. + +The current support for composite keys does not support relations as the keys, although this is supported by NHibernate. Nevertheless this is on the Roapmap and should be implemented by the next version. + +### Association table with surrogate key + +On this approach we introduce a primary key in a table where, semantically, the key could be the the two foreign keys. + +```sql +CREATE TABLE PostCategory +( + [id] [int] IDENTITY (1, 1) NOT NULL, + [postid] [int] NOT NULL, + [categoryid] [int] NOT NULL, + [arbitraryvalue] [int] NULL +) ON [PRIMARY] +``` + +Now it is just a matter of implementing the class as you normally would. + +```csharp +using Castle.ActiveRecord; +using NHibernate.Expression; + +[ActiveRecord] +public class PostCategory : ActiveRecordBase +{ + private int id; + private Post post; + private Category category; + private int arbitraryvalue; + + [PrimaryKey] + private int Id + { + get { return id; } + set { id = value; } + } + + [BelongsTo("postid")] + public Post Post + { + get { return post; } + set { post = value; } + } + + [BelongsTo("categoryid")] + public Category Category + { + get { return category; } + set { category = value; } + } + + [Property] + public int ArbitraryValue + { + get { return arbitraryvalue; } + set { arbitraryvalue = value; } + } + + public static PostCategory[] FindByPost(Post post) + { + return FindAll(typeof(PostCategory), Expression.Eq("Post", post)); + } + + public static PostCategory[] FindByCategory(Category category) + { + return FindAll(typeof(PostCategory), Expression.Eq("Category", category)); + } +} +``` + +As you see we introduced find methods to allow the retrival of instances based on an specific post or category. + +### Using a composite key + +The composite key approach does not require the introduction of a surrogate key, but requires more work and can be used with relations (yet). As you can see the post and category are represent by their ids instead of `Post` and `Category` instance. This is highly undesirable for object oriented domain models. + +```csharp +using Castle.ActiveRecord; +using NHibernate.Expression; + +[Serializable] +public class PostCategoryKey +{ + private int postid; + private int categoryid; + + [KeyProperty] + public int PostId + { + get { return postid; } + set { postid = value; } + } + + [KeyProperty] + public int CategoryId + { + get { return categoryid; } + set { categoryid = value; } + } + + public override int GetHashCode() + { + return postid ^ categoryid; + } + + public override bool Equals(object obj) + { + if (this == obj) + { + return true; + } + PostCategoryKey key = obj as PostCategoryKey; + if (key == null) + { + return false; + } + if (postid != key.postid || categoryid != key.categoryid) + { + return false; + } + return true; + } +} + +[ActiveRecord] +public class PostCategory : ActiveRecordBase +{ + private PostCategoryKey id; + private int arbitraryvalue; + + [CompositeKey] + public PostCategoryKey Id + { + get { return id; } + set { id = value; } + } + + [Property] + public int ArbitraryValue + { + get { return arbitraryvalue; } + set { arbitraryvalue = value; } + } + + public static PostCategory[] FindByPost(Post post) + { + return FindAll(typeof(PostCategory), + Expression.Eq("PostCategory_postid", post.Id)); + } + + public static PostCategory[] FindByCategory(Category category) + { + return FindAll(typeof(PostCategory), + Expression.Eq("PostCategory_categoryid", category.Id)); + } +} +``` + +## OneToOne + +The one-to-one implemented by NHibernate is often misunderstood. It is used for classes that share primary keys and useful when you have some kind of Class Table Inheritance. + +The primary key of the table which isn't autogenerated must be declared using PrimaryKeyType.Foreign: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("Customer")] +public class Customer : ActiveRecordBase +{ + private int custID; + private string name; + private CustomerAddress addr; + + [PrimaryKey] + public int CustomerID + { + get { return custID; } + set { custID = value; } + } + + [OneToOne] + public CustomerAddress CustomerAddress + { + get { return addr; } + set { addr = value; } + } + + [Property] + public string Name + { + get { return name; } + set { name = value; } + } +} + +[ActiveRecord("CustomerAddress")] +public class CustomerAddress : ActiveRecordBase +{ + private int custID; + private string address; + private Customer cust; + + [PrimaryKey(PrimaryKeyType.Foreign)] + public int CustomerID + { + get { return custID; } + set { custID = value; } + } + + [OneToOne] + public Customer Customer + { + get { return cust; } + set { cust = value; } + } + + [Property] + public string Address + { + get { return addr; } + set { addr = value; } + } +} +``` + +You should read more about it on NHibernate documentation. + +More information on the attribute can be found at Attributes article. + +## Any and HasManyToAny + +There are certain cases when you need to make an association from an entity to a range of possible objects that doesn't necessarily share a common base class. + +:warning: **Warning:** This is a fairly advanced scenario. Try to find a simpler solution if you can. + +### Using Any + +A simple example may be a payment method in an `Order` class, where the choices are either a bank account or a credit card, like this: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("CreditCards")] +public class CreditCard : ActiveRecordBase, IPaymentMethod +{ ... } + +[ActiveRecord("BankAccounts")] +public class BankAccount : ActiveRecordBase, IPaymentMethod +{ ... } +``` + +A possible `Order` class does not know in advance the payment map, or how to map them. They are not part of any hierarchy (either in the object model or an ActiveRecord one). The solution is to map them to this schema: + +```sql +CREATE TABLE Orders +( + [Id] [int] not null identity(1,1), + ... + [Billing_Details_Id] [int] not null, + [Billing_Details_Type] [nvarchar] not null +) +``` + +Together `BillingDetailsId` and `BillingDetailsType` points to the correct account or credit card that should pay for the order. Here is the attributes declarations. Note that unlike most other attributes, here you need to specify a few properties. They cannot be infered. + +```csharp +[Any(typeof(int), MetaType=typeof(string), + TypeColumn="Billing_Details_Type", + IdColumn="Billing_Details_Id", + Cascade=CascadeEnum.SaveUpdate)] +[Any.MetaValue("CREDIT_CARD", typeof(CreditCard))] +[Any.MetaValue("BANK_ACCOUNT", typeof(BankAccount))] +public IPaymentMethod PaymentMethod +{ + get { ... } + set { ... } +} +``` + +The first parameter is the type of the Id column (in this case `BillingDetailsId`), the second is the `MetaType` definition, which in this case mean the type of the the field that defines the type of the id. + +Next we have the `TypeColumn` and `IdColumn`, which match `Billing_Details_Type` and `Billing_Details_Id`. + +The interesting part is the `Any.MetaValue` attribute. Here, we define that when the value in the `Billing_Details_Type` column is "`CREDIT_CARD`", the value in the `Billing_Details_Id` column is the primary key of a `CreditCard`, and when the `Billing_Details_Type` is "`BANK_ACCOUNT`", then the value in `Billing_Details_Id` should be interpreted as the primary key of a `BankAccount` class. +Quick Note + +The type of the property should be of a common type or interface that all the possible objects share (worst case scenario: make it of type `System.Object`). + +### Using HasManyToAny + +A natural extention of `Any`, the `HasManyToAnyAttribute` provides the same functionality for collections. Here is an example of a class that needs a set of payment methods: + +```csharp +[HasManyToAny(typeof(IPayment), "pay_id", "payments_table", typeof(int), + "Billing_Details_Type", "Billing_Details_Id", MetaType=typeof(string))] +[Any.MetaValue("CREDIT_CARD", typeof(CreditCard))] +[Any.MetaValue("BANK_ACCOUNT", typeof(BankAccount))] +public ISet PaymentMethod +{ + get { ... } + set { ... } +} +``` + +The parameters for `HasManyToAny` are (in order of apperances in the constructor): + +* `typeof(IPayment)`: the type of the objects in this collection +* `pay_id`: the key column that maps the values in this collection to this object +* `payment_table`: the table for this collection +* `typeof(int)`: the type of the id column - identical to the first parameter of `Any` +* `Billing_Details_Type`: identical in function to the `Billing_Details_Type` mentioned above +* `Billing_Details_Id`: identical to the `{Billing_Details_Id` mentioned above +* `MetaType=typeof(string)`: the type of the type column / identical to the one described above + +More information on the attribute can be found at Attributes article. \ No newline at end of file diff --git a/docs/schema-generation.md b/docs/schema-generation.md new file mode 100644 index 0000000..e7ff5e2 --- /dev/null +++ b/docs/schema-generation.md @@ -0,0 +1,73 @@ +# Schema Generation + +Creating the DDL script and keeping it up-to-date with the code is a time consuming task. There are a number of different ways to do this, and every every developer has their own preferences. Companies tend to work according to their own specific standards. + +Castle ActiveRecord exposes a NHibernate feature to create and drop schemas. The relevant methods are: + +* `CreateSchema` and `DropSchema` will create and drop the schema against the database +* `GenerateCreationScripts` and `GenerateDropScripts` generate a file with the scripts +* `CreateSchemaFromFile` executes a DDL script against the database + +All of the above methods are exposed by the ActiveRecordStarter class and can only be used after you have invoked one of the Initialize method overloads. + +## Allowing ActiveRecord generate the schema + +Allowing ActiveRecord generate and execute the creation schema is desirable in certain scenarios. For example, to prototype an application or to build a test database. + +If you want to use this approach you will need to specify some additional properties on your active record attributes. These additional properties, which include NotNull and Length, are used to guide the schema generation. For more information see Attributes article. + +:warning: **Warning:** Be aware that the schema generation is not bullet proof. For some complex models the generated script might be buggy. For 95% of the cases it is acceptable. + +Consider the following ActiveRecord type definition: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("Blogs")] +public class Blog : ActiveRecordBase +{ + private int id; + private String name; + private String author; + + [PrimaryKey(PrimaryKeyType.Native, "blog_id")] + private int Id + { + get { return id; } + set { id = value; } + } + + [Property("blog_name", NotNull=true, Length=25)] + public String Name + { + get { return name; } + set { name = value; } + } + + [Property("blog_author", NotNull=true, Length=50)] + public String Author + { + get { return author; } + set { author = value; } + } +} +``` + +The following code will initialize the framework and generate the schema: + +```csharp +ActiveRecordStarter.Initialize(new XmlConfigurationSource("appconfig.xml"), typeof(Blog)); +ActiveRecordStarter.CreateSchema(); +``` + +:information_source: The CreateSchema method will drop any existing tables with the same name before it creates new tables. Be very cautious about performing this operation on a production database. + +### Executing an external script file + +This may be a better alternative, especially if you work on team that has someone dedicated to keep the database schema up to date. The SQL files can be part of the project and can be executed during initialization. + +```csharp +ActiveRecordStarter.Initialize(new XmlConfigurationSource("appconfig.xml"), typeof(Blog) ); +ActiveRecordStarter.CreateSchemaFromFile("myscript1.sql"); +ActiveRecordStarter.CreateSchemaFromFile("myscript2.sql"); +``` \ No newline at end of file diff --git a/docs/simple-column-mapping.md b/docs/simple-column-mapping.md new file mode 100644 index 0000000..af57c39 --- /dev/null +++ b/docs/simple-column-mapping.md @@ -0,0 +1,146 @@ +# Simple Column Mapping + +You can map ordinary columns to properties or to fields directly. + +## Mapping columns to properties + +The simplest form to map a property declared on your class to a column in the database table is to use `PropertyAttribute` without parameters. ActiveRecord will **assume** that the column name is the same as the property name. For example, given the following table script: + +```sql +CREATE TABLE Entity ( + [id] [int] IDENTITY (1, 1) NOT NULL, + [name] [varchar] (20) NULL +) ON [PRIMARY] +``` + +The mapping for this class would be (including the [Primary Key Mapping|primary key]): + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord] +public class Entity : ActiveRecordBase +{ + private int id; + private string name; + + [PrimaryKey(PrimaryKeyType.Native)] + private int Id + { + get { return id; } + set { id = value; } + } + + [Property] + public String Name + { + get { return name; } + set { name = value; } + } +} +``` + +If the column had a different name, for example `EntityName`, you could use: + +```csharp +private string name; + +[Property("name")] +public String Name +{ + get { return name; } + set { name = value; } +} +``` + +You do not need a setter for the primary key, but NHibernate needs to set the value somehow. You can then specify the `Access` for it, for example: + +```csharp +private string name; + +[Property(Access=PropertyAccess.FieldCamelcase)] +public String Name +{ + get { return name; } +} +``` + +If the property should be readonly, it is also possible to use a private setter. It is then not necessary to specify the `PropertyAccess` + +```csharp +private string name; + +[Property(Access=PropertyAccess.FieldCamelcase)] +public String Name +{ + get { return name; } + private set { name = value; } +} +``` + +Please refer to the Reference Manual's Attributes article for further information. + +## Mapping columns to fields + +Although property binding seems more natural, fields can also be mapped using the `FieldAttribute`. The field can be of any visibility. + +The usage is the same as `PropertyAttribute`. + +```csharp +[Field] +private string name; +``` + +Please refer to the Reference Manual's Attributes article for further information. + +## Large Objects + +If your table have columns for large content (text or binary), you must specify the column type using the property `ColumnType`, which is present on both `PropertyAttribute` and `FieldAttribute` + +Value to use | .NET type | Database type +------------ | --------- | ------------- +StringClob | string | Text +BinaryBlob | byte[] | Binary +Serializable | any object that implements serializable | Binary + +## Nullable Types + +As you know, value types in .net cannot assume a null value. Often however, columns in the database like ints or `DateTime` are nullable. + +Beginning with .Net Framework 2.0, support for nullable value types is available. ActiveRecord supports the nullable value types natively: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord] +public class Entity : ActiveRecordBase +{ + private int id; + private Int32? age; + private DateTime? created; + private Boolean? accepted; + + // omitted primary key + + [Property] + public Int32? Age + { + get { return age; } + set { age = value; } + } + + [Property] + public DateTime? CreatedAt + { + get { return created; } + set { created = value; } + } + + [Property] + public Boolean? Accepted + { + get { return accepted; } + set { accepted = value; } + } +} +``` \ No newline at end of file diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..3204663 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,75 @@ +# Troubleshooting + +Sometimes things go wrong. This section lists actions you may take to find out why a mapping is not being accepted, or why your query is not working. + +## Enabling ActiveRecord debug + +When you enable Castle ActiveRecord debug mode, it output the mapping files it produces for each entity. This might be valuable to identify problems, or to report problems to NHibernate team. + +For instructions on how to enable it check the [XML Configuration Reference](xml-configuration-reference.md). + +## Enabling NHibernate debug + +NHibernate uses log4net, so it's just a matter of configuring it. For example, to have a log.txt file with all debug information you can get, use: + +```xml + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +After that, make sure you are initializing the log4net config in your code by invoking its initializer. + +```csharp +log4net.Config.XmlConfigurator.Configure(); +``` + +Then, run your test cases and check the log file for Exceptions - yes, do a search. There will be plenty of information about what went wrong. Note that log.txt will be in the same directory as your EXE or Web application directory -- not necessarily where your .csproj file is. \ No newline at end of file diff --git a/docs/tuning.md b/docs/tuning.md new file mode 100644 index 0000000..1cff87e --- /dev/null +++ b/docs/tuning.md @@ -0,0 +1,101 @@ +# Tuning (Performance Improvements) + +This article explains some steps towards a better performance. + +## Too many selects (select n+1) + +:information_source: This is the most common reason for performance issues with OR/M based applications. Be sure to read this section and the relevant documentation in NHibernate's site thoroughfully. In nearly all cases, some thought and careful appliance of eager fetching can solve the issue. + +Using an O/RM can greatly simplify your life, but is has its on set of Gotcha that you need to be aware of. One of the more serious ones is the Select N + 1 issue. Let us start from the simple case, of the usual Blog -->> Posts model. Assuming that you have not enabled lazy loading, what would be the result of this line of code? + +```csharp +Blog[] blogs = Blog.FindAll(); +``` + +Well, we have a single select to grab all the blogs from the database. And then, for each blog a select to grab all its posts (and then a select per post to load its comments, etc). That is a problem usually refered as the Select N + 1 problem. Fortantely, we can easily solve this by marking the assoication from blog to posts and from post to comments as lazy. See the lazy load section and [Enabling lazy load|Enabling lazy load] for the details. + +Now, let us assume that all your collections are lazy loaded and that you have the following piece of code: + +```csharp +foreach (Post post in blog.Posts) +{ + foreach (Comment comment in post.Comments) + { + //print comment... + } +} +``` + +What is going on in here? We execute a SELECT to grab all the Posts in the blogs. Then, for each of the posts, we need to execute another select. We just got that issue again! But here we have another tool at our disposal, eager loading. + +## Lazy load + +One problem that you may encounter is that loading a single entity causes its entire object graph to be loaded with it. (You load a post, and it loads its blog, which loads all its posts, which load all of their comments, etc...). This issue is solved by using Lazy Loading, which means that NHibernate will only load the data that you are interested at. + +Enable lazy load for relations as described in the [Enabling lazy load|Enabling lazy load]. Lazy loading can be defined at the class level as well, using `ActiveRecord(Lazy=true)`. + +Marking a class as lazy means that you all its persistent properties must be virtual (so NHibernate is able to intercept and load the property when it is required) and all accesses (even inside the class) to properties must be done using the properties (and not the fields). + +:information_source: Lazy loading is only possible when you are inside a scope! + +Lazy loading is one of the more important tools in increasing the performance of an application, and like most performance related issues, it require testing in the context of the application to know what is the best solution for each scenario. + +## Overused Relations and eager loadings + +If your code does intensive work on relations, consider replacing the operation by an [HQL query|HQL query]. Databases are very optimized to handle process on a huge amount of data. + +Using HQL, you can specify the data that you want to load, saving database roundtrips. For instnace, here is how we load a post with all its comments: + +```csharp +from Post p join fetch p.Comments where p.Id = 1 +``` + +There are a couple of things that you need to know about eager loading (check the "Too many selects" section to see more details about using it effectively): + +* Eager loading is only avialable using HQL queries in Active Record +* You can eagerly load only a single collection (HasMany, HasManyAndBelongsToMany) assoication per query, but any number of entity (BelongsTo) assoications. + +Eager loading is used by using the fetch modifier to a join. In the above query, we saw that we can use it to load a collection eagerly. Now let us see how we can load a collection (HasMany) and an assoication (BelongsTo) eagerly: + +```csharp +from Post p + join fetch p.Comments + join fetch p.Blog + where p.Id = 1 +``` + +Remember that NHibernate is issuing a join to gather all this data, so eager loading many assoications is not recommended. Issue several seperted queries and let NHibernate sort the results into a coherent object graph. + +The query above will completely solve the Select N+1 issue for the code shown in the introduction. This efficently grab all the data from the database in a single query. + +In almost all cases, it is better to ask the database for the data as expclictly as possible, rather than loading the data on demand. The HQL langauge is can bring a lot of power into your hands. Consult the [NHibernate documentation on HQL](http://docs.jboss.org/hibernate/stable/core/reference/en/html/queryhql.html) for all the detials. + +## Profiling Active Record + +The easiest way to figure out what is going on with Active Record is to watch how it talks to the database. In a console application, all we need to do is to tell NHibernate that we would like it to show us the queries, by adding this to Active Record config section: + +```xml + +``` + +However, in a web (or WinForms for that matter) scenario, that is not very helpful. For that, we need to redirect NHibernate's logging to a useful place. Let's start by simply logging all the queries to the trace (which we can show as part of the page). Add the following to your web.config file: + +```xml + +
+ + + + + + + + + + + + + +``` + +Another very useful tool in this regard is [SQL Server Profiler](http://msdn.microsoft.com/en-us/library/ms173799.aspx), which can show you the queries executed on the server in real time. If you don't your SQL Server, you might consider using the commercial tool [NHibernate Profiler](http://nhprof.com/). If you want to profile the application itself, you can use a commercial profiling software like [dotTrace profiler](http://www.jetbrains.com/profiler/). \ No newline at end of file diff --git a/docs/type-hierarchy.md b/docs/type-hierarchy.md new file mode 100644 index 0000000..380ba7a --- /dev/null +++ b/docs/type-hierarchy.md @@ -0,0 +1,291 @@ +# Type Hierarchy + +In an object oriented world classes can be extended and as you descend down the hierarchy they become more specialized. For example, a Person has attributes shared by persons. A Customer class extends Person with specialized attributes that only customers have. + +It is also common to share behavior by using a common base class. For example, articles and blog posts can be distinct entities in a blog application. However, both receive comments and trackbacks. It would be a good idea if they extend from a base class that offers the "reviewable" behavior. + +In this section we present you with approaches to implement type hierarchies using Castle ActiveRecord. + +## Type hierarchy mapping patterns + +There are three common patterns for mapping type hierarchies to database tables. Martin Fowler uses the following names for these patterns: + +### Single Table Inheritance + +Single Table Inheritance from the database perspective uses a single table with a discriminator column to determine which type each row contains. The table must contain columns for all the attributes required by any subclasses. + +### Class Table Inheritance + +Class Table Inheritance involves using different tables for each class where the "base" table defines the primary key, and the others "inherit" it. + +### Concrete Table Inheritance + +Concrete Table Inheritance is a third way to map a class hierarchy, each concrete class has it's own database table. + +## Single Table Inheritance - Using a discriminator + +First of all consider the following table script: + +```sql +CREATE TABLE [companies] ( + [id] [int] IDENTITY (1, 1) NOT NULL , + [client_of] [int] NULL , + [name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL , + [type] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL +) ON [PRIMARY] + +CREATE TABLE [people] ( + [id] [int] IDENTITY (1, 1) NOT NULL , + [name] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL +) ON [PRIMARY] + +CREATE TABLE [people_companies] ( + [person_id] [int] NOT NULL , + [company_id] [int] NOT NULL +) ON [PRIMARY] +``` + +We want to represent three entities with the same Companies table (`Company`, `Firm`, `Client`). In order to achieve this, we use a discriminator column - in this case the column type. + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("companies", + DiscriminatorColumn="type", + DiscriminatorType="String", + DiscriminatorValue="company")] +public class Company : ActiveRecordBase +{ + private int id; + private String name; + private IList _people; + + public Company() + { + } + + public Company(string name) + { + this.name = name; + } + + [PrimaryKey] + private int Id + { + get { return id; } + set { id = value; } + } + + [Property] + public String Name + { + get { return name; } + set { name = value; } + } +} + +[ActiveRecord(DiscriminatorValue="firm")] +public class Firm : Company +{ + private IList clients; + + public Firm() + { + } + + public Firm(string name) : base(name) + { + } + + [HasMany(typeof(Client), ColumnKey="client_of")] + public IList Clients + { + get { return clients; } + set { clients = value; } + } +} + +[ActiveRecord(DiscriminatorValue="client")] +public class Client : Company +{ + private Firm firm; + + public Client() + { + } + + public Client(string name, Firm firm) : base(name) + { + this.firm = firm; + } + + [BelongsTo("client_of")] + public Firm Firm + { + get { return firm; } + set { firm = value; } + } +} +``` + +Each class can have its own `FindAll`, `DeleteAll`, only affecting its subset of records on the same table. + +:information_source: If you want to expose the discriminator column in your ActiveRecord type then you must turn off inserts and updates for that column. In other words use `Property(Insert=false, Update=false)`. This is required as otherwise NHibernate will control it, not your code. + +## Class Table Inheritance - Using joined subclasses + +With this approach we would have a specialized table for each subclass. Consider the following table script: + +```sql +CREATE TABLE [Entity] +( + [id] [int] IDENTITY (1, 1) NOT NULL , + [name] [varchar] (50) NULL , + [type] [varchar] (10) NULL +) ON [PRIMARY] + +CREATE TABLE [EntityCompany] +( + [comp_id] [int] NOT NULL , + [company_type] [tinyint] NOT NULL +) ON [PRIMARY] + +CREATE TABLE [EntityPerson] +( + [person_id] [int] NOT NULL , + [email] [varchar] (50) NULL , + [phone] [varchar] (12) NULL +) ON [PRIMARY] +``` + +The `Entity` table holds the primary key that is auto generated. Both `EntityCompany` and `EntityPerson` have primary keys as well. But they are foreign keys to the primary key on the Entity table. + +The base class is no different from ordinary ActiveRecord types. The only difference is the introduction of the `JoinedBaseAttribute` used to mark the base class in a joined base approach. + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("entity"), JoinedBase] +public class Entity : ActiveRecordBase +{ + private int id; + private string name; + private string type; + + public Entity() + { + } + + [PrimaryKey] + private int Id + { + get { return id; } + set { id = value; } + } + + [Property] + public string Name + { + get { return name; } + set { name = value; } + } + + [Property] + public string Type + { + get { return type; } + set { type = value; } + } + + public static void DeleteAll() + { + DeleteAll(typeof(Entity)); + } + + public static Entity[] FindAll() + { + return (Entity[]) FindAll(typeof(Entity)); + } + + public static Entity Find(int id) + { + return (Entity) FindByPrimaryKey(typeof(Entity), id); + } +} +``` + +The subclasses extend `Entity` and use the attribute `JoinedKeyAttribute` to decorate the shared key. This is required. + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord("entitycompany")] +public class CompanyEntity : Entity +{ + private byte company_type; + private int comp_id; + + [JoinedKey("comp_id")] + public int CompId + { + get { return comp_id; } + set { comp_id = value; } + } + + [Property("company_type")] + public byte CompanyType + { + get { return company_type; } + set { company_type = value; } + } + + public new static void DeleteAll() + { + DeleteAll(typeof(CompanyEntity)); + } + + public new static CompanyEntity[] FindAll() + { + return (CompanyEntity[]) FindAll(typeof(CompanyEntity)); + } + + public new static CompanyEntity Find(int id) + { + return (CompanyEntity) FindByPrimaryKey(typeof(CompanyEntity), id); + } +} + +[ActiveRecord("entityperson")] +public class PersonEntity : Entity +{ + private int person_id; + + [JoinedKey] + public int Person_Id + { + get { return person_id; } + set { person_id = value; } + } + + public new static void DeleteAll() + { + DeleteAll(typeof(PersonEntity)); + } + + public new static PersonEntity[] FindAll() + { + return (PersonEntity[]) FindAll(typeof(PersonEntity)); + } + + public new static PersonEntity Find(int id) + { + return (PersonEntity) FindByPrimaryKey(typeof(PersonEntity), id); + } +} +``` + +Please refer to the Reference Manual's Attributes article for further information. + +## Concrete Table Inheritance - Using + +Concrete table inheritance is supported by ActiveRecord without using any special techniques. If you define a type hierarchy you can place a `ActiveRecordAttribute` on each subclass which requires its own table. \ No newline at end of file diff --git a/docs/understanding-scopes.md b/docs/understanding-scopes.md new file mode 100644 index 0000000..1b35cda --- /dev/null +++ b/docs/understanding-scopes.md @@ -0,0 +1,125 @@ +# Understanding Scopes + +Any database related operation on ActiveRecord ultimately delegates the task to NHibernate. Basically, all NHibernate operations demands an ISession instance. + +When ActiveRecord is about to delegate something to NHibernate it checks if there is a ISession instance available and if not, one is created and disposed as soon as the operation is completed. + +A SessionScope is a way to encapsulate and extend the life of a NHibernate's ISession instance. Once one is active, when ActiveRecord checks for an ISession instance, the scope is found and from that point on it is in charge of providing the session. + +It is absolutely necessary that you know the basics of what is the purpose of the NHibernate ISession, the Flush operation and supported Flush behaviors. Please take a moment to read the NHibernate documentation. + +If you're familiar with the Unit of Work pattern, then consider a ISession a unit of work interaction with the database. NHibernate, however, is smart enough to flush changes whenever it decides it needs to. For instance, before sending a query to the database. + +That might lead to unexpected results. That's why it's important to understand how things work and then design your solution appropriately. + +## Understanding the internals + +The `ISessionFactoryHolder` interface implementation holds and manages one or more session factories (one per database configured). Any ActiveRecord operation invokes `CreateSession` and `ReleaseSession`, for example: + +```csharp +protected internal static Array FindAll(Type targetType, Order[] orders, params ICriterion[] criterias) +{ + ISession session = holder.CreateSession(targetType); + + try + { + // implementation omitted for clarity + } + catch(ValidationException) + { + throw; + } + catch(Exception ex) + { + throw new ActiveRecordException("Could not perform FindAll for " + targetType.Name, ex); + } + finally + { + holder.ReleaseSession(session); + } +} +``` + +The holder implementation always checks for a registered scope: + +```csharp +[MethodImpl(MethodImplOptions.Synchronized)] +public ISession CreateSession(Type type) +{ + if (threadScopeInfo.HasInitializedScope) + { + return CreateScopeSession(type); + } + + ISessionFactory sessionFactory = GetSessionFactory(type); + + ISession session = OpenSession(sessionFactory); + + System.Diagnostics.Debug.Assert( session != null ); + + return session; +} +``` + +## ISessionScope + +The `ISessionScope` interface defines the contract for possible scope implementations. All scopes must have a fixed type that defines its semantic. + +We have three built-in supported scopes: + +* `SessionScope`: Keeps an `ISession` instance alive, thus maximizing the performance by providing a first level cache, batching operations and allowing lazy load to work +* `TransactionMode`: Defines the scope of a transaction. Can be nested within a `SessionScope` and even between multiple transactions. +* `DifferentDatabaseScope`: replaces the connection used by NHibernate with one provided by your code, thus forcing the underlying operations to happen in a different database + +## Understanding SessionScope + +The `SessionScope` should always be used on real apps built using ActiveRecord. It allows lazy load relations to work, it batches operations and provides a first level cache. + +### First level cache + +Consider the following code: + +``` +TODO +``` + +### Batching changes + +Consider the following code: + +``` +TODO +``` + +### Possible unexpected behavior + +Consider the following code: + +``` +TODO +``` + +## Read only Scopes + +A NHibernate `ISession` manages the changes made to entities and `Flush` them in some situations. If your process performs lots of reads, and is not supposed to write, or you want to decide when a write should be performed, then you should create a `SessionScope` that does not perform `Flush`. + +```csharp +using(new SessionScope(FlushAction.Never)) +{ + // lots of operations db related here +} +``` + +For these cases, performance will be greatly improved. However it is up to you to manage the `Flush`: + +```csharp +using(new SessionScope(FlushAction.Never)) +{ + // lots of operations + + // Everything is OK, flush the changes + SessionScope.Current.Flush(); +} +``` + +At the same time, NHibernate is keeping tracks of changes to objects within the scope. If there are too many objects and too many changes to keep track, then performance will slowly downgrade. So a flushing now and then will be required. diff --git a/docs/unit-testing.md b/docs/unit-testing.md new file mode 100644 index 0000000..c8a296c --- /dev/null +++ b/docs/unit-testing.md @@ -0,0 +1,163 @@ +# Unit Testing + +Testing plays the most important role on software development. With ActiveRecord we usually keep a separated database for unit testing, so all data can be deleted mercilessly. + +We suggest that you use a base class for testing your object model. This greatly simplifies the task. However you have a few decisions to make: + +* Do you want to manage the schema yourself? +* Do you want to let ActiveRecord/NHibernate create the schema for you on the test database? + +If you want to manage the schema yourself, you must delete all data before executing each test, something like the following will be enough: + +```csharp +protected virtual void PrepareSchema() +{ + // Remember to do it in a descendent dependency order + + Office.DeleteAll(); + User.DeleteAll(); +} +``` + +Note that you have to implement the `DeleteAll` method on your classes. Something like: + +```csharp +using Castle.ActiveRecord; + +[ActiveRecord] +public class Office : ActiveRecordBase +{ + // definition omitted + + public static void DeleteAll() + { + ActiveRecordBase.DeleteAll(typeof(Office)); + } +} +``` + +On the other hand, if you want to let ActiveRecord create the schema, use the following: + +```csharp +protected virtual void PrepareSchema() +{ + ActiveRecordStarter.CreateSchema(); +} + +protected virtual void DropSchema() +{ + ActiveRecordStarter.DropSchema(); +} +``` + +The code below is a suggestion of an abstract class to simplify unit testing: + +:warning: **Warning:** Don't forget to call the base method when using additional SetUp attributes in your test classes. + +```csharp +using NUnit.Framework; + +using Castle.ActiveRecord; +using Castle.ActiveRecord.Framework; +using Castle.ActiveRecord.Framework.Config; + +public abstract class AbstractModelTestCase +{ + protected SessionScope scope; + + [TestFixtureSetUp] + public virtual void FixtureInit() + { + InitFramework(); + } + + [SetUp] + public virtual void Init() + { + PrepareSchema(); + + CreateScope(); + } + + [TearDown] + public virtual void Terminate() + { + DisposeScope(); + + DropSchema(); + } + + [TestFixtureTearDown] + public virtual void TerminateAll() + { + } + + protected void FlushAndRecreateScope() + { + DisposeScope(); + CreateScope(); + } + + protected void CreateScope() + { + scope = new SessionScope(); + } + + protected void DisposeScope() + { + scope.Dispose(); + } + + protected virtual void PrepareSchema() + { + // If you want to delete everything from the model. + // Remember to do it in a descendent dependency order + + // Office.DeleteAll(); + // User.DeleteAll(); + + // Another approach is to always recreate the schema + // (please use a separate test database if you want to do that) + + ActiveRecordStarter.CreateSchema(); + } + + protected virtual void DropSchema() + { + ActiveRecordStarter.DropSchema(); + } + + protected virtual void InitFramework() + { + IConfigurationSource source = ActiveRecordSectionHandler.Instance; + + ActiveRecordStarter.Initialize(source); + + // Remember to add the types, for example + // ActiveRecordStarter.Initialize( source, typeof(Blog), typeof(Post) ); + } +} +``` + +A test case using this base class would be similar to the following: + +```csharp +using NUnit.Framework; + +[TestFixture] +public class OfficeTestCase : AbstractModelTestCase +{ + [Test] + public void Creation() + { + Office myoffice = new Office("The Office"); + myoffice.Create(); + + FlushAndRecreateScope(); // Persist the changes as we're using scopes + + Office[] offices = Office.FindAll(); + Assert.AreEqual(1, offices.Length); + Assert.AreEqual("The Office", offices[0].Name); + } +} +``` \ No newline at end of file diff --git a/docs/using-hql.md b/docs/using-hql.md new file mode 100644 index 0000000..33ab05f --- /dev/null +++ b/docs/using-hql.md @@ -0,0 +1,263 @@ +# Using HQL (Hibernate Query Language) + +As NHibernate is database agnostic, a custom query language is used to query NHibernate entities. The query is translated to the underlying database and then executed. + +Teaching HQL is out of the scope of this article. You should consult [the NHibernate documentation on HQL](http://www.hibernate.org/hib_docs/reference/en/html/queryhql.html). + +There are at least three different ways to run a HQL query using ActiveRecord. + +## SimpleQuery and ScalarQuery + +`SimpleQuery` and `ScalarQuery` can be used in cases where the query would be a direct HQL query call. + +Here are some examples: + +* Using generics +* Not using generics + +### Using Generics + +```csharp +[ActiveRecord] +public class Blog : ActiveRecordBase +{ + ... + + // Static method from Blog class, retrieves all Post instances + // from the blog of the specified author. Uses positional parameters. + public static Post[] GetPostsFromAuthor( String author ) + { + SimpleQuery q = new SimpleQuery(@" + from Post p + where p.Blog.Author = ? + ", author); + return q.Execute(); + } + + // Static method from Blog class, retrieves the ID of all Post instances + // in a specified date interval. Uses named parameters. + public static int[] GetPostIdsFromInterval( DateTime start, DateTime end ) + { + // the second parameter specifies the AR type used for determining the database connection + SimpleQuery q = new SimpleQuery(typeof(Post), @" + select p.ID + from Post p + where p.Date between :start and :end + "); + q.SetParameter("start", start); + q.SetParameter("end", end); + return q.Execute(); + } + + // Instance method from Blog, gets the last post date. + public DateTime LastPostDate() + { + ScalarQuery q = new ScalarQuery(typeof(Post), @" + select max(p.Date) + from Post p + where p.Blog = ? + ", this); + return q.Execute(); + } +} +``` + +### Not Using Generics + +```csharp +[ActiveRecord] +public class Blog : ActiveRecordBase +{ + ... + + // Static method from Blog class, retrieves all Post instances + // from the blog of the specified author. Uses positional parameters. + public static Post[] GetPostsFromAuthor( String author ) + { + SimpleQuery q = new SimpleQuery(typeof(Post), @" + from Post p + where p.Blog.Author = ? + ", author); + return (Post[]) ExecuteQuery(q); + } + + // Static method from Blog class, retrieves the ID of all Post instances + // in a specified date interval. Uses named parameters. + public static int[] GetPostIdsFromInterval( DateTime start, DateTime end ) + { + // the second type parameter specifies the type of the query result + SimpleQuery q = new SimpleQuery(typeof(Post), typeof(int), @" + select p.ID + from Post p + where p.Date between :start and :end + "); + q.SetParameter("start", start); + q.SetParameter("end", end); + return (int[]) ExecuteQuery(q); + } + + // Instance method from Blog, gets the last post date. + public DateTime LastPostDate() + { + ScalarQuery q = new ScalarQuery(typeof(Post), @" + select max(p.Date) + from Post p + where p.Blog = ? + ", this); + return (DateTime) ExecuteQuery(q); + } +} +``` + +## Custom Query + +If your want: + +* encapsulation of your business rules within a query object; +* custom, advanced parameter handling; +* conditional queries (i.e., building the HQL and parameters manually); +* direct use of NHibernate's Criteria; or +* direct access to NHibernate's ISession or IQuery objects + +Then you can write a custom ActiveRecord query. + +You just need to inherit from `ActiveRecordBaseQuery` (or implement the `IActiveRecordQuery` interface). + +For example: + +```csharp +public class MyCustomQuery : ActiveRecordBaseQuery +{ + private String authorName = null; + private int maxResults = 2; + + public MyCustomQuery() : base(typeof(Blog)) + { + } + + public MyCustomQuery(string authorName) : base(typeof(Blog)) + { + this.AuthorName = authorName; + } + + public int MaxResults { get { return this.maxResults; } set { this.maxResults = value; } } + public String AuthorName { get { return this.authorName; } set { this.authorName = value; } } + + protected override IQuery CreateQuery(ISession session) + { + String hql = "from Blog b"; + + if (this.AuthorName != null) + hql += " where b.Author like :author"; + + IQuery q = session.CreateQuery(hql); + + if (AuthorName != null) + q.SetString("authorName", this.AuthorName); + + q.SetMaxResults(this.MaxResults); + + return q; + } + + protected override object InternalExecute(ISession session) + { + IQuery q = CreateQuery(session); + return SupportingUtils.BuildArray(typeof(Blog), q.List(), null, false); + } +} +``` + +The usage: + +```csharp +[ActiveRecord] +public class Blog : ActiveRecordBase +{ + ... + + public Blog[] GetThreeBlogsFromAuthor( String authorName ) + { + QueryWithNamedParameters q = new QueryWithNamedParameters(); + q.AuthorName = authorName; + q.MaxResults = 3; + return (Blog[]) ExecuteQuery(q); + } +} +``` + +## Execute Callback + +The third way to write custom queries is to use the `ActiveRecordBase.Execute` method, which basically does the same as the Custom Query approach, but without the need to write any additional classes. + +* Using generics +* Not using generics + +### Using Generics with Callback + +```csharp +[ActiveRecord] +public class Blog : ActiveRecordBase +{ + ... + + public static Post[] GetPostsFromAuthor( String author ) + { + return (Post[]) Execute( + delegate(ISession session, object instance) + { + // create the query... + IQuery query = session.CreateQuery( "from Post p where p.Blog.Author = :author" ); + + // set the parameters... + query.SetString("author", (String) instance); + + // fetch the results... + IList results = query.List(); + + // OPTIONAL: convert the results to an array or + // something meaningful, instead of returning the IList + Post[] posts = new Post[results.Count]; + results.CopyTo(posts, 0); + + // return + return posts; + }, author); + } +} +``` + +### Not Using Generics with Callback + +```csharp +[ActiveRecord] +public class Blog : ActiveRecordBase +{ + ... + + public static Post[] GetPostsFromAuthor( String author ) + { + return (Post[]) Execute( new NHibernateDelegate(GetPostsFromAuthorCallback), author ); + } + + private object GetPostsFromAuthorCallback( ISession session, object instance ) + { + // create the query... + IQuery query = session.CreateQuery( "from Post p where p.Blog.Author = :author" ); + + // set the parameters... + query.SetString("author", (String) instance); + + // fetch the results... + IList results = query.List(); + + // OPTIONAL: convert the results to an array or + // something meaningful, instead of returning the IList + Post[] posts = new Post[results.Count]; + results.CopyTo(posts, 0); + + // return + return posts; + } +} +``` \ No newline at end of file diff --git a/docs/using-imports.md b/docs/using-imports.md new file mode 100644 index 0000000..cc58f88 --- /dev/null +++ b/docs/using-imports.md @@ -0,0 +1,33 @@ +# Using Imports + +If there is a collision of names among your entities, you may use `Import` so you do not need to use their full name on queries. + +You can also use imports to query returns to classes. + +```csharp +using Castle.Framework; + +[ActiveRecord, Import(typeof(OrderSummary), "summary")] +public class Order : ActiveRecordBase +{ + // omitted for clarity +} + +public class OrderSummary +{ + private float value; + private int quantity; + + public float Value + { + get { return value; } + set { this.value = value; } + } + + public int Quantity + { + get { return quantity; } + set { quantity = value; } + } +} +``` \ No newline at end of file diff --git a/docs/using-scopes.md b/docs/using-scopes.md new file mode 100644 index 0000000..9790641 --- /dev/null +++ b/docs/using-scopes.md @@ -0,0 +1,89 @@ +# Using Scopes + +Scopes allow you to optimize a sequence of database related operations and to add some semantic to a block of code. A `SessionScope` is required if you want to use lazy initialized types or collections. + +A scope is active when constructed, and performs its work upon disposal. Once created - and before being disposed - the scope is active and available to all ActiveRecord operations on the same thread it was created. That means that you do not need to pass scopes along with in your method calls. + +## SessionScope + +A `SessionScope` is used to batch operations and to keep the NHibernate session. You must dispose a `SessionScope` after using it, so the C# `using` idiom becomes handy to enforce that. + +```csharp +using(new SessionScope()) +{ + Blog blog = new Blog(); + blog.Author = "hammett"; + blog.Name = "some name"; + blog.Save(); + + Post post = new Post(blog, "title", "post contents", "Castle"); + post.Save(); +} +``` + +More on the `SessionScope` can be found at [Understanding Scopes|Understanding Scopes]. + +## TransactionScope + +There is also an scope to enable transactions: + +```csharp +TransactionScope transaction = new TransactionScope(); + +try +{ + Blog blog = new Blog(); + blog.Author = "hammett"; + blog.Name = "some name"; + blog.Save(); + + Post post = new Post(blog, "title", "post contents", "Castle"); + post.Save(); +} +catch(Exception) +{ + transaction.VoteRollBack(); + + throw; +} +finally +{ + transaction.Dispose(); +} +``` + +## Nested transactions + +You can also have nested transactions which are particularly useful when you have a layer to mark transaction boundaries: + +```csharp +using(TransactionScope root = new TransactionScope()) +{ + Blog blog = new Blog(); + + using(TransactionScope t1 = new TransactionScope(TransactionMode.Inherits)) + { + blog.Author = "hammett"; + blog.Name = "some name"; + blog.Save(); + + t1.VoteCommit(); + } + + using(TransactionScope t2 = new TransactionScope(TransactionMode.Inherits)) + { + Post post = new Post(blog, "title", "post contents", "Castle"); + + try + { + post.SaveWithException(); + } + catch(Exception) + { + t2.VoteRollBack(); + } + } +} +``` + +Note the `TransactionMode.Inherits`. \ No newline at end of file diff --git a/docs/using-the-activerecordmediator.md b/docs/using-the-activerecordmediator.md new file mode 100644 index 0000000..b5dea74 --- /dev/null +++ b/docs/using-the-activerecordmediator.md @@ -0,0 +1,149 @@ +# Using the ActiveRecordMediator (avoiding a base class) + +Castle ActiveRecord offers a lot of functionality in persisting objects: It allows to use NHibernate without requiring to maintain XML mapping files, it manages sessions and its integration into MonoRail simplifies using web applications. + +One constraint that was found to hinder using ActiveRecord in practice was the requirement to inherit from a certain base class. Given the fact that multiple inheritance is not possible in .Net, the need for a base class is very problematic. + +And while the active record pattern is especially useful for creating data-driven applications, there are use cases where it is not adequate. Especially when adopting Domain Driven Design, there remains considerable debate whether ActiveRecord should be banned from the domain layer altogether. Although only removing the base class from ActiveRecord types does not make them persistance ignorant or deals with other problematic factors like the dominance of properties, it avoids attaching persistance behaviour to the classes. + +Stripped of the persistance related methods, ActiveRecord types can be used as domain classes. This is still questionable, but a practical way to accomplish transition from a data-driven to a domain-driven approach. + +Without persistance behaviour, ActiveRecord types can also be used as a DTOs (Data Transfer Objects) across the application. Methods like `Create()` or `Delete()` should not be present in DTOs since they distract the client code developer from using the applications services. A plain serializable object on the other hand can be filled with data and passed to the application layer which will use it to execute the required domain logic in the domain layer. + +## Basic Operations + +The basic operations are present in the `ActiveRecordMediator` class. This is a generic class that contains static methods providing the same functionality as `ActiveRecordBase` concerning basic operations discussed in lifecycle article. + +The following example shows the usage of ActiveRecordMediator: + +```csharp +// Create +Customer c = new Customer(); +c.Name = "Mr. Johnson"; +c.Address = String.Empty; +ActiveRecordMediator.Save(c); + +// Read/Update +Customer loaded = ActiveRecordMediator.FindOne(Expression.Eq("Name", "Mr. Johnson")); +loaded.Address = "Middle of the Road"; +ActiveRecordMediator.Save(loaded); + +// Delete +ActiveRecordMediator.Delete(c); +``` + +All information about the lifecycle, sessions and transactions are still valid, only the calls to perform ActiveRecord operations has changed. + +## Validation Support + +Another consequence of not using a base class is missing validation support. The `ActiveRecordValidationBase` offers an `IsValid()` and ActiveRecord denies flushing changes if an entity inheriting from this base class is not valid. + +Without using the base class, this functionality is void. Only database constraints are still effective. These constraints are defined within the Property or Field attribute and allow only basic validations. + +However, Castle's Validation support is not gone, but it must be invoked explicitly instead. + +The following example shows how this is done: + +```csharp +// Entity +[ActiveRecord] +public class Customer +{ + private Guid id; + + [PrimaryKey(PrimaryKeyType.GuidComb)] + public Guid Id + { + get { return id; } + set { id = value; } + } + private string name; + + [Property(Unique=true,NotNull=true)] + [ValidateNonEmpty] + [ValidateIsUnique] + public string Name + { + get { return name; } + set { name = value; } + } + private string address; + + [Property(NotNull=true)] + [ValidateNonEmpty] + public string Address + { + get { return address; } + set { address = value; } + } +} + +// using code +Customer c = new Customer(); +c.Name = "Mr. Johnson"; +c.Address = String.Empty; + +IValidatorRunner runner = new ValidatorRunner(new CachedValidationRegistry()); +if (runner.IsValid(c)) +{ + ActiveRecordMediator.Save(c); +} +else +{ + foreach (string msg in runner.GetErrorSummary(c).ErrorMessages) + { + Console.WriteLine(msg); + } +} +``` + +## Providing Callbacks + +The class `ActiveRecordHooksBase` which is base class of `ActiveRecordBase` provides a couple of methods to hook into ActiveRecord's lifecycle management. Classes that do not inherit from `ActiveRecordBase` do not offer these hooks. + +In order to hook into the lifecycle, it is possible to use the strategies that NHibernate offers. Since NHibernate works only on plain classes, these methods are also suitable for ActiveRecord-types without a base class. + +The two possibilities are: + +* **Interceptors**: Interceptors implement the interface `IInterface`, which defines roughly the same hooks as `ActiveRecordHooksBase`. Interceptors must be registered with the session used. +* **Events**: The event system was introduced in NHibernate 2.0. It replaces interceptors and offers a more modular approach to hooking into the lifecycle. For all hooks events are fired from within NHibernate. Any event listener registered will be notified of the event and can act accordingly. + +:information_source: Events have the benefit that the listeners can be registered in the configuration. This allows to add them transparently to the application. **ActiveRecord does not yet support the registration of event listeners in the configuration.** + +The use of lifecycle callbacks, interceptors and events is discussed in a the article [Hooks and lifecycle](hooks-and-lifecycle.md). + +## The ActiveRecordMediator as a Repository + +In addition to the persistence methods available many classic ActiveRecord entity types contain own persistence logic, for example static `Find`-methods that return entity collections based on the types special characteristics. + +When the original persistance logic ActiveRecord offers, is removed from the entity, there is no good reason to put additional persistance logic in it, even if domain-driven development is not an issue. + +Such methods belong together in a single class. This type of class is often called a **Repository**. If a specialized repository implementation such as `IRepository` from **Rhino Commons** is not used, `ActiveRecordMediator` can be used to build custom repository types: + +```csharp +public class CustomerRepo : ActiveRecordMediator +{ + public static Customer[] FindByName(string name) + { + return FindAll(new ICriterion[]{Expression.Like("Name", name,MatchMode.Anywhere)}); + } +} +``` + +The repository can then be used exactly like the `ActiveRecordMediator` itself: + +```csharp +// Create +Customer c = new Customer(); +c.Name = "Mr. Johnson"; +c.Address = String.Empty; +CustomerRepo.Save(c); + +// Read/Update +Customer loaded = CustomerRepo.FindByName("Johnson")[0]; +loaded.Address = "Middle of the Road"; +CustomerRepo.Save(loaded); + +// Delete +CustomerRepo.Delete(loaded); +``` \ No newline at end of file diff --git a/docs/using-version-and-timestamp.md b/docs/using-version-and-timestamp.md new file mode 100644 index 0000000..34edc73 --- /dev/null +++ b/docs/using-version-and-timestamp.md @@ -0,0 +1,31 @@ +# Using Version and Timestamp + +NHibernate's Version and Timestamp feature can be used on Castle ActiveRecord as well. Those are used to detect changes from other processes/requests, avoiding a last-win situation. + +When saving, NHibernate will make sure that the update will only succeed if the version/timestamp in the database match the version/timestamp on the object. If they are different, the changes will not be saved, and a `StaleObjectException` will be thrown. + +## Using Version + +Version can be of the following types: `Int64`, `Int32`, `Int16`, `Ticks`, `Timestamp`, or `TimeSpan`. + +```csharp +[Version("customer_version")] +public Int32 Version +{ + get { return version; } + set { version = value; } +} +``` + +## Using Timestamp + +Note that using the `Timestamp` attribute is equal to using the `Version` attribute with `Type="timestamp"`. Because of timestamp precision issues, `Version` is consider safer to use than `Timestamp` + +```csharp +[Timestamp("customer_timestamp")] +public Int32 Timestamp +{ + get { return ts; } + set { ts = value; } +} +``` \ No newline at end of file diff --git a/docs/validation-support.md b/docs/validation-support.md new file mode 100644 index 0000000..c2710cc --- /dev/null +++ b/docs/validation-support.md @@ -0,0 +1,51 @@ +# Validation Support + +You can have validatable properties on your ActiveRecord classes by using `ActiveRecordValidationBase` instead of `ActiveRecordBase` + +By doing so, you can use the following methods: + +* `IsValid`: will return true only if all validation test passes +* `ValidationErrorMessages`: returns a string array of descriptive error messages + +## Example + +The following class uses validations on two properties. If you attempt to save an invalid instance, ActiveRecord will throw an exception, so calling `IsValid` before saving it is a good idea. + +```csharp +using Castle.Components.Validator; + +[ActiveRecord] +public class Customer : ActiveRecordValidationBase +{ + private String contactName; + private String phone; + + public Customer() + { + } + + [Property, ValidateNonEmpty] + public string ContactName + { + get { return contactName; } + set { contactName = value; } + } + + [Property, ValidateNonEmpty] + public string Phone + { + get { return phone; } + set { phone = value; } + } +} +``` + +For a list of implemented validators and how to implement your own see [Validators](validators.md). + +Some validation logic is not suitable for declarative (attribute based) validators. If that is your case, you can override the method `IsValid`. Just make sure, if your validation test passes, you invoke the base implementation. + +By default, a `ValidationException` exception will be thrown if the validation fails. If you want to change this behavior, override the method `OnNotValid`. + +## ActiveRecordValidationBase + +You can also use ActiveRecordValidationBase so you can combine the niceness of declarative validations with the power of the generic implementation of ActiveRecordBase \ No newline at end of file diff --git a/docs/validators.md b/docs/validators.md new file mode 100644 index 0000000..12382a6 --- /dev/null +++ b/docs/validators.md @@ -0,0 +1,59 @@ +# Validators + +A validator performs a rule check and is used in combinator with the ActiveRecordValidationBase. + +There are two classes involved to make the validation work. The first one is an attribute, that allow the programmer to declaratively decorate a property with the validation (or validations) it wants to perform on its value. The second one is the implementation of a validator. The attribute is responsible for instantiating the validator implementation. + +When associating a validator with a property, you may optionally override the error message in the case the validation fails. For example: + +Note: validation relies on `Castle.Components.Validator`. + +```csharp +[ValidateEmail] +public String Email +{ + get { return email; } + set { email = value; } +} +``` + +If the above validation fails the error message will be "Field Email doesn't seem like a valid e-mail". To customize the message, use: + +```csharp +[ValidateEmail("Not a valid email")] +public String Email +{ + get { return email; } + set { email = value; } +} +``` + +## The Available Validators + +The following sections describe the existing validators. + +### ValidateIsUnique + +Checks that the property value does not exist in the database table. + +### ValidateRegExp + +Checks the value of the property against a regular expression that must be matched. + +### ValidateEmail + +Checks if the property value looks like a valid email. This is done using a regular expression. + +### ValidateNonEmpty + +Checks if the value is not null and not empty. + +### ValidateConfirmation + +Checks the value of the property has the same value of a different property. Useful for Password and Confirm password properties. + +## Creating a custom validator + +To create your own validator you must start with an attribute that extends from `AbstractValidationAttribute`. It must pass on an instance that implements `IValidator`. + +By implementing the `IValidator` interface you will get access to the `PropertyInfo` the attribute is associated with. You must perform the check on the `Perform` method. \ No newline at end of file diff --git a/docs/web-applications.md b/docs/web-applications.md new file mode 100644 index 0000000..d3c986c --- /dev/null +++ b/docs/web-applications.md @@ -0,0 +1,124 @@ +# Web Applications + +For a web application you should initialize ActiveRecord on the `Application_Start` call. It is also advisable that you keep a session scope per request. + +## Initializing + +As `Application_Start` will be executed only once per application instance, it is the best place to initialize the framework. The initialization is covered on [Configuration and Initialization](configuration-and-initialization.md) document. Here we are just going to show you how to get this working in a web application. + +First, if your web application does not have a `global.asax file`, create one like the one below. Fix the namespace to match your project namespace. + +``` +<%@ Application Inherits="My.Web.App.MyHttpApplication" %> +``` + +Then create a class that extends `HttpApplication`. + +```csharp +namespace My.Web.App +{ + using System; + using System.Web; + + using Castle.ActiveRecord; + using Castle.ActiveRecord.Framework; + using Castle.ActiveRecord.Framework.Config; + + using My.Web.App.Models; + + public class MyHttpApplication : HttpApplication + { + protected void Application_Start(Object sender, EventArgs e) + { + // Replace the code below as you want to match your + // preference about ActiveRecord initialization + + IConfigurationSource source = ActiveRecordSectionHandler.Instance; + + ActiveRecordStarter.Initialize( source, + typeof(Account), + typeof(AccountPermission), + typeof(ProductLicense), + typeof(SimplePerson), + typeof(Category), + typeof(PersonBase), + typeof(PersonUser)); + } + } +} +``` + +## Enabling a SessionScope per request + +You might use the `HttpApplication` to subscribe to the `BeginRequest` and `EndRequest` events. Those are the best place to start a session scope and to dispose it. + +```csharp +namespace My.Web.App +{ + using System; + using System.Web; + + using Castle.ActiveRecord; + using Castle.ActiveRecord.Framework; + using Castle.ActiveRecord.Framework.Config; + + using My.Web.App.Models; + + public class MyHttpApplication : HttpApplication + { + public MyHttpApplication() + { + // Subscribe to the events + + BeginRequest += new EventHandler(OnBeginRequest); + EndRequest += new EventHandler(OnEndRequest); + } + + protected void Application_Start(Object sender, EventArgs e) + { + // Initialization code omitted + } + + public void OnBeginRequest(object sender, EventArgs e) + { + // You might want to configure a different kind of session scope here, ie a readonly one + HttpContext.Current.Items.Add("ar.sessionscope", new SessionScope()); + } + + public void OnEndRequest(object sender, EventArgs e) + { + try + { + SessionScope scope = HttpContext.Current.Items["ar.sessionscope"] as SessionScope; + + if (scope != null) + { + scope.Dispose(); + } + } + catch(Exception ex) + { + HttpContext.Current.Trace.Warn("Error", "EndRequest: " + ex.Message, ex); + } + } + } +} +``` + +This approach gives you control over the `SessionScope` initialization. But if you do not want anything different than the standard functionality, you may replace the code above for the addition of a module. + +## Using the SessionScopeWebModule + +The `SessionScopeWebModule` module does exactly what the code above does -- subscribes to the events and creates and disposes of a `SessionScope`. + +To use it, just add the following entry to your `web.config`: + +```xml + + + + + +``` \ No newline at end of file diff --git a/docs/xml-configuration-reference.md b/docs/xml-configuration-reference.md new file mode 100644 index 0000000..6f4f063 --- /dev/null +++ b/docs/xml-configuration-reference.md @@ -0,0 +1,285 @@ +# XML Configuration Reference + +The following is the definition of the expected xml schema. Differences regarding the how it must appear in a standalone xml file or in a configuration associated with an AppDomain (web.config for instance) are also explained below. + +:information_source: If you are using Castle ActiveRecord in a web application, **you must add** the attribute `isWeb="true"`. This forces ActiveRecord to use a different strategy to hold instances. This is necessary as different threads may serve the same request events in a web application. + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +The following table explains the attributes. + +Attribute | Required | Description +--------- | -------- | ----------- +isWeb | No | If ActiveRecord is running in a ASP.NET application, you must add this attribute with the value true +isDebug | No | If true forces ActiveRecord to write the mappings to files. Useful for debugging and to show NHibernate team when asking for help on their forum. The files are written to the bin folder. +pluralizeTableNames | No | If true, ActiveRecord will infer the table names as plural of the entity name. Defaults to false. +threadinfotype | No | Full qualified type name to a custom implementation of IThreadScopeInfo +sessionfactoryholdertype | No | Full qualified type name to a custom implementation of ISessionFactoryHolder +namingstrategytype | No | Full qualified type name to a custom implementation of INamingStrategy. This is a NHibernate interface +type (on config node) | No | Only required if more than one config node is present. Should be a fully qualified type name to an abstract class that extends ActiveRecordBase and defines the boundaries to a different database +database (on config node) | No* | Optional shortcut attribute that populates configuration with default values for chosed database. The defaults can be overriden. When used, 'connectionStringName' must also be specified. Alternatively a shorter version of this attribute - 'db' can be used. +connectionStringName (on config node) | No* | Name of connection string in config to use. When used, 'database' must also be specified. Alternatively a shorter version of this attribute - 'csn' can be used. + +The NHibernate config elements are rather static and can be used from the examples below. The only element to pay attention to is the connection information. There are two possibilities two specify how to connect to the database: + +* Adding a connection string using the key connection.connection_string +* Using a predefined named connection string by specifying connection.connection_string_name + +Using the latter allows to use a connection string that is set in the connectionStrings-section of app.config or web.config. Those connection strings can be set by administrators using a GUI without knowing about ActiveRecord or NHibernate configuration. Example: + +```xml + + +
+ + + + + + + + + + + + + + +``` + +## Configuration on standalone xml file + +All that is required in a standalone configuration file is that the activerecord node be the root element node. For example: + +```xml + + + + + + + + + + +``` + +## AppDomain configuration + +Every AppDomain has a configuration file associated with it. For web application the file will be web.config. For .net executables it will be the name of the executable file and the sufix .config, for example myapp.exe.config. + +You can use these files to add a configuration to Castle ActiveRecord. Just make sure you declare a section for it under the sections node. + +```xml + + + +
+ + + + + + + + + + + + + + +``` + +## Examples per Database + +The following sections illustrates some usage of the Xml configuration. + +### Microsoft SQL Server 2000 + +```xml + + + + + + + + + + +``` + +### Microsoft SQL Server 2005 + +```xml + + + + + + + + + + +``` + +### Oracle + +```xml + + + + + + + + + + +``` + +### MySQL + +```xml + + + + + + + + + + +``` + +### Firebird + +```xml + + + + + + + + + + + +``` + +### PostgreSQL + +```xml + + + + + + + + + + +``` \ No newline at end of file