Skip to content

Commit ed6d68f

Browse files
Merge pull request #653 from johelvisguzman/GH-652
(GH-652) Allow to fetch one-directional nav properties within fetch helper
2 parents 7168113 + e7405df commit ed6d68f

File tree

9 files changed

+320
-173
lines changed

9 files changed

+320
-173
lines changed

src/DotNetToolkit.Repository/Configuration/Conventions/Internal/ForeignKeyConventionHelper.cs

Lines changed: 78 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,85 +13,125 @@
1313

1414
internal class ForeignKeyConventionHelper
1515
{
16-
private static readonly ConcurrentDictionary<PropertyInfo, Result> _foreignKeyCache = new ConcurrentDictionary<PropertyInfo, Result>();
16+
private static readonly ConcurrentDictionary<Tuple<Type, Type, string>, Result> _foreignKeyCache = new ConcurrentDictionary<Tuple<Type, Type, string>, Result>();
1717

1818
public static Result GetForeignKeyPropertyInfos([NotNull] PropertyInfo pi)
1919
{
2020
Guard.NotNull(pi, nameof(pi));
2121

22-
if (!_foreignKeyCache.TryGetValue(pi, out var result))
22+
var sourceType = pi.DeclaringType;
23+
var targetType = pi.PropertyType.GetGenericTypeOrDefault();
24+
25+
return GetForeignKeyPropertyInfos(sourceType, targetType, pi.Name);
26+
}
27+
28+
public static Result GetForeignKeyPropertyInfos([NotNull] Type sourceType, [NotNull] Type targetType, [NotNull] string navPiName)
29+
{
30+
Guard.NotNull(sourceType, nameof(sourceType));
31+
Guard.NotNull(targetType, nameof(targetType));
32+
33+
var key = Tuple.Create(sourceType, targetType, navPiName);
34+
35+
if (!_foreignKeyCache.TryGetValue(key, out var result))
2336
{
24-
result = GetForeignKeyPropertyInfosCore(pi);
25-
_foreignKeyCache.TryAdd(pi, result);
37+
result = GetForeignKeyPropertyInfosCore(sourceType, targetType, navPiName);
38+
_foreignKeyCache.TryAdd(key, result);
2639
}
2740

2841
return result;
2942
}
3043

31-
private static Result GetForeignKeyPropertyInfosCore(PropertyInfo pi)
44+
private static Result GetForeignKeyPropertyInfosCore(Type sourceType, Type targetType, string navPiName)
3245
{
33-
var foreignType = pi.PropertyType.GetGenericTypeOrDefault();
34-
var declaringType = pi.DeclaringType;
35-
36-
if (foreignType.IsEnumerable() || declaringType.IsEnumerable())
46+
if (sourceType.IsEnumerable() || targetType.IsEnumerable())
3747
return null;
3848

3949
bool foundInSource;
4050

41-
if (TryGetForeignKeyPropertyInfos(foreignType, declaringType,
51+
if (TryGetForeignKeyPropertyInfos(
52+
sourceType, targetType, navPiName,
53+
searchNavPiInSource: true,
4254
out var foreignKeyPropertyInfos,
4355
out var foreignNavPropertyInfo,
4456
out var adjacentNavPropertyInfo))
4557
{
46-
foundInSource = false;
47-
adjacentNavPropertyInfo = pi;
58+
foundInSource = true;
4859
}
49-
else if (TryGetForeignKeyPropertyInfos(declaringType, foreignType,
60+
else if (TryGetForeignKeyPropertyInfos(
61+
targetType, sourceType, navPiName,
62+
searchNavPiInSource: false,
5063
out foreignKeyPropertyInfos,
5164
out foreignNavPropertyInfo,
5265
out adjacentNavPropertyInfo))
5366
{
54-
foundInSource = true;
67+
foundInSource = false;
5568
}
5669
else
5770
{
5871
return null;
5972
}
6073

61-
var rightNavPi = pi;
62-
var rightPiType = rightNavPi.PropertyType.GetGenericTypeOrDefault();
63-
var rightKeysToJoinOn = foundInSource
64-
? PrimaryKeyConventionHelper.GetPrimaryKeyPropertyInfos(rightPiType)
65-
: foreignKeyPropertyInfos;
66-
67-
var leftNavPi = foreignNavPropertyInfo;
68-
var leftPiType = leftNavPi.PropertyType.GetGenericTypeOrDefault();
69-
var leftKeysToJoinOn = foundInSource
70-
? foreignKeyPropertyInfos
71-
: PrimaryKeyConventionHelper.GetPrimaryKeyPropertyInfos(leftPiType);
74+
// if left is null, its probably because there is no bi-directional nav properties in both types
75+
PropertyInfo[] leftKeysToJoinOn = null;
76+
var leftNavPi = foundInSource ? adjacentNavPropertyInfo : foreignNavPropertyInfo;
77+
if (leftNavPi != null)
78+
{
79+
var leftPiType = leftNavPi.PropertyType.GetGenericTypeOrDefault();
80+
leftKeysToJoinOn = foundInSource
81+
? foreignKeyPropertyInfos
82+
: PrimaryKeyConventionHelper.GetPrimaryKeyPropertyInfos(leftPiType);
83+
}
7284

73-
var newLeftNavPi = foundInSource ? adjacentNavPropertyInfo : leftNavPi;
85+
PropertyInfo[] rightKeysToJoinOn = null;
86+
var rightNavPi = foundInSource ? foreignNavPropertyInfo : adjacentNavPropertyInfo;
87+
if (rightNavPi != null)
88+
{
89+
var rightPiType = rightNavPi.PropertyType.GetGenericTypeOrDefault();
90+
rightKeysToJoinOn = foundInSource
91+
? PrimaryKeyConventionHelper.GetPrimaryKeyPropertyInfos(rightPiType)
92+
: foreignKeyPropertyInfos;
93+
}
94+
// might be doing a backgward tarversal, which is the only reason why
95+
// left might have something but the right side wont
96+
else if (leftNavPi != null)
97+
{
98+
rightKeysToJoinOn = foreignKeyPropertyInfos;
99+
}
74100

75-
return new Result(newLeftNavPi, leftKeysToJoinOn, rightNavPi, rightKeysToJoinOn);
101+
return new Result(leftNavPi, leftKeysToJoinOn, rightNavPi, rightKeysToJoinOn);
76102
}
77103

78-
private static bool TryGetForeignKeyPropertyInfos(Type source, Type target,
104+
private static bool TryGetForeignKeyPropertyInfos(Type sourceType, Type targetType, string navPiName, bool searchNavPiInSource,
79105
out PropertyInfo[] foreignKeyPropertyInfos,
80106
out PropertyInfo foreignNavPropertyInfo,
81107
out PropertyInfo adjacentNavPropertyInfo)
82108
{
83-
var propsFromSource = source.GetRuntimeProperties().Where(ModelConventionHelper.IsColumnMapped).ToList();
84-
var propsFromTarget = target.GetRuntimeProperties().Where(ModelConventionHelper.IsColumnMapped).ToList();
85-
86-
var foreignNavPi = propsFromSource.FirstOrDefault(x => x.PropertyType == target);
87-
var adjacentNavPi = propsFromTarget.FirstOrDefault(x => x.PropertyType == source);
88-
89-
var foreignKeyPiList = new List<PropertyInfo>();
90-
91109
adjacentNavPropertyInfo = null;
92110
foreignKeyPropertyInfos = null;
93111
foreignNavPropertyInfo = null;
94112

113+
var propsFromSource = sourceType.GetRuntimeProperties().Where(ModelConventionHelper.IsColumnMapped).ToList();
114+
var propsFromTarget = targetType.GetRuntimeProperties().Where(ModelConventionHelper.IsColumnMapped).ToList();
115+
116+
PropertyInfo foreignNavPi = null;
117+
PropertyInfo adjacentNavPi = null;
118+
119+
if (!string.IsNullOrEmpty(navPiName))
120+
{
121+
if (searchNavPiInSource)
122+
{
123+
foreignNavPi = propsFromSource.FirstOrDefault(x => x.Name == navPiName);
124+
adjacentNavPi = propsFromTarget.FirstOrDefault(x => x.PropertyType == sourceType);
125+
}
126+
else
127+
{
128+
adjacentNavPi = propsFromTarget.FirstOrDefault(x => x.Name == navPiName);
129+
foreignNavPi = propsFromSource.FirstOrDefault(x => x.PropertyType == targetType);
130+
}
131+
}
132+
133+
var foreignKeyPiList = new List<PropertyInfo>();
134+
95135
if (foreignNavPi != null)
96136
{
97137
// Gets by checking the annotations in source
@@ -108,7 +148,7 @@ private static bool TryGetForeignKeyPropertyInfos(Type source, Type target,
108148
string.Format(
109149
Resources.ForeignKeyAttributeOnPropertyNotFoundOnDependentType,
110150
propertyInfosWithForeignKey.Name,
111-
source.FullName,
151+
sourceType.FullName,
112152
foreignKeyAttributeName));
113153
}
114154
}
@@ -130,7 +170,7 @@ private static bool TryGetForeignKeyPropertyInfos(Type source, Type target,
130170
}
131171

132172
// Try to find by naming convention
133-
var primaryKeyPropertyInfos = PrimaryKeyConventionHelper.GetPrimaryKeyPropertyInfos(target);
173+
var primaryKeyPropertyInfos = PrimaryKeyConventionHelper.GetPrimaryKeyPropertyInfos(targetType);
134174

135175
if (!foreignKeyPiList.Any() && primaryKeyPropertyInfos.Any())
136176
{

src/DotNetToolkit.Repository/Extensions/EnumerableExtensions.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace DotNetToolkit.Repository.Extensions
22
{
33
using Configuration.Conventions;
4+
using Extensions.Internal;
45
using JetBrains.Annotations;
56
using Query;
67
using Query.Strategies;
@@ -102,10 +103,10 @@ public static IEnumerable<T> ApplyFetchingOptions<T>([NotNull] this IEnumerable<
102103
Guard.NotNull(innerQueryCallback, nameof(innerQueryCallback));
103104

104105
var fetchingPaths = fetchStrategy.DefaultIfFetchStrategyEmpty().PropertyPaths.ToList();
105-
var fetchHelper = new FetchHelper<T>(innerQueryCallback);
106+
var fetchHelper = new FetchHelper(innerQueryCallback);
106107

107108
if (fetchingPaths.Any())
108-
query = fetchingPaths.Aggregate(query, (current, path) => fetchHelper.Include(current, path));
109+
query = fetchingPaths.NormalizePropertyPaths().Aggregate(query, (current, path) => fetchHelper.Include(current, path));
109110

110111
return query;
111112
}

src/DotNetToolkit.Repository/Extensions/Internal/FetchQueryStrategyExtensions.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Query.Strategies;
55
using System;
66
using System.Collections.Generic;
7+
using System.Linq;
78
using System.Linq.Expressions;
89
using System.Reflection;
910
using System.Text;
@@ -12,6 +13,65 @@
1213
// https://github.com/SharpRepository/SharpRepository/tree/master/SharpRepository.Repository/FetchStrategies/FetchQueryStrategyExtensions.cs
1314
internal static class FetchQueryStrategyExtensions
1415
{
16+
/// <summary>
17+
/// Returns a new array of properties which have been merged by their corresponding sequence, and without and duplicates.
18+
/// </summary>
19+
/// <param name="paths">The paths to normalize.</param>
20+
/// <returns>A new array of properties which have been merged by their corresponding sequence, and without and duplicates</returns>
21+
public static string[] NormalizePropertyPaths([NotNull] this IEnumerable<string> paths)
22+
{
23+
Guard.NotNull(paths, nameof(paths));
24+
25+
if (paths != null)
26+
{
27+
var orderedPaths = paths
28+
.Select(x => new { Path = x, Props = x.Split('.') })
29+
.OrderBy(x => x.Props.Length)
30+
.ThenBy(x => x.Path)
31+
.ToArray();
32+
33+
var pathsSeqDict = new Dictionary<string, bool>();
34+
35+
for (int i = 0; i < orderedPaths.Length; i++)
36+
{
37+
var p1 = orderedPaths[i];
38+
string currPathSeq = orderedPaths[i].Path;
39+
40+
if (pathsSeqDict.ContainsKey(currPathSeq))
41+
{
42+
continue;
43+
}
44+
45+
string[] currPaths = p1.Props;
46+
bool foundNextSeq = false;
47+
48+
for (int j = 1; j < orderedPaths.Length; j++)
49+
{
50+
var p2 = orderedPaths[j];
51+
string nextPathSeq = p2.Path;
52+
string[] nextPaths = p2.Props;
53+
54+
// compare the first few properties in both list
55+
if (currPaths.Length == nextPaths.Length - 1 &&
56+
currPaths.SequenceEqual(nextPaths.Take(nextPaths.Length - 1)))
57+
{
58+
currPathSeq = nextPathSeq;
59+
currPaths = nextPaths;
60+
foundNextSeq = true;
61+
}
62+
}
63+
64+
if (foundNextSeq || currPaths.Length == 1)
65+
pathsSeqDict[currPathSeq] = true;
66+
}
67+
68+
return pathsSeqDict.Keys.ToArray();
69+
}
70+
71+
return new string[0] { };
72+
73+
}
74+
1575
/// <summary>
1676
/// 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).
1777
/// </summary>

0 commit comments

Comments
 (0)