Skip to content

Commit

Permalink
Experimenting on syntax with Extension Methods and building compiled …
Browse files Browse the repository at this point in the history
…lambdas with Expressions.
  • Loading branch information
runerys committed Apr 1, 2012
1 parent ec83286 commit 7181ac2
Show file tree
Hide file tree
Showing 13 changed files with 388 additions and 154 deletions.
92 changes: 92 additions & 0 deletions DataContext.Core/ContextAware/ContextAwareExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
namespace DataContext.Core.ContextAware
{
using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;

public static class ContextAwareExtensions
{
private const string CONTEXT_ID = "ContextId";

public static List<T> ToListInContext<T>(this IQueryable<T> query, int contextId)
{
return query.In(contextId).ToList();
}

public static T FirstInContext<T>(this IQueryable<T> query, int contextId)
{
return query.In(contextId).First();
}

private static readonly Dictionary<Type, bool> TypeHasContextIdPropertyCache = new Dictionary<Type, bool>();

public static IQueryable<T> In<T>(this IQueryable<T> query, int contextId)
{
// Convention: Filter on ContexId if property is present.
// Don't want to introduce IContextAware interface.

var entityType = typeof(T);

if (!TypeHasContextIdPropertyCache.ContainsKey(entityType))
{
var contextProperty = entityType.GetProperty(CONTEXT_ID);
var hasContextIdProperty = contextProperty != null;

TypeHasContextIdPropertyCache.Add(entityType, hasContextIdProperty);
}

if (!TypeHasContextIdPropertyCache[entityType])
return query;

var expression = In<T>(contextId);

return query.Where(expression);
}

public static Expression<Func<TEntity, bool>> In<TEntity>(int contextId)
{
// Create the lambda: "e => e.ContextId == contextId"

var entityParameter = Expression.Parameter(typeof(TEntity));
var contextIdProperty = Expression.Property(entityParameter, CONTEXT_ID);

var contextIdConstant = Expression.Constant(contextId);
var comparison = Expression.Equal(contextIdProperty, contextIdConstant);

return Expression.Lambda<Func<TEntity, bool>>(comparison, new[] { entityParameter });
}

private static Func<ObjectContext, int> getContextId;

public static IQueryable<T> InContext<T>(this ObjectSet<T> objectSet) where T:class
{
// Convention: Filter on ContexId if property is present.
// Don't want to introduce IContextAware interface.

// Create lambda: (context) => ((DataContextModelContainer)context).ContextId;
if (getContextId == null)
{
var containerContextIdProperty = objectSet.Context.GetType().GetProperty(CONTEXT_ID);

if (containerContextIdProperty == null)
throw new InvalidOperationException("Then entity framework model must have a public ContextId property. Implement this in a partial class.");

var getter = containerContextIdProperty.GetGetMethod();

var entityExpression = Expression.Parameter(typeof(ObjectContext));
var castEntity = Expression.ConvertChecked(entityExpression, typeof(DataContextModelContainer));

var extract = Expression.Call(castEntity, getter);

var lambda = Expression.Lambda<Func<ObjectContext, int>>(extract, entityExpression).Compile();
getContextId = lambda;
}

var modelContextId = getContextId.Invoke(objectSet.Context);

return objectSet.Where(In<T>(modelContextId));
}
}
}
126 changes: 126 additions & 0 deletions DataContext.Core/ContextAware/DataContextModelContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;
using System.Data;
using System.Data.Objects;
using System.Linq.Expressions;

namespace DataContext.Core
{
using System.Collections.Generic;
using System.Linq;

using DataContext.Core.ContextAware;

public partial class DataContextModelContainer
{
private CurrentContext CurrentContext { get; set; }

public int ContextId
{
get { return CurrentContext.Id; }
}

public int Context
{
get { return CurrentContext.Id; }
}

public DataContextModelContainer(CurrentContext currentContext)
: this()
{
CurrentContext = currentContext;
}

partial void OnContextCreated()
{
SavingChanges += OnSavingChanges;
}

private void OnSavingChanges(object sender, EventArgs eventArgs)
{
foreach (ObjectStateEntry entry in ((ObjectContext)sender).ObjectStateManager.GetObjectStateEntries(EntityState.Added))
{
if (entry.IsRelationship)
continue;

var entity = entry.Entity;

if (entity == null)
continue;

// Faster than pure Reflection
SetContextIdWithCachedCompiledLambda(entity);
}
}

private static Dictionary<Type, Action<object, int>> PropertySetters = new Dictionary<Type, Action<object, int>>();

private void SetContextIdWithCachedCompiledLambda(object entity)
{
if (CurrentContext == null)
return;

var entityType = entity.GetType();

// Generate and cache setter lambda
// Not thread safe access to cache - yet
if (!PropertySetters.ContainsKey(entityType))
{
var contextIdProperty = entityType.GetProperty("ContextId");

if (contextIdProperty == null)
{
PropertySetters.Add(entityType, null);
return;
}

// Create expression: (entity, contextId) => ((Type)entity).set_ContextId = contextId

var getsetMethod = contextIdProperty.GetSetMethod();

var entityExpression = Expression.Parameter(typeof(object));
var parameterExpression = Expression.Parameter(typeof(int), "contextId");

var castEntity = Expression.ConvertChecked(entityExpression, entityType);

var assignment = Expression.Call(castEntity, getsetMethod, new[] { parameterExpression });

Action<object, int> lambda = Expression.Lambda<Action<object, int>>(assignment, entityExpression, parameterExpression).Compile();

PropertySetters.Add(entityType, lambda);
}

var setter = PropertySetters[entity.GetType()];

if (setter == null)
return;

setter.Invoke(entity, ContextId);
}

public Expression<Func<TEntity, bool>> InContext<TEntity>(TEntity entity)
{
var entityParameter = Expression.Parameter(typeof(TEntity));
var contextId = Expression.Constant(CurrentContext.Id, typeof(int));

var comparison = Expression.Equal(entityParameter, contextId);

return Expression.Lambda<Func<TEntity, bool>>(comparison, new[] { entityParameter });
}

public IQueryable<T> WithContextFilterOn<T>(Expression<Func<DataContextModelContainer, ObjectSet<T>>> objectSetExpression) where T : class
{
var body = objectSetExpression.Body as MemberExpression;

if (body == null)
throw new ArgumentException("Parameter expr must be a memberexpression");

var objectSetName = body.Member.Name;

var property = this.GetType().GetProperty(objectSetName);

var objectSet = property.GetValue(this, null) as ObjectSet<T>;

return objectSet.In(this.Context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
namespace DataContext.Core.ContextAware
{
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Experimenting on how to make the repository less aware about the contextId
/// </summary>
public class EntityFrameworkPersonRepositoryWithContextAwareModel : IPersonRepository
{
private readonly ModelFactory _modelFactory;

public EntityFrameworkPersonRepositoryWithContextAwareModel(ModelFactory modelFactory)
{
_modelFactory = modelFactory;
}

public List<Person> WithFirstName(string firstname)
{
using (var model = _modelFactory.New())
{
// First try: Kind of "fluent"
var people = from p in model.People.In(model.Context)
where p.FirstName == firstname
select p;

return people.ToList();
}
}

public List<Person> WithLastName(string lastname)
{
using (var model = _modelFactory.New())
{
// Second try: A bit backwards
var people = from p in model.WithContextFilterOn(m => m.People)
where p.LastName == lastname
select p;

return people.ToList();
}
}

public List<Person> WithName(string name)
{
using (var model = _modelFactory.New())
{
// Probably the better alternative this far
var people = from p in model.People.InContext()
where name == p.FirstName + " " + p.LastName
select p;

return people.ToList();
}
}

public List<Person> WithName2(string name)
{
using (var model = _modelFactory.New())
{
var people = from p in model.People
where name == p.FirstName + " " + p.LastName
select p;

// Experimenting - not that happy about this one
return people.ToListInContext(model.ContextId);
}
}

public void Save(Person person)
{
using (var model = _modelFactory.New())
{
model.People.AddObject(person);
model.SaveChanges();
}
}
}
}
17 changes: 17 additions & 0 deletions DataContext.Core/ContextAware/ModelFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace DataContext.Core.ContextAware
{
public class ModelFactory
{
private readonly CurrentContext _currentContext;

public ModelFactory(CurrentContext currentContext)
{
_currentContext = currentContext;
}

public DataContextModelContainer New()
{
return new DataContextModelContainer(_currentContext);
}
}
}
9 changes: 7 additions & 2 deletions DataContext.Core/DataContext.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
<Reference Include="Autofac.Configuration">
<HintPath>..\packages\Autofac.2.6.1.841\lib\NET40\Autofac.Configuration.dll</HintPath>
</Reference>
<Reference Include="LinqKit">
<HintPath>..\packages\LinqKit.1.0\lib\35\LinqKit.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Entity" />
Expand All @@ -49,17 +52,19 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ContextAware\ContextAwareExtensions.cs" />
<Compile Include="CurrentContext.cs" />
<Compile Include="DataContextModel.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>DataContextModel.edmx</DependentUpon>
</Compile>
<Compile Include="DataContextModelContainer.cs" />
<Compile Include="EntityFrameworkPersonRepositoryWithContextAwareModel.cs" />
<Compile Include="ContextAware\DataContextModelContainer.cs" />
<Compile Include="ContextAware\EntityFrameworkPersonRepositoryWithContextAwareModel.cs" />
<Compile Include="IPersonRepository.cs" />
<Compile Include="IServiceLocator.cs" />
<Compile Include="EntityFrameworkPersonRepository.cs" />
<Compile Include="ContextAware\ModelFactory.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceLocator.cs" />
</ItemGroup>
Expand Down
Loading

0 comments on commit 7181ac2

Please sign in to comment.