Skip to content

Commit 8e43038

Browse files
author
Jeff Treuting
committed
ConfigurationBasedRepository and FetchStrategy update
Added new constructors for ConfigurationBasedRepository so you can pass in the repositoryName and the configSection if they are needed, or still use the parameterless constructor to do the defaults in the config file. Made the logic to take the Expression based Include() parameter and translate to a string for the EF Include statement. Now you can do a multiple level deep expression like x => x.OrderItems.Select(i => i.Product) and it will translate to "OrderItems.Product" (thanks to http://www.codetuning.net/blog/post/Entity-Framework-compile-safe-Includes.aspx) Fixed SharpRepository#71 Also added some new FetchStrategy unit tests
1 parent f794d2f commit 8e43038

File tree

7 files changed

+113
-14
lines changed

7 files changed

+113
-14
lines changed

SharpRepository.Benchmarks.Configuration/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ static void Main(string[] args)
9595
sw.Stop();
9696
Console.WriteLine(" {0} ms total -- {1} avg ms per\n", sw.Elapsed.TotalMilliseconds, sw.Elapsed.TotalMilliseconds / Convert.ToDouble(Max));
9797
}
98-
99-
Console.WriteLine("\nDone: press any key to quit");
98+
99+
Console.WriteLine("\nDone: press enter to quit");
100100
Console.Read();
101101
}
102102

SharpRepository.Repository/ConfigurationBasedRepository.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@ namespace SharpRepository.Repository
1919
{
2020
protected readonly IRepository<T, TKey> Repository;
2121

22-
// no need to new this up directly, should be used as the base class for a custom repository implementation
23-
protected ConfigurationBasedRepository()
22+
// protected constructors so user can't instantiate directly
23+
// we have 2 constructors so you can use the defualt sharpRepository section or specify a config section
24+
// you can also provide the repository name from the config file instead of whatever the default is if needed
25+
protected ConfigurationBasedRepository(string configSection, string repositoryName)
2426
{
25-
// Load up the repository based on the configuration file
26-
Repository = RepositoryFactory.GetInstance<T, TKey>();
27+
Repository = RepositoryFactory.GetInstance<T, TKey>(configSection, repositoryName);
28+
}
29+
30+
protected ConfigurationBasedRepository(string repositoryName = null)
31+
{
32+
// Load up the repository based on the default configuration file
33+
Repository = RepositoryFactory.GetInstance<T, TKey>(repositoryName);
2734
}
2835

2936
IEnumerator IEnumerable.GetEnumerator()
Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,43 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq.Expressions;
4+
using System.Reflection;
5+
using System.Text;
6+
using SharpRepository.Repository.Helpers;
37

48
namespace SharpRepository.Repository.FetchStrategies
59
{
610
/// <summary>
711
/// Per Will Beattie's article, Specification Pattern, Entity Framework & LINQ
812
/// <see cref="http://blog.willbeattie.net/2011/02/specification-pattern-entity-framework.html"/>
13+
/// <see cref="http://www.codetuning.net/blog/post/Entity-Framework-compile-safe-Includes.aspx"/>
914
/// </summary>
1015
public static class FetchStrategyExtensions
1116
{
1217
/// <summary>
13-
/// Evaluates the Linq expression and returns the name of the property.
18+
/// Evaluates the Linq expression and returns the name of the property or the multiple level deep string representation of the Expression (i.e. prop.Collection.Property).
1419
/// </summary>
1520
/// <typeparam name="T">Type being evaluated</typeparam>
1621
/// <param name="selector">Name of the property per the Linq expression</param>
1722
/// <returns></returns>
18-
public static string ToPropertyName<T>(this Expression<Func<T, object>> selector)
23+
public static string ToIncludeString<T>(this Expression<Func<T, object>> selector)
1924
{
20-
var me = selector.Body as MemberExpression;
21-
if (me == null)
25+
// Retrieve member path:
26+
var members = new List<PropertyInfo>();
27+
ExpressionHelper.CollectRelationalMembers(selector, members);
28+
29+
// Build string path:
30+
var sb = new StringBuilder();
31+
var separator = "";
32+
foreach (var member in members)
2233
{
23-
throw new ArgumentException("MemberExpression expected.", "selector");
34+
sb.Append(separator);
35+
sb.Append(member.Name);
36+
separator = ".";
2437
}
2538

26-
var trimPrefix = me.ToString();
27-
return trimPrefix.Substring(trimPrefix.IndexOf('.') + 1);
39+
// return concatenated string
40+
return sb.ToString();
2841
}
2942
}
3043
}

SharpRepository.Repository/FetchStrategies/GenericFetchStrategy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public IEnumerable<string> IncludePaths
2525

2626
public IFetchStrategy<T> Include(Expression<Func<T, object>> path)
2727
{
28-
_properties.Add(path.ToPropertyName());
28+
_properties.Add(path.ToIncludeString());
2929
return this;
3030
}
3131

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq.Expressions;
4+
using System.Reflection;
5+
6+
// Reference: http://www.codetuning.net/blog/post/Entity-Framework-compile-safe-Includes.aspx
7+
// Thank you to Rudi Grobler at codetuning.net for this blog post that I reworked slightly, but am using their basic code to allow for a multiple level LINQ expression
8+
// to get translated into a string representation that can be used for the EF Include statement
9+
namespace SharpRepository.Repository.Helpers
10+
{
11+
internal static class ExpressionHelper
12+
{
13+
internal static void CollectRelationalMembers(Expression exp, IList<PropertyInfo> members)
14+
{
15+
switch (exp.NodeType)
16+
{
17+
case ExpressionType.Lambda:
18+
CollectRelationalMembers(((LambdaExpression)exp).Body, members);
19+
break;
20+
case ExpressionType.MemberAccess:
21+
{
22+
var mexp = (MemberExpression)exp;
23+
CollectRelationalMembers(mexp.Expression, members);
24+
members.Add((PropertyInfo)mexp.Member);
25+
}
26+
break;
27+
case ExpressionType.Call:
28+
{
29+
var cexp = (MethodCallExpression)exp;
30+
31+
if (cexp.Method.IsStatic == false)
32+
throw new InvalidOperationException("Invalid type of expression.");
33+
34+
foreach (var arg in cexp.Arguments)
35+
CollectRelationalMembers(arg, members);
36+
}
37+
break;
38+
case ExpressionType.Parameter:
39+
return;
40+
default:
41+
throw new InvalidOperationException("Invalid type of expression.");
42+
}
43+
}
44+
}
45+
}

SharpRepository.Repository/SharpRepository.Repository.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
<Compile Include="CompoundKeyRepositoryBase.cs" />
9393
<Compile Include="CompoundKeyRepositoryBase.Batch.cs" />
9494
<Compile Include="CompoundKeyRepositoryBase.BatchItem.cs" />
95+
<Compile Include="Helpers\ExpressionHelper.cs" />
9596
<Compile Include="IRepositoryBase.cs" />
9697
<Compile Include="LinqCompoundKeyRepositoryBase.cs" />
9798
<Compile Include="ICompoundKeyRepository.cs" />

SharpRepository.Tests/FetchStrategies/FetchStrategyTests.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,38 @@ public void FetchStrategy_May_Include_Multiple_References()
2020
strategy.IncludePaths.ShouldContain("PhoneNumbers");
2121
strategy.IncludePaths.Count().ShouldEqual(2);
2222
}
23+
24+
[Test]
25+
public void FetchStrategy_May_Include_String_Property_Names()
26+
{
27+
// This is a non-sense example because the Email property is not another table, but it works the exact same
28+
var strategy = new GenericFetchStrategy<Contact>()
29+
.Include("EmailAddresses")
30+
.Include("PhoneNumbers");
31+
32+
strategy.IncludePaths.ShouldContain("EmailAddresses");
33+
strategy.IncludePaths.ShouldContain("PhoneNumbers");
34+
strategy.IncludePaths.Count().ShouldEqual(2);
35+
}
36+
37+
[Test]
38+
public void FetchStrategy_May_Include_Multiple_Levels()
39+
{
40+
// This is a non-sense example because the Email property is not another table, but it works the exact same
41+
var strategy = new GenericFetchStrategy<Contact>()
42+
.Include(p => p.EmailAddresses.Select(e => e.Email));
43+
44+
strategy.IncludePaths.ShouldContain("EmailAddresses.Email");
45+
}
46+
47+
[Test]
48+
public void FetchStrategy_May_Include_Multiple_Levels_First_Syntax()
49+
{
50+
// This is a non-sense example because the Email property is not another table, but it works the exact same
51+
var strategy = new GenericFetchStrategy<Contact>()
52+
.Include(p => p.EmailAddresses.First().Email);
53+
54+
strategy.IncludePaths.ShouldContain("EmailAddresses.Email");
55+
}
2356
}
2457
}

0 commit comments

Comments
 (0)