User-defined types
Cassandra 2.1 introduced support for User-defined types (UDT). A user-defined type simplifies handling a group of related properties.
A quick example is a user account table that contains address details described through a set of columns: street, city, zip code. With the addition of UDTs, you can define this group of properties as a type and access them as a single entity or separately.
User-defined types are declared at the keyspace level.
In your application, you can map your UDTs to application entities. For example, given the following UDT:
CREATE TYPE address (
street text,
city text,
zip int,
phones list<text>
);
You create a C# class that maps to the UDT:
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public int Zip { get; set; }
public IEnumerable<string> Phones { get; set;}
}
You declare the mapping once at the session level:
await session.UserDefinedTypes.DefineAsync(
UdtMap.For<Phone>()
.Map(v => v.Alias, "alias")
.Map(v => v.CountryCode, "country_code")
.Map(v => v.Number, "number")).ConfigureAwait(false);
You can also provide the keyspace when declaring a UDT. This is useful for these situations:
- If the UDT is defined on a keyspace which is not the default - which can be set via Session.Connect(string)
or Builder.WithDefaultKeyspace(string)
- if you don’t declare a default keyspace with the methods mentioned above
To provide the keyspace when declaring a UDT:
csharp
await session.UserDefinedTypes.DefineAsync(
UdtMap.For<Phone>(keyspace: "keyspace")
.Map(v => v.Alias, "alias")
.Map(v => v.CountryCode, "country_code")
.Map(v => v.Number, "number")).ConfigureAwait(false);
Once declared the mapping will be available for the lifetime of the application:
var results = session.Execute("SELECT id, name, address FROM users where id = 756716f7-2e54-4715-9f00-91dcbea6cf50");
var row = results.First();
// You retrieve the field as a value of type Address
var userAddress = row.GetValue<Address>("address");
Console.WriteLine("The user lives on {0} Street", userAddress.Street);
CQL column and C# class properties mismatch
For the automatic mapping to work, the table column names and the class properties must match (note that column-to-field matching is case-insensitive). For example, in the UDT and the C# class examples above were changed:
CREATE TYPE address (
street text,
city text,
zip_code int,
phones list<text>
);
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public int ZipCode { get; set; }
public IEnumerable<string> Phones { get; set;}
}
You can also define the properties manually:
session.UserDefinedTypes.Define(
UdtMap.For<Address>()
.Map(a => a.Street, "street")
.Map(a => a.City, "city")
.Map(a => a.Zip, "zip")
.Map(a => a.Phones, "phones")
);
You can still use automatic mapping, but you must add a call to the Map method. For example:
session.UserDefinedTypes.Define(
UdtMap.For<Address>()
.Automap()
.Map(a => a.Zipcode, "zip_code")
);
Nesting User-defined types In CQL
UDTs can be nested relatively arbitrarily. For the C# driver you have to define the mapping to all the user-defined types used.
Based on the previous example, let’s change the phones column from set<text>
to a set<phone>
, where phone
contains an alias, a number and a country code.
Phone UDT
CREATE TYPE phone (
alias text,
number text,
country_code int
);
Address UDT
CREATE TYPE address (
street text,
city text,
zip_code int,
phones list<phone>
);
Now we can update the Address
class to use the Phone
class:
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public int ZipCode { get; set; }
public IEnumerable<Phone> Phones { get; set;}
}
You have to define the mapping for both classes
session.UserDefinedTypes.Define(
UdtMap.For<Phone>(),
UdtMap.For<Address>()
.Automap()
.Map(a => a.ZipCode, "zip_code")
);
After that, you can reuse the mapping within your application.
var userAddress = row.GetValue<Address>("address");
var mainPhone = userAddress.Phones.First();
Console.WriteLine("User main phone is {0}", mainPhone.Alias);