Data Annotations in the Entity Framework and Code First

Data annotation attributes were introduced in .NET 3.5 as a way to add validation to classes used by ASP.NET applications. Since that time, RIA Services has begun using data annotations and they are now part of Silverlight.  Code First allows you to build an EDM Entity Framework model using code (C# or VB.NET) and is now in its third CTP.

Feedback we’ve received from these CTPs shows demand for Code First to read data annotation attributes from class and property definitions to configure aspects such as maximum string length, which properties are keys, or the table name in which to store a type of entity.

This post outlines which existing data annotation attributes we’ll adopt and which new attributes we are proposing.

Using Data Annotations

When building a model using Code First, you start by writing a set of entity classes. For example:

public class Book
{
    public string ISBN { get; set; }
    public string Title { get; set; }
    public string AuthorSSN { get; set; }
    public Person Author { get; set; }
}

public class Person
{
    public string SSN { get; set; }
    public string Name { get; set; }
    public ICollection<Book> Books { get; set; }
}

When using this class model with Code First in CTP3, you would need to use the fluent API to describe which properties are keys, which relationships exist, and the length of each string property if you wanted these to be constrained. These are very common tasks and we’d like users of Code First to be able to make these kinds of configuration without having to use the fluent API to describe the model. Using data annotation attributes, the above model characteristics can be made declaratively as part of the class definition:

public class Book
{
[Key]
    public string ISBN { get; set; }

    [StringLength(256)]
    public string Title { get; set; }

public string AuthorSSN { get; set; }

    [RelatedTo(RelatedProperty=“Books”, Key=”AuthorSSN”, RelatedKey=”SSN”)]
public Person Author { get; set; }
}

public class Person
{
    [Key]
    public string SSN { get; set; }

    [StringLength(512)]
    public string Name { get; set; }

    [RelatedTo(RelatedProperty=”Author”)]
    public ICollection<Book> Books { get; set; }
}

Existing Data Annotation Attributes

There are several existing data annotation attributes in the System.ComponentModel.DataAnnotations namespace that can be used by Code First. These are:

KeyAttribute

KeyAttribute is used to specify that a property/column is part of the primary key of the entity and applies to scalar properties only.

StringLengthAttribute

StringLengthAttribute is used to specify the maximum length of a string. As there is no minimum length in Code First or EF, the “MinimumLength” property on the attribute would be ignored. This attribute applies only to properties that are of type string.

In .NET 4.0, a negative MaximumValue is not allowed. but in the next version of the framework the recommendation is to change this behavior so that a value of ‘-1’ means “max”. This isn’t ideal, so another idea we are considering is to make a MaxStringLengthAttribute that derives from StringLengthAttribute. The MaxStringLengthAttribute wouldn’t take parameters, but would just assume the maximum allowable value.

ConcurrencyCheckAttribute

ConcurrencyCheckAttribute is used to specify that a property/column has a concurrency mode of “fixed” in the EDM model. A fixed concurrency mode means that this property is part of the concurrency check of the entity during save operations and applies to scalar properties only.

RequiredAttribute

RequiredAttribute is used to specify that a property/column is non-nullable and applies to scalar, complex, and navigation properties:

  • Scalar properties: means this property is non-nullable (even if the CLR type says it is nullable).
  • Complex properties: the attribute is not allowed here so we would throw an exception
  • Navigation properties
    • Collection property: the attribute is not allowed here so we would throw an exception
    • Reference property: this means the backing FK property is non-null, and that the end of the association is cardinality “1” (instead of 0..1).

TimestampAttribute

The TimestampAttribute is used to specify that a byte[] property/column has a concurrency mode of “fixed” in the model and that it should be treated as a timestamp column on the store model (non-nullable byte[] in the CLR type). This attribute applies to scalar properties of type byte[] only. and only one TimestampAttribute can be present on an entity as this is what is allowed on most database platforms.

DataMemberAttribute

The DataMemberAttribute, while not strictly part of the data annotation collection, can be used to help specify the ordinal of primary keys. This is important on entities where there is a composite primary key and you want to make an assertion about the order the keys are specified on the database or are used in your application. This attribute applies only to scalar properties only that are part of the key. Ordinals do not need to be consecutive, they only specify the relative order of the key properties in metadata.

Ideally we’d like to be able to specify the key order as part of the KeyAttribute via an “Ordinal” property, but we can’t make changes to KeyAttribute until the next release of the .NET framework.

New Data Annotation Attributes

While the existing data annotation attributes provide many useful options for defining your model, there are still several common cases where we’d like to be able to make model changes with annotation attributes. Our goal with data annotation support in Code First is not to provide another way to completely specify a model, but to provide a mechanism that addresses the most common kinds of configurations that developers need to influence their model.

This is an area where it would be great to get early feedback so we can incorporate your ideas into our data annotation plan - below are descriptions of the annotation attributes we are proposing for Code First.

RelatedToAttribute

The RelatedToAttribute is used for two things:

  • To assign bi-directionality to certain relationships where the built-in convention doesn’t detect it
  • To associate a navigation property with its foreign key(s)

The RelatedToAttribute is placed on navigation properties and can be present on zero, one, or both ends of a relationship. There are a few parameters on the RelatedToAttribute:

  • A property to optionally specify the RelatedProperty on the target type.
    This is to support bi directionality in relationships (Book.Author is related to Person.Books, for example).
  • A property to optionally specify the keys on this entity that constrain the relationship.
    This can be either the primary key(s) if the attribute is on a navigation property on the principal end of a relationship, or the foreign key(s) if the attribute is on a navigation property on the dependent end of a relationship.
  • A property to optionally specify the keys on the related entity that constrain the relationship.
    This can be either the primary key(s) if the attribute is on a navigation property on the dependent end of a relationship, or the foreign key(s) if the attribute is on a navigation property on the principal end of a relationship.
  • A property to optionally specify cascade delete behavior.
    When this property is set to true and the principal entity is deleted, the dependent end of the relationship will also be deleted.

In the example at the beginning of the blog post, the RelatedToAttribute was used to related the Book.Author and Person.Books navigation properties so that all of Book.AuthorSSN, Book.Author, and Person.Books represent the same relationship in the model.

We also considered using the existing AssociationAttribute to specify relationships. however this attribute assumes that there already is a backing model and requires a relationship name and required key properties which are not needed when describing your model with Code First.

LengthAttribute

LengthAttribute can be used to specify the maximum length of an array type, such as a byte[] and applies only to properties. A value of ‘-1’ means “max” and all other negative values are not allowed. There is overlap here with the StringLengthAttribute: when the LengthAttribute is specified on a property of type string, the semantics will be the same as StringLengthAttribute.

StoreGeneratedAttribute

The StoreGeneratedAttribute can be used to specify that a property’s value is generated on the database store and the only store generated pattern supported in the first version is “identity”. This attribute applies to properties only and has a single optional nullable boolean property Identity. A null value for Identity means that no decision should be made for Identity: the convention or an explicit API call will be made to turn the identity pattern on or off.

StoreNameAttribute

The StoreNameAttribute can be used to specify a name for a store/database item. This attribute has different meanings depending on the kind of thing this attribute applies to:

  • If the attribute is on a context class, it means “database name”.
  • If the attribute is on an entity class, it means “table name”.
  • If the attribute is on a property in an entity class, it means “column name”.

StoreIgnoreAttribute

StoreIgnoreAttribute can be used to specify that a CLR type or property should not be included when reading type or property metadata from a CLR type. This attribute can apply to classes, which means they should not be considered as an EntityType or ComplexType object but can also apply to properties, which means that property should not be part of the EntityType or ComplexType. We haven’t finalized the name of this attribute yet, so if you have suggestions, please let us know.

StoreInlineAttribute

StoreInlineAttribute can be used to specify that a CLR type should be considered as a ComplexType when reading type metadata from CLR types. Complex types can be thought of as a class without an identity/keys, or a logic grouping of properties (such as an Address or PhoneNumber) and applies only to class definitions. ComplexTypes are stored inline with the entity. We may choose not to include this attribute as the convention we are thinking of adding to Code First should detect most cases where a complex type is being used.

Other Possibilities

There may be other kinds of model definition that you’d like to be able to do using data annotation attributes. Some other candidates are:

  • Being able to specify whether a string is unicode or not
  • Being able to specify whether a type is variable or fixed length
  • Having data annotation attributes put CSDL annotations in your model that other systems (such as OData) can read. An example of this is the RangeAttribute.

Let us know if any of these options sound interesting or if there is something else you’d like us to consider.

Summary

Code First will offer a few different ways of configuring a model: purely by built-in conventions, using data annotation attributes to make certain kinds of model changes, or by using a fluent API to fully customize the model. To provide more flexibility over the existing data annotation attributes, we are adding new attributes that Code First can use.

We’d like to hear your feedback on the set of attributes that are available and how Code First will use them.

Jeff Derstadt
Entity Framework Team