<EmbeddedStruct> Element

Stores an embedded type (struct or non-persistent class) as a property of a persistent class in a set of database fields.

<Member name="property">

       <EmbeddedStruct [constructor="Auto|Explicit" [constructorSignature="ctor-signature"]]>

              (<Membername="struct-member"

                    [ConstructorParameter="param-name"]>

                    persistent-field-element

              </Member>)+

       </EmbeddedStruct>

</Member>

or

<Member name="property">

       <EmbeddedStruct [constructor="Auto|Explicit" [constructorSignature="ctor-signature"]]>

              (<Membername="struct-member">

                    [<ConstructorParameterparameterName="param-name"/>]

                    persistent-field-element

              </Member>)+

       </EmbeddedStruct>

</Member>

property: The name of an abstract property of the persistent class, that returns the embedded type. The property can have a getter (read only), or setter (write only), or both (read and write).

Auto: Genome tries to match the constructor for initialising the embedded type automatically. This is the default value, when the attribute is omitted.

Explicit: Genome matches for a constructor based on the provided ctor-signature.

ctor-signature: List of types to match for the constructor signature, when Explicit is specified. When omitting this attribute, and specifying Explicit, an empty signature list is assumed (which selects the default constructor).

struct-member: A member of the embedded type, which is to be mapped to a database field. Currently, only members that can be mapped with <PersistentField/> are supported.

param-name: Optional specification for the name of a constructor parameter of the embedded type, to which Genome should map the database field value when constructing an instance from the database. This is necessary when Genome is not using the default constructor for initialising the embedded type, and the constructor parameter names cannot be automatically matched to the embedded type members.

persistent-field-element: Mapping of the member to a persistent field.

Remarks

·    Embedded types can be structs or non-persistent classes. Non-persistent class refers to any CLR type which does not map its extent to a specific database table (i.e. does not maintain an identity in the database).

·    The embedded class cannot be abstract.

·    Currently, only scalar members of the struct or CLR type can be mapped to database fields.

·    Only public members of the embedded type can be mapped to database fields.

·    Genome needs to determine, how to initialise the embedded struct, when reading it from the database: initialisation is performed either with the default constructor and explicitly setting the individual properties of the embedded struct, or using a constructor which initialises the fields.

o    If at least one mapped member is read only, or there is no default constructor on the class (structs always provide a default constructor), the embedded type has to provide a public constructor for initialising all the members when constructing an instance from the database.

o    You can explicitly specify a contructor to be used with constructor="Explicit" and providing a ctor-signature (constructorSignature). Omitting constructorSignature, or specifying an empty ctor-signature, selects the default constructor.

o    Currently, there is no support for mixed initialisation of members using constructor and direct member assignment. This means that either all mapped members must be public or all members must be initialised by a constructor. You cannot use a specific constructor, which does not initialise all mapped fields of the embedded struct.

o    When not using the default constructor, Genome needs to match the constructor parameters to the mapped members of the embedded struct. By default, the mapped member name with a lower case first letter is assumed as the constructor parameter name. The parameter name can be explicitly defined using the ConstructorParameter element or attribute on the member mapping.

·    Currently, Genome cannot detect changes to members in an embedded class automatically. Therefore, we recommend that you use structs or treat the embedded instance as immutable (reset the reference with a new instance instead of modifying values of the assigned instance).

·    Mapped members of the embedded type can also be used in queries (e.g. for filtering).

·    Members of embedded types can also be mapped to server-side OQL expressions. Map the embedded type with <Type/> and use the corresponding member mappings.

Examples

Mapping an embedded struct with read/writeable properties

In the Northwind database, the Customer has the following scalar fields:

public abstract class Customer : Persistent

{

 

    public abstract string CustomerId { get; set; }

 

 

    public abstract string CompanyName { get; set; }

    public abstract string ContactName { get; set; }

    public abstract string ContactTitle { get; set; }

 

    public abstract string Address { get; set; }

    public abstract string City { get; set; }

    public abstract string Region { get; set; }

    public abstract string PostalCode { get; set; }

    public abstract string Country { get; set; }

 

    public abstract string Phone { get; set; }

    public abstract string Fax { get; set; }

}

The Address, City, Region, PostalCode and Country properties can be extracted to an embedded struct Address:

public struct Address

{

    Public string Street;

    public string City;

    public string Region;

    public string PostalCode;

    public string Country;

}

Address can be a class as well. In this case, you have to make sure that the class provides a default constructor (a constructor that can be called with no arguments).

The embedded class can now be exposed on Customer, instead of the individual scalar fields:

public abstract class Customer : Persistent

{

 

    public abstract string CustomerId { get; set; }

 

 

    public abstract string CompanyName { get; set; }

    public abstract string ContactName { get; set; }

    public abstract string ContactTitle { get; set; }

 

    public abstract Address Address { get; set; }

 

    public abstract string Phone { get; set; }

    public abstract string Fax { get; set; }

}

In the mapping of Customer, the individual fields of Address have to be mapped to the database fields of the customers table, which were previously mapped to scalar properties:

<Type name="Customer">

    <RootInheritance tableName="Customers"/>

    <Member name="Address">

        <EmbeddedStruct>

            <Member name="Street" PersistentField="Address" />

            <Member name="City" PersistentField="City" />

            <Member name="Region" PersistentField="Region" />

            <Member name="PostalCode" PersistentField="PostalCode" />

            <Member name="Country" PersistentField="Country" />

        </EmbeddedStruct>

    </Member>

</Type>

Note that Address.Street is mapped to the Address field of the customers table.

Filtering for embedded struct members

Having mapped an embedded struct as described above, you can also use the struct members for filtering.

The following query retrieves all customers in the USA:

extentof(Customer)[Address.Country=="USA"]

The same in LINQ:

from c in DataDomain.Extent<Customer>()

where c.Address.Country == "USA"

select c

Projecting to embedded struct members

Similarly, you can use the struct members in projections.

This query retrieves the addresses of all customers:

[Address]extentof(Customer)

The same in LINQ:

from c in DataDomain.Extent<Customer>()

select c.Address;

You can also project to members of embedded structs.

This query retrieves the list of countries of all customers:

[Address.Country distinct]extentof(Customer)

The same in LINQ:

(from c in DataDomain.Extent<Customer>()

select c.Address.Country).Distinct();

As a general rule, you can use embedded struct members for the same operations as direct persistent fields.

Initialising an embedded struct with a constructor

When Genome loads an object with an embedded struct member from the database, it initialises the struct by assigning the mapped properties of the embedded struct with the mapped database field values.

However, when the struct members are read only, this initialisation does not work. In this case, you can map to a constructor of the struct, which initialises its members. By default, Genome tries to match the constructor with the following rules:

·    The constructor has to have a parameter for each mapped property.

·    The parameter name has to match the mapped property name with a lower case start character.

The following code shows a modified Address struct that will be initialised by the defined constructor, as the Street property is read only:

public struct Address

{

    private string _street;

    public string Street

    {

        get { return _street; }

    }

    public string City;

    public string Region;

    public string PostalCode;

    public string Country;

    public Address(string street, string city, string region, string postalCode, string country)

    {

        _street = street;

        this.City = city;

        Region = region;

        PostalCode = postalCode;

        Country = country;

    }

}

In case the constructor parameter names cannot be matched with the public member names, the ConstructorParameter attribute can be used to provide explicit mapping:

<Type name="Customer">

    <RootInheritance tableName="Customers"/>

    <Member name="Address">

        <EmbeddedStruct>

            <Member name="Street" PersistentField="Address"

                    ConstructorParameter="street" />

        </EmbeddedStruct>

    </Member>

</Type>

Note that the constructor has to contain all mapped properties of the embedded struct as parameter. You do not need to specify the ConstructorParameter attribute for members, as long as they follow Genome’s default naming expectation.

When mapping an embedded struct to a class, remember to ensure that a default constructor is available if you do not provide a special initialisation constructor as described above.

Mapping methods of an embedded struct

Like methods of any other CLR type, methods of embedded structs can be mapped to be used in OQL.

On the previously discussed Address class, imagine the following method to look up the country code of an Address:

public class Address

{

    public string GetCountryCode(DataDomain dd)

    {

        return (from cc in dd.Extent<CountryCode>()

                where cc.CountryName == this.Country

                select cc.Code).SingleOrDefault();

    }

}

Note that Address is expressed as a class, since you cannot use this (access instance members) in lambda expressions in C#.

You can map this expression as well, in order to use it in other queries:

<Type name="Address">

    <Member name="GetCountryCode" signature="TechTalk.Genome.DataDomain">

        <Linq><![CDATA[ 

            (from cc in dd.Extent<CountryCode>()

            where cc.CountryName == this.Country

            select cc.Code).SingleOrDefault();

        ]]></Linq>

    </Member>

</Type>

Note that this mapping of Address just provides server-side behaviour for Address.GetCountryCode() and has nothing to do with the mapping of Customer.Address.

After that, you can use the mapped member in server-side expressions, such as filtering and projection. The following query returns all customers with country code "AUT":

extentof(Customer)[Address.GetCountryCode(this.DataDomain)=="AUT"]

The same in LINQ:

from c in DataDomain.Extent<Customer>()

where c.GetCountryCode(DataDomain)=="AUT"

select c;

Note that both queries are fully evaluated in the database, joining the CountryCodes table in the WHERE clause.

Updating an embedded struct

When working with embedded structs, keep in mind that Genome can only detect changes applied to the property that returns the struct, not to changes of values within the struct.

This means that an update such as

customer.Address = new Address(…);

can be tracked, while the following update is not detected by Genome:

customer.Address.Country = "NewCountry";

It is therefore recommended to implement embedded structs as immutable, as described below.

Updating embedded structs mapped as struct

When updating struct members, keep in mind that structs are value types: this means that you cannot update the property of a struct as shown below (assuming the previously described domain model that has Address implemented as struct):

customer.Address.Country = "NewCountry";

The reason for this is that customer.Address is a property or field that returns the address as a new value (and not as a reference). Any changes you apply to this new value will be lost and not assigned to customer.Address.

Instead, assign the value to a variable, on which you can performan changes, and assign the new struct value back to customer.Address:

Address a = customer.Address;

address.Country = "NewCountry";

customer.Address = adress;

To avoid this confusion, it is a recommended pattern in .NET to implement structs as immutable. This means that the struct can only be initialised with the constructor, and exposes all properties read only:

public struct Address

{

    private string _street;

    public string Street

    {

        get { return _street; }

    }

 

    private string _city;

    public string City;

    {

        get { return _city; }

    }

 

    private string _region;

    public string Region;

    {

        get { return _region; }

    }

 

    private string _postalCode;

    public string PostalCode;

    {

        get { return _postalCode; }

    }

 

    private string _country;

    public string Country;

    {

        get { return _country; }

    }

 

    public Address(string street, string city, string region, string postalCode, string country)

    {

        _street = street;

        _street = city;

        _region = region;

        _postalCode = postalCode;

        _country = country;

    }

}

Note that the public constructor for initialising the struct needs to match Genome’s default naming expectations or has to be mapped explicitly with ConstructorParameter.

Assigning a new Country is then straightforward:

customer.Address = new Adress(customer.Address.Street, customer.Address.City, customer.Address.Region, customer.Address.PostalCode, "NewCountry");

Updating embedded structs mapped as class

When Address is a class, the members are directly modifiable. However, Genome cannot detect changes performed directly on members of an embedded class. It is therefore recommended again to make the class immutable by exposing the members read only, allowing only for initialisation by the constructor, as described previously.

Requirements

Editions:Professional, Evaluation, Express

Database Platforms:Microsoft SQL Server 2000, Microsoft SQL Server 2005, Oracle 9i Release 2, Oracle 10g Release 2, IBM DB2