Use this framework if you want to overcome some of Entity Framework's limitations without leaking too many persistence implementation details into your domain model. This implementation is similar to NHibernate's user types capability. It allows you to create custom types by giving you control over how properties are persisted and materialized.
Note: Only use this feature for properties that you DO NOT need to filter by. The public properties of your model classes are not actually mapped so EF will not know how to generate a WHERE clause for those properties.
- Column level encryption
- Persist enums as strings
- Persist complex types as JSON/XML
- Persist lists as delimited strings
To enable user types, you must add the following to the constructor of your DbContext.
public class ExampleContext : DbContext
{
public ExampleContext()
{
this.EnableUserTypes();
}
}
To configure user type properties, you can use the built-in extenstion method.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>()
.UserTypeProperty<Customer, string, CryptoUserType<Customer, string>>(x => x.Secret);
}
Or create your own extension to improve the readability of your code.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().CryptoProperty(x => x.Secret);
}
public static class CryptoUserTypeExtension
{
public static StringPropertyConfiguration CryptoProperty<TEntity, TValue>(this EntityTypeConfiguration<TEntity> mapper, Expression<Func<TEntity, TValue>> expression, string backingPropertyName = null) where TEntity : class
{
return mapper.UserTypeProperty<TEntity, TValue, CryptoUserType<TEntity, TValue>>(expression, backingPropertyName);
}
}
Your model classes must contain backing properties for each user type property. Unfortunately, this is just a limitation of entity framework, but don't worry, these properties can be marked as private.
public class Customer
{
public CustomerStatus Status { get; set; }
public string Secret { get; set; }
public CustomerPreferences Preferences { get; set; }
#region Backing Fields
private string StatusBacking { get; set; }
private string SecretBacking { get; set; }
private string PreferencesBacking { get; set; }
#endregion Backing Fields
}
To create a user type, your class must implement the IUserType interface.
public interface IUserType
{
void OnObjectMaterialized(object entity);
void OnSavingChanges(object entity);
}
Or your class must derive from the UserTypeBase class. The UserTypeBase class handles most of the work for you and only requires that you implement 2 abstract methods.
public class CryptoUserType<TEntity, TValue> : UserTypeBase<TEntity, TValue> where TEntity : class
{
public CryptoUserType(string propertyName, string backingPropertyName)
: base(propertyName, backingPropertyName)
{
}
protected override string GetBackingValue(object targetValue)
{
var cipher = Encryption.EncryptRijndael(targetValue.ToString());
return Convert.ToBase64String(cipher);
}
protected override object GetTargetValue(string backingValue)
{
byte[] cipher = Convert.FromBase64String(backingValue);
string targetValue = Encryption.DecryptRijndael(cipher);
return targetValue;
}
}
Column-level encryption.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().CryptoProperty(x => x.Secret);
}
Persist enums as strings (not currently possible with EF).
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().EnumProperty(x => x.Status).IsRequired().HasMaxLength(15);
}
Persist complex types as JSON.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>().JsonProperty(x => x.Preferences);
}