-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Experimenting on syntax with Extension Methods and building compiled …
…lambdas with Expressions.
- Loading branch information
Showing
13 changed files
with
388 additions
and
154 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
126
DataContext.Core/ContextAware/DataContextModelContainer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
DataContext.Core/ContextAware/EntityFrameworkPersonRepositoryWithContextAwareModel.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.