Instances of value types directly contain their data. Variables that are of value types each have their own copy of the data, and therefore, an operation on one variable does not affect other variables. Value types are implicitly sealed types, since they cannot be derived from.
All supported values types, regardless of whether built-in, enums, nullable or user-defined, are only supported as fields of other user-defined persistent types. It is not possible to store a standalone instance of a value type in the database.
The following table lists the .NET Framework built-in value types supported by OpenAccess ORM:
These value types correspond to the primitive data types used by programming languages. An additional type, System.Enum, is also inherited from System.ValueType. System.Enum provides the base class for enumerations. An enumeration is a named constant whose underlying type can be any integral type except Char. If no underlying type is explicitly declared, Int32 is used. User-defined types can be derived from System.Enum. Derived types are value types and are sealed, i.e., these types cannot serve as base types for other user-defined types.
All enumeration types, whose underlying type is one of the supported built–in value types (see above for supported built-in value types), are supported and treated like their underlying value type.
Arrays of enumerations are not supported.
Nullable Value Types
.NET 2.0 introduces System.Nullable<type> where type is a valuetype. This allows you to express a null-value for fields of this type.
The C# alias is type?
Both the syntaxes are equivalent as shown in the example below:
public class PersClass
int normalInt; //can not be null
int? nullableInt1; //can be null
System.Nullable<int> nullableInt2; //equivalent to int?
Earlier, with the normal int field, especially in conjunction with legacy databases, it was possible that some fields could not have the required mapping (since null was not allowed), especially if the corresponding column in the database allowed NULL-values.
However, with a nullable int field, the range of allowed values for the field can be the same as the range of allowed values for the database column, especially when both permit NULL-values.
OpenAccess ORM supports nullable types for all of the supported built-in value types as listed above. These types are also supported only as field types of user-defined persistent types.
User-defined value types
For user defined enumerations refer to the enumerations section above.
User-defined value types are used in .NET to express simple user defined data types, which can be copied such as telephone numbers. They do not follow the normal semantics with respect to referencability i.e., they are not easily referencable, from the outside and are copied by value in general. This allows them to be embedded into other objects as you can do with built-in data types like int or double.
User-defined value types can be used to express common data aggregation and functionality, for e.g. an Address value type, which is physically contained/used by a Person and a Company class.
All other user-defined value types have to be marked as persistent, so that they can be used as field types of other user-defined persistent types. Instances of user-defined persistent value types can only be stored as a part of a user-defined persistent reference, i.e. they cannot be stored standalone.
However, a user-defined persistent value-type is allowed to have fields of another user-defined value-type.
Such fields are stored embedded in the table of their owning object, i.e. they are "flattened" as additional columns into the parents table (see also Mapping), User-defined value-types don’t have their own table.
You should access the data of the structs only via accessor methods (properties) This is necessary to avoid an unconscious "copy-by-value" behavior, which might not be what you intended. The intended usage pattern for them is to support "atomic" application specific types, which can be embedded in memory and in the relational tables.
Also currently arrays, collections or dictionaries of user-defined value types are not supported
For user-defined values, the same rules, which apply to user-defined persistent reference types apply; the exception being that no standalone entities are allowed (refer to the User-defined persistent reference types section below for the more information).
Change Tracking for Value Type Fields
If an instance of a persistence capable class contains a built-in value type field and this field is passed by reference (as a ref parameter) to a method that modifies the field, OpenAccess ORM cannot track this change. In practice what this means, is that the object instance containing the value type field will not be marked as modified and will not be updated when the enclosing transaction is committed. As, this can lead to unexpected behavior, the OpenAccess ORM enhancer will detect such situations and issue a warning during enhancement.
The behavior described above is only true for built-in value types (Refer to Built-in Value Types for a list of the built-in value types supported in OpenAccess ORM). In the case of user-defined persistent value types, there are no such limitations; change tracking works even when the user-defined value type fields are passed by reference. Therefore, there are no warnings printed by the enhancer for user-defined value type fields.
Consider the following example. Start with a persistence capable class, MyPersistent:
In the MySource.cs source file, define a method with an int parameter passed by reference:
int MethodWithRefParam( ref int val )
val = val + 42;
In the following code, the value of the persistent int field is passed by reference:
IObjectScope os = . . .
MyPersistent obj = . . .
MethodWithRefParam( ref obj.intValue );
For this code, the OpenAccess ORM enhancer prints the following warning:
MySource.cs(15,4): Warning: Cannot track changes for the field MyPersistent.intValue in the call of int MethodWithRefParam(int ByRef).
In the above case, even though the intValue field has been modified, the obj instance will not be updated in the database during the Commit() call.
You can avoid the above situation and the enhancer warning, by making a change to your source code. There are two possibilities:
The easiest way is to simply not to rely on the use of the "ref" parameter and instead assign the object field to the return value from the method:
obj.intValue = MethodWithRefParam( obj.intValue );
OpenAccess ORM can track this "direct" modification to the object and can properly mark it as modified.
A second possibility is to use an additional "out" parameter and pass the value type twice. For value type fields that are passed as "out" parameters the OpenAccess ORM change tracking mechanism will always mark the object containing the passed value type as modified (Even if the method does not actually modify the parameter).
The following example shows how this works. First redefine the method to take the "ref" parameter and an additional "out" parameter:
int MethodWithRefParam( ref int inVal, out int outVal )
outVal = inVal + 42;
Now, when you call the method, pass the value type twice.
MethodWithRefParam( obj.intValue, obj.intValue );
OpenAccess ORM will mark obj as modified and the object will be updated in the database during the transaction Commit() call.