Experimental Entity Mixins via Shadow Properties in Entity Framework 7

Entity Framework 7 introduces a new concept to the EF runtime known as shadow properties. Shadow properties are properties declared as part of the database model, but not part of the specific entity class. You can declare shadow properties as part of your model, and then EF provides a way to access and reason against those properties as part of your queries, through a special type called EF.

Firstly, we'll declare an example entity, the User:

public class User
{
  public int Id { get; set; }

  public string Name { get; set; }
}

So, my model building code might be something like:

var entity = builder.Entity<User>();
entity.HasKey(u => u.Id);
entity.Property(u => u.Name)
  .IsRequired()
  .HasMaxLength(20);

To declare a shadow property for this type, I could do the following:

entity.Property<string>("GooglePlusProfile")
  .HasMaxLength(10);

I'd recommend watching this video with Rowan Miller who guides you through some simple examples.

In my example above, I've detailed a my user entity, and have added a my entity properties, and now using the EF type, I can provide a predicate for my queries:

from u in context.Users
where EF.Property(u, "GooglePlusProfile") != null
select u;

The mechanism is good, but I'm not really a fan of the magic strings in there, I'd prefer something that we can actually statically declare, like any other property.

What is a entity mixin?

In my prototype, an entity mixin is another type that is declared separately from my entity, but it's properties become shadow properties of the host entity. So first things first, I need to find a way of declaring properties of one type, and have them become properties of another, in the context of my entity model. So, I define a custom MixinTypeBuilder and provide an extension method to EntityTypeBuilder. The calls to my custom builder are forwarded to a parent EntityTypeBuilder where the properties are actually declared. This provides nice syntactic sugar:

var entity = builder.Entity<User>();
entity.HasKey(u => u.Id);
entity.Property(u => u.Name)
  .IsRequired()
  .HasMaxLength(20);

var mixin = entity.Mixin<Author>();
mixin.Property(a => a.GooglePlusProfile)
  .IsRequired()
  .HasMaxLength(50);

Here is my custom builder:

public class MixinTypeBuilder<TMixin> where TMixin : Mixin
{
    private readonly EntityTypeBuilder _entityTypeBuilder;
    private readonly string _propertyPrefix = $"{typeof(TMixin).Name}_";

    internal MixinTypeBuilder(EntityTypeBuilder entityTypeBuilder)
    {
        _entityTypeBuilder = entityTypeBuilder;

        _entityTypeBuilder.Annotation("MixinType", typeof(TMixin));
    }

    public PropertyBuilder<TProperty> Property<TProperty>(Expression<Func<TMixin, TProperty>> propertyExpression)
    {
        if (propertyExpression == null)
        {
            throw new ArgumentNullException(nameof(propertyExpression));
        }

        string propertyName = propertyExpression.GetPropertyAccess().Name;
        string propertyKey = $"{_propertyPrefix}{propertyName}";

        var builder = _entityTypeBuilder.Property<TProperty>(propertyKey);

        return builder;
    }
}

My example mixin, Author, now declares my shadow property GooglePlusProfile, which I rewrite as Author_GooglePlusProfile (this would need to be reflected in any table naming - migrations should handle this automatically). We also add an annotation to the entity being built to give it a reference to a mixin type, this will be used later through query generation.

Querying with Mixins

At this point, we haven't done anything to enhance how we query against our custom shadow properties. What I want to provide, is a nice statically type way of expressing access to Mixin properties. I want to achieve something like this:

from u in context.Users
where u.Mixin<Author>().GooglePlusProfile != null
select u;

We're now needing to define a method Mixin<T>() which will allow us access the properties of the mixin as part of our query. EF7 won't support this out of the box, so we need to make some tweaks.

EF7 is extensible and modular by nature and when you're composing your application through DI, you can easily replace parts of the framework with your own implementations.

My first port of call is to get the actual method working, so I define an interface called ISupportMixins and an abstract implementation MixinHost:

public interface ISupportMixins
{
    void AddMixin(Mixin mixin);

    T Mixin<T>() where T : Mixin;
}

public abstract class MixinHost : ISupportMixins
{
    private readonly List<Mixin> _mixins = new List<Mixin>();

    void ISupportMixins.AddMixin(Mixin mixin)
    {
        _mixins.Add(mixin);
    }

    public T Mixin<T>() where T : Mixin
    {
        return _mixins.OfType<T>().FirstOrDefault() ?? Activator.CreateInstance<T>();
    }
}

We can now adjust our entity implementation for User:

public class User : MixinHost
{
  public int Id { get; set; }

  public string Name { get; set; }
}

The key part of this experiment is being able to transform expressions from what you see as part of your code, to what is actually executed against the database. Given my User and Author classes, I want to do the following:

// From:
u.Mixin<Author>().GooglePlusProfile != null

// To:
EF.Property<string>(u, "Author_GooglePlusProfile") != null

EF7 already has the plumbing in place to support the call to EF.Property<string>(u, "Author_GooglePlusProfile"), so we just need to transform our form of u.Mixin<Author>().GooglePlusProfile into this. We do this with an expression visitor, which allows us to walk to expression tree and replace it.

public class MixinExpressionVisitor : ExpressionVisitorBase
{
    public Expression TransformMixinMemberExpression(MemberExpression member)
    {
        var method = (MethodCallExpression)member.Expression;
        var target = method.Object;
        string propertyName = $"{method.Type.Name}_{member.Member.Name}";

        return Expression.Call(
            EntityQueryModelVisitor.PropertyMethodInfo.MakeGenericMethod(member.Type),
            target,
            Expression.Constant(propertyName));
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        var method = node.Expression as MethodCallExpression;
        if (method != null && method.Method.IsGenericMethod && method.Method.Name == "Mixin")
        {
            // Here we are transforming calls like "entity.Mixin<Type>().Property" to "EF.Property<PropertyType>(entity, "FullPropertyName")"
            return TransformMixinMemberExpression(node);
        }

        return base.VisitMember(node);
    }
}

EF7 uses Remotion's re-linq for handling the LINQ provider work, and this provides an intermediate query model with which we can thread our code.

Currently, I've only been playing with the relational model with EF7, so to do what I needed, I provided my own implementation of Microsoft.Data.Entity.Query.RelationalQueryModelVisitor:

using EFRelationalQueryModelVisitor = Microsoft.Data.Entity.Query.RelationalQueryModelVisitor;

public class RelationalQueryModelVisitor : EFRelationalQueryModelVisitor
{
    // ctor ommited for brevity....

    public override void VisitQueryModel(QueryModel queryModel)
    {
        queryModel.TransformExpressions(e => new MixinExpressionVisitor().Visit(e));

        base.VisitQueryModel(queryModel);
    }
}

This is one of the parts we're replacing on EF, but we extend the pre-existing type so we don't lose anything it is already doing. To replace this type, you can replace the service description:

services.Replace(ServiceDescriptor.Scoped<IEntityQueryModelVisitorFactory, RelationalQueryModelVisitorFactory>());

When EF now runs, it'll be using our implementation instead of the stock version.

We do have another scenario to consider, and that is the projection, if I want to do the following:

select u.Mixin<Author>();

This will currently work, but the property values will not be provided because that'll be transformed to a simple new Author() expression. Instead, let's make some adjustments, so that:

// From:
select u.Mixin<Author>();

// To:
select new Author { GooglePlusProfile = EF.Property<string>(u, "Author_GooglePlusProfile") };

We do this by enhancing our visitor type:

public class MixinExpressionVisitor : ExpressionVisitorBase
{
    // Other Visitor code ommited for brevity

    private readonly IModel _model;

    public MixinExpressionVisitor(IModel model)
    {
        _model = model;
    }

    public Expression TransformMixinMethodExpression(MethodCallExpression method)
    {
        var mixinType = method.Type;
        var entityType = method.Object.Type;
        string prefix = $"{mixinType.Name}_";

        // Get the available properties of the mixin.
        var entity = _model.GetEntityType(entityType);
        var properties = entity
            .GetProperties()
            .Where(p => p.Name.StartsWith(prefix))
            .ToArray();

        // Create an object initializer expression.
        var ctor = Expression.New(mixinType);
        var memberBindings = new MemberBinding[properties.Length];
        for (int i = 0; i < properties.Length; i++)
        {
            var property = properties[i];
            string propertyName = property.Name.Replace(prefix, "");
            var member = mixinType.GetProperty(propertyName);
            var value = Expression.Call(
                EntityQueryModelVisitor.PropertyMethodInfo.MakeGenericMethod(member.PropertyType),
                method.Object,
                Expression.Constant(property.Name));

            memberBindings[i] = Expression.Bind(member, value);
        }

        return Expression.MemberInit(ctor, memberBindings);
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        var method = node.Method;
        if (method != null && method.IsGenericMethod && method.Name == "Mixin")
        {
            // Here we are transforming calls like "select entity.Mixin<Type>()" to "new Type { PropertyName = EF.Property<PropertyType>(entity, "FullPropertyName") }
            return TransformMixinMethodExpression(node);
        }
        return base.VisitMethodCall(node);
    }
}

The visitor now has to consider the the IModel of the database context, because we need to understand what properties are available to the mixin. We use the annotations we provided previously to the model to grab those properties that are shadow properties and properties of our mixin.

We take each of those properties, and transform them to a call to EF.Property<T>(...), and aggregate these as an object initializer, using the Expression.MemberInit(...) method.

This is all very transparent to your end query, because the return type of .Mixin<T>() is T, which is also the return type of the constructor to T.

Hydrating the mixin and offering change detection

The last steps our of experiment is to actually get the values from the entity to the mixin, and this now actually introduces a restriction on where these mixins can actually be used. Shadow property values are actually stored by the change tracker, so we can't use them in untracked scenarios.

I haven't really found a decent point in the framework to hook into the entity hydration, and this is because EF generates expressions instead of using Activator to create the types, so I went a layer higher and modified how the state manager is actually creating references to items it is tracking. This is another part of "stock" EF we're going to replace, the Microsoft.Data.Entity.ChangeTracking.Internal.InternalEntityEntryFactory

using EFInternalEntityEntryFactory = Microsoft.Data.Entity.ChangeTracking.Internal.InternalEntityEntryFactory;

public class InternalEntityEntryFactory : EFInternalEntityEntryFactory
{
    public InternalEntityEntryFactory(IEntityEntryMetadataServices metadataServices) : base(metadataServices)
    {
    }

    public override InternalEntityEntry Create(IStateManager stateManager, IEntityType entityType, object entity)
    {
        var entry = base.Create(stateManager, entityType, entity);

        BindMixins(entry, entityType, entity);

        return entry;
    }

    public override InternalEntityEntry Create(IStateManager stateManager, IEntityType entityType, object entity, ValueBuffer valueBuffer)
    {
        var entry = base.Create(stateManager, entityType, entity, valueBuffer);

        BindMixins(entry, entityType, entity);

        return entry;
    }

    private void BindMixins(InternalEntityEntry entry, IEntityType entityType, object entity)
    {
        var mixinHost = entity as ISupportMixins;
        if (mixinHost != null)
        {
            var mixinTypes = entityType
                .Annotations
                .Where(a => a.Name == "MixinType")
                .Select(a => (Type)a.Value)
                .Distinct()
                .ToArray();

            foreach (var mixinType in mixinTypes)
            {
                // Create the mixin.
                var mixin = (Mixin)Activator.CreateInstance(mixinType);

                // Set the resolver.
                mixin.SetPropertyEntryResolver(p => new PropertyEntry(entry, p));

                // Assign to the host entity.
                mixinHost.AddMixin(mixin);
            }
        }
    }
}

This process firstly determines if the entity is a mixin candidate by looking for our ISupportMixins contract. Next, we determine what mixins are applied to the entity through the annotations, and for each one, we'll create an instance of it (mixins must derive from Mixin), and assign a delegate which allows us to get or set values against the current state manager. Lastly, we then add the mixin to the host.

So, putting it all together, this means I can do the following:

var users = (
    from u in context.Users
    where u.Mixin<Author>().GooglePlusProfile != null
    select u //u.Mixin<Author>()
).ToList();


var user = users.FirstOrDefault();
if (user != null)
{
    var author = user.Mixin<Author>();

    // You can make changes here:
    author.GooglePlusProfile = "enter your G+ profile here...";

    // Save changes
    context.SaveChanges();
}

It is by no means a perfect solution, there are a few scenarios we don't currently support:

  • Support chance detection when selecting only the mixin: from u in context.Users select u.Mixin<Author>(). In this scenario, the properties set against the mixin don't support change detection (they are stored internally by the Mixin base type), because the parent entity is not loaded for the state manager work to kick in.
  • Haven't found a nice way of attaching a Mixin instance to an existing entity and then saving changes.

After all, this is just an experiment.

Code available on GitHub

comments powered by Disqus