Skip to content

Commit 5c1f70c

Browse files
Ihar YakimushIhar Yakimush
authored andcommitted
support Cast method
1 parent 8c6b945 commit 5c1f70c

File tree

11 files changed

+264
-41
lines changed

11 files changed

+264
-41
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,5 +147,13 @@ IQueryable<Product> solrLinq = solr.AsQueryable(setup =>
147147
- SingleAsync
148148
- SingleOrDefault
149149
- SingleOrDefaultAsync
150+
### Cast
151+
Cast to interface and get SolrQueryResults object of interface type
152+
```
153+
SolrQueryResults<IProduct> result = solrLinq
154+
.Where(p => p.Id != null)
155+
.Cast<IProduct>
156+
.ToSolrQueryResults();
157+
```
150158
## Nuget
151159
https://www.nuget.org/packages/SolrNet.Linq

SolrNet.Linq.IntegrationTests/DerivedProduct.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,9 @@ public class DerivedProduct : Product
44
{
55
public string Id2 { get; set; }
66
}
7+
8+
public class DerivedDerivedProduct : DerivedProduct
9+
{
10+
public string Id3 { get; set; }
11+
}
712
}

SolrNet.Linq.IntegrationTests/Product.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77

88
namespace SolrNet.Linq.IntegrationTests
99
{
10-
public class Product
10+
public interface IProduct
11+
{
12+
string Id { get; set; }
13+
ICollection<string> Categories { get; set; }
14+
}
15+
public class Product : IProduct
1116
{
1217
[DataMember]
1318
[SolrUniqueKey("id")]

SolrNet.Linq.IntegrationTests/SelectTests.cs

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,129 @@ public void AnonymousClassSolrResult()
141141
[Fact]
142142
public void SelectDerivedWithCast()
143143
{
144-
IQueryable<DerivedProduct> derivedProducts = Product.SolrOperations.Value.AsQueryable().Where(p => p.Id != null)
145-
.Select(p => new DerivedProduct{Id2 = p.Id}).Where(p => p.Id2 != null);
144+
IQueryable<DerivedProduct> derivedProducts = Product.SolrOperations.Value.AsQueryable()
145+
.Where(p => p.Id != null && p.Categories.Any())
146+
.Select(p => new DerivedProduct { Id2 = p.Id, Categories = p.Categories})
147+
.Where(p => p.Id2 != null);
146148

147149
IQueryable<Product> q2 = derivedProducts.Cast<Product>();
148150

149151
var t1 = q2.ToSolrQueryResults();
150152

151153
Assert.NotNull(t1);
152-
Assert.NotNull(t1[0].Id);
154+
Assert.NotNull(t1[0].Categories);
155+
Assert.Null(t1[0].Id);
156+
Assert.True(t1.NumFound > 0);
157+
}
158+
159+
[Fact]
160+
public void SelectDerivedWithCastToInterface()
161+
{
162+
IQueryable<DerivedProduct> derivedProducts = Product.SolrOperations.Value.AsQueryable()
163+
.Where(p => p.Id != null && p.Categories.Any())
164+
.Select(p => new DerivedProduct { Id2 = p.Id, Categories = p.Categories })
165+
.Where(p => p.Id2 != null);
166+
167+
IQueryable<IProduct> q2 = derivedProducts.Cast<IProduct>();
168+
169+
SolrQueryResults<IProduct> t1 = q2.ToSolrQueryResults();
170+
171+
Assert.NotNull(t1);
172+
Assert.NotNull(t1[0].Categories);
173+
Assert.Null(t1[0].Id);
174+
Assert.True(t1.NumFound > 0);
175+
}
176+
177+
[Fact]
178+
public void SelectDerivedWithDoubleCast()
179+
{
180+
IQueryable<DerivedDerivedProduct> derivedProducts = Product.SolrOperations.Value.AsQueryable()
181+
.Where(p => p.Id != null && p.Categories.Any())
182+
.Select(p => new DerivedDerivedProduct { Id2 = p.Id, Id3 = p.Id, Categories = p.Categories })
183+
.Where(p => p.Id3 != null);
184+
185+
IQueryable<Product> q2 = derivedProducts.Cast<DerivedProduct>().Cast<Product>();
186+
187+
var t1 = q2.ToSolrQueryResults();
188+
189+
Assert.NotNull(t1);
190+
Assert.NotNull(t1[0].Categories);
191+
Assert.Null(t1[0].Id);
192+
Assert.True(t1.NumFound > 0);
193+
}
194+
195+
[Fact]
196+
public void FilterAfterCastNotWorking()
197+
{
198+
IQueryable<DerivedDerivedProduct> derivedProducts = Product.SolrOperations.Value.AsQueryable()
199+
.Where(p => p.Id != null && p.Categories.Any())
200+
.Select(p => new DerivedDerivedProduct { Id2 = p.Id, Id3 = p.Id, Categories = p.Categories })
201+
.Where(p => p.Id3 != null);
202+
203+
IQueryable<Product> q2 = derivedProducts.Cast<Product>().Where(p => p.Id != null);
204+
205+
Assert.Throws<InvalidOperationException>(() => q2.ToSolrQueryResults());
206+
}
207+
208+
[Fact]
209+
public void CastToWrongTypeNotWorking()
210+
{
211+
IQueryable<DerivedProduct> derivedProducts = Product.SolrOperations.Value.AsQueryable()
212+
.Where(p => p.Id != null && p.Categories.Any())
213+
.Select(p => new DerivedProduct { Id2 = p.Id, Categories = p.Categories })
214+
.Where(p => p.Id2 != null);
215+
216+
var q2 = derivedProducts.Cast<DerivedDerivedProduct>();
217+
218+
Assert.Throws<InvalidCastException>(() => q2.ToSolrQueryResults());
219+
}
220+
221+
[Fact]
222+
public void MethodsWhichNotNeedContextWorksAfterCast()
223+
{
224+
IQueryable<DerivedDerivedProduct> derivedProducts = Product.SolrOperations.Value.AsQueryable()
225+
.Where(p => p.Id != null && p.Categories.Any())
226+
.Select(p => new DerivedDerivedProduct { Id2 = p.Id, Id3 = p.Id, Categories = p.Categories })
227+
.Where(p => p.Id3 != null);
228+
229+
Product q2 = derivedProducts.Cast<Product>().Skip(1).Take(1).FirstOrDefault();
230+
231+
Assert.NotNull(q2);
232+
}
233+
234+
[Fact]
235+
public void SelectMultipleCopiesOfField()
236+
{
237+
IQueryable<DerivedDerivedProduct> q1 = Product.SolrOperations.Value.AsQueryable()
238+
.Where(p => p.Id != null && p.Categories.Any())
239+
.Select(p => new DerivedDerivedProduct { Id2 = p.Id, Id3 = p.Id, Categories = p.Categories })
240+
.Where(p => p.Id2 != null);
241+
242+
var t1 = q1.ToSolrQueryResults();
243+
244+
Assert.NotNull(t1);
245+
Assert.NotNull(t1[0].Id2);
246+
Assert.Equal(t1[0].Id2, t1[0].Id3);
247+
Assert.NotNull(t1[0].Categories);
248+
Assert.Null(t1[0].Id);
249+
Assert.True(t1.NumFound > 0);
250+
}
251+
252+
[Fact]
253+
public void SelectAnyAfterSelect()
254+
{
255+
IQueryable<DerivedProduct> derivedProducts = Product.SolrOperations.Value.AsQueryable()
256+
.Where(p => p.Id != null)
257+
.Select(p => new DerivedProduct {Id2 = p.Id, Categories = p.Categories})
258+
.Where(p => p.Id2 != null && p.Categories.Any());
259+
260+
IQueryable<Product> q2 = derivedProducts.Cast<Product>();
261+
262+
var t1 = q2.ToSolrQueryResults();
263+
264+
Assert.NotNull(t1);
265+
Assert.NotNull(t1[0].Categories);
266+
Assert.Null(t1[0].Id);
153267
Assert.True(t1.NumFound > 0);
154268
}
155269

SolrNet.Linq/Expressions/ExpressionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public static bool HasMemberAccess(this Expression expression, Type type)
4343

4444
if (expression is MemberExpression me)
4545
{
46-
if (me.Member.DeclaringType == type)
46+
if (me.Member.DeclaringType!= null && me.Member.DeclaringType.IsAssignableFrom(type))
4747
{
4848
return true;
4949
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using SolrNet.Impl;
4+
5+
namespace SolrNet.Linq.Impl
6+
{
7+
public class CastResponseParser<TNew, TOld> : TransformationResponseParser<TNew, TOld>
8+
{
9+
public CastResponseParser(ISolrDocumentResponseParser<TOld> inner,
10+
ISolrDocumentResponseParser<Dictionary<string, object>> dictionaryParser) : base(inner, dictionaryParser)
11+
{
12+
}
13+
14+
protected override TNew GetResult(TOld old, Dictionary<string, object> dictionary)
15+
{
16+
return Enumerable.Repeat(old, 1).Cast<TNew>().Single();
17+
}
18+
}
19+
}

SolrNet.Linq/Impl/ExecuterExtensions.cs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
22
using System.Collections;
3+
using System.Collections.Generic;
34
using System.Linq;
45
using System.Linq.Expressions;
56
using System.Reflection;
67
using System.Runtime.CompilerServices;
8+
using System.Xml.Xsl;
79
using SolrNet.Impl;
810
using SolrNet.Impl.DocumentPropertyVisitors;
911
using SolrNet.Impl.FieldParsers;
@@ -35,10 +37,25 @@ public static IExecuter<TNew> ChangeType<TNew, TOld>(
3537
oldExecuter.GetFieldRecursive<ISolrDocumentResponseParser<TOld>>();
3638

3739
ISolrFieldParser fieldParser = oldParser.GetFieldRecursive<ISolrFieldParser>();
40+
SolrDictionaryDocumentResponseParser dictionaryParser = new SolrDictionaryDocumentResponseParser(fieldParser);
3841

39-
ISolrDocumentResponseParser<TNew> docParser = new SelectResponseParser<TNew, TOld>(oldParser,
40-
new SolrDictionaryDocumentResponseParser(fieldParser), expression,
41-
selectExpressionsCollection);
42+
ISolrDocumentResponseParser<TNew> docParser;
43+
44+
if (expression.Method.DeclaringType == typeof(Queryable) && expression.Method.Name == nameof(Queryable.Cast))
45+
{
46+
docParser = new CastResponseParser<TNew, TOld>(oldParser, dictionaryParser);
47+
}
48+
else if (expression.Method.DeclaringType == typeof(Queryable) && expression.Method.Name == nameof(Queryable.Select))
49+
{
50+
docParser = new SelectResponseParser<TNew, TOld>(oldParser,
51+
dictionaryParser, expression,
52+
selectExpressionsCollection);
53+
}
54+
else
55+
{
56+
throw new InvalidOperationException(
57+
$"Unable to change query type from {typeof(TOld).Name} to {typeof(TNew).Name}. Method {expression.Method.Name} not supported");
58+
}
4259

4360
ISolrAbstractResponseParser<TNew> parser = new DefaultResponseParser<TNew>(docParser);
4461

@@ -66,8 +83,13 @@ internal static T GetFieldRecursive<T>(this object instance, int limit = 5) wher
6683
BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
6784

6885
Type type = instance.GetType();
69-
FieldInfo[] fields = type.GetFields(bindFlags);
70-
86+
List<FieldInfo> fields = new List<FieldInfo>();
87+
fields.AddRange(type.GetFields(bindFlags));
88+
while (type.BaseType != null && type.BaseType != typeof(object))
89+
{
90+
type = type.BaseType;
91+
fields.AddRange(type.GetFields(bindFlags));
92+
}
7193

7294
foreach (FieldInfo field in fields
7395
.OrderBy(info => typeof(T).IsAssignableFrom(info.FieldType) ? 0 : 1)
@@ -84,16 +106,29 @@ internal static T GetFieldRecursive<T>(this object instance, int limit = 5) wher
84106
{
85107
if (limit > 0)
86108
{
87-
if (fieldValue is IEnumerable enumerable)
109+
if (fieldValue is IEnumerable enumerable && !(fieldValue is IQueryable))
88110
{
111+
int loopLimit = 1000;
89112
foreach (object obj in enumerable)
90113
{
114+
if (obj == null)
115+
{
116+
continue;
117+
}
118+
91119
T inner = obj.GetFieldRecursive<T>(limit - 1);
92120

93121
if (inner != null)
94122
{
95123
return inner;
96124
}
125+
126+
loopLimit--;
127+
128+
if (loopLimit < 0)
129+
{
130+
break;
131+
}
97132
}
98133
}
99134
else

SolrNet.Linq/Impl/SelectResponseParser.cs

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,22 @@
88

99
namespace SolrNet.Linq.Impl
1010
{
11-
public class SelectResponseParser<TNew,TOld> : ISolrDocumentResponseParser<TNew>
11+
public class SelectResponseParser<TNew,TOld> : TransformationResponseParser<TNew, TOld>
1212
{
13-
private readonly ISolrDocumentResponseParser<TOld> _inner;
14-
private readonly ISolrDocumentResponseParser<Dictionary<string, object>> _dictionaryParser;
1513
private readonly MethodCallExpression _selectCall;
1614
private readonly SelectExpressionsCollection _selectState;
1715

18-
public SelectResponseParser(ISolrDocumentResponseParser<TOld> inner, ISolrDocumentResponseParser<Dictionary<string, object>> dictionaryParser, MethodCallExpression selectCall, SelectExpressionsCollection selectState)
16+
public SelectResponseParser(
17+
ISolrDocumentResponseParser<TOld> inner,
18+
ISolrDocumentResponseParser<Dictionary<string, object>> dictionaryParser,
19+
MethodCallExpression selectCall,
20+
SelectExpressionsCollection selectState):base(inner, dictionaryParser)
1921
{
20-
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
21-
_dictionaryParser = dictionaryParser ?? throw new ArgumentNullException(nameof(dictionaryParser));
22-
_selectCall = selectCall;
23-
_selectState = selectState;
24-
}
25-
public IList<TNew> ParseResults(XElement parentNode)
26-
{
27-
if (parentNode == null)
28-
return null;
29-
30-
List<TNew> result = new List<TNew>();
31-
var docs = this._dictionaryParser.ParseResults(parentNode);
32-
IList<TOld> olds = this._inner.ParseResults(parentNode);
33-
34-
for (int i = 0; i < olds.Count; i++)
35-
{
36-
result.Add(this.GetResult(olds[i], docs[i]));
37-
}
38-
39-
return result;
40-
}
22+
_selectCall = selectCall ?? throw new ArgumentNullException(nameof(selectCall));
23+
_selectState = selectState ?? throw new ArgumentNullException(nameof(selectState));
24+
}
4125

42-
private TNew GetResult(TOld old, Dictionary<string, object> dictionary)
26+
protected override TNew GetResult(TOld old, Dictionary<string, object> dictionary)
4327
{
4428
ReplaceCalculatedVisitor visitor = new ReplaceCalculatedVisitor(this._selectState, dictionary);
4529

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq.Expressions;
4+
using System.Xml.Linq;
5+
using SolrNet.Impl;
6+
using SolrNet.Linq.Expressions;
7+
8+
namespace SolrNet.Linq.Impl
9+
{
10+
public abstract class TransformationResponseParser<TNew, TOld> : ISolrDocumentResponseParser<TNew>
11+
{
12+
private readonly ISolrDocumentResponseParser<TOld> _inner;
13+
private readonly ISolrDocumentResponseParser<Dictionary<string, object>> _dictionaryParser;
14+
15+
protected TransformationResponseParser(ISolrDocumentResponseParser<TOld> inner,
16+
ISolrDocumentResponseParser<Dictionary<string, object>> dictionaryParser)
17+
{
18+
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
19+
_dictionaryParser = dictionaryParser ?? throw new ArgumentNullException(nameof(dictionaryParser));
20+
}
21+
22+
public IList<TNew> ParseResults(XElement parentNode)
23+
{
24+
if (parentNode == null)
25+
return null;
26+
27+
List<TNew> result = new List<TNew>();
28+
var docs = this._dictionaryParser.ParseResults(parentNode);
29+
IList<TOld> olds = this._inner.ParseResults(parentNode);
30+
31+
for (int i = 0; i < olds.Count; i++)
32+
{
33+
result.Add(this.GetResult(olds[i], docs[i]));
34+
}
35+
36+
return result;
37+
}
38+
39+
protected abstract TNew GetResult(TOld old, Dictionary<string, object> dictionary);
40+
}
41+
}

SolrNet.Linq/SolrNet.Linq.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
<PackageTags>solr solrnet linq</PackageTags>
1313
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1414
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
15-
<Version>1.3.0</Version>
16-
<PackageReleaseNotes>Added support for Select() method. Compatible with OData way of building select expression.
17-
Added support for Any(), Count(), LongCount() methods and its async versions</PackageReleaseNotes>
15+
<Version>1.4.0</Version>
16+
<PackageReleaseNotes>Improved support of Select() method. Compatible with OData way of building select expression.
17+
Added support for Cast&lt;T&gt;() method.</PackageReleaseNotes>
18+
<AssemblyVersion>1.4.0.0</AssemblyVersion>
19+
<FileVersion>1.4.0.0</FileVersion>
1820
</PropertyGroup>
1921

2022
<ItemGroup>

0 commit comments

Comments
 (0)