Skip to content

Commit 69a0855

Browse files
Ihar YakimushIhar Yakimush
authored andcommitted
support select with MemberInitExpression
1 parent 053966c commit 69a0855

File tree

9 files changed

+277
-28
lines changed

9 files changed

+277
-28
lines changed
Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,76 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
4+
using System.Runtime.Serialization;
5+
using SolrNet.Attributes;
6+
using SolrNet.Commands.Parameters;
37
using Xunit;
48

59
namespace SolrNet.Linq.IntegrationTests
610
{
11+
public class Product2
12+
{
13+
public string Id { get; set; }
14+
15+
public ICollection<string> Categories { get; set; }
16+
17+
public decimal Price { get; set; }
18+
19+
public double Qwe { get; set; }
20+
}
721
public class SelectTests
822
{
923
[Fact]
1024
public void AnonymousClass()
1125
{
12-
var t1 = Product.SolrOperations.Value.AsQueryable().Where(p => p.Id != null)
26+
var t1 = Product.SolrOperations.Value.AsQueryable(lo => lo.SetupQueryOptions = qo =>
27+
{
28+
Assert.Equal("Qwe:pow(2,2)", qo.Fields.ElementAt(0));
29+
Assert.Equal("Id:id", qo.Fields.ElementAt(1));
30+
Assert.Equal("Price:price", qo.Fields.ElementAt(2));
31+
Assert.Equal("Categories:cat", qo.Fields.ElementAt(3));
32+
}).Where(p => p.Id != null)
1333
.Select(p => new {p.Id, p.Price, p.Categories, Qwe = Math.Pow(2,2) })
1434
.Where(arg => arg.Categories.Any(s => s == "electronics"))
1535
.OrderBy(arg => arg.Id)
1636
.FirstOrDefault();
1737

1838
Assert.NotNull(t1);
1939
}
40+
41+
[Fact]
42+
public void Product2()
43+
{
44+
var t1 = Product.SolrOperations.Value.AsQueryable(lo => lo.SetupQueryOptions = qo =>
45+
{
46+
Assert.Equal(4, qo.Fields.Count);
47+
Assert.Equal("Qwe:pow(2,2)", qo.Fields.ElementAt(0));
48+
Assert.Equal("Id:id", qo.Fields.ElementAt(1));
49+
Assert.Equal("Price:price", qo.Fields.ElementAt(2));
50+
Assert.Equal("Categories:cat", qo.Fields.ElementAt(3));
51+
}).Where(p => p.Id != null)
52+
.Select(p => new Product2 {Id = p.Id, Price = p.Price, Categories = p.Categories, Qwe = Math.Pow(2, 2)})
53+
.Where(arg => arg.Categories.Any(s => s == "electronics"))
54+
.OrderBy(arg => arg.Id)
55+
.FirstOrDefault();
56+
57+
Assert.NotNull(t1);
58+
59+
var t2 = Product.SolrOperations.Value.AsQueryable().Where(p => p.Id != null)
60+
.Where(arg => arg.Categories.Any(s => s == "electronics"))
61+
.OrderBy(arg => arg.Id)
62+
.FirstOrDefault();
63+
64+
Assert.NotNull(t2);
65+
Assert.Equal(t2.Id, t1.Id);
66+
67+
SolrQueryResults<Product2> t3 = Product.SolrOperations.Value.AsQueryable().Where(p => p.Id != null)
68+
.Select(p => new Product2 {Id = p.Id, Price = p.Price, Categories = p.Categories, Qwe = Math.Pow(2, 2)})
69+
.Where(arg => arg.Categories.Any(s => s == "electronics"))
70+
.OrderBy(arg => arg.Id).Take(1).ToSolrQueryResults();
71+
72+
Assert.Single(t3);
73+
Assert.Equal(t1.Id, t3.Single().Id);
74+
}
2075
}
2176
}

SolrNet.Linq/Expressions/Context/SelectContext.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,59 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Linq.Expressions;
45
using System.Reflection;
56

67
namespace SolrNet.Linq.Expressions.Context
78
{
89
public class SelectContext : MemberContext
910
{
10-
public NewExpression Expression { get; }
11+
public NewExpression NewExpression { get; }
1112
public MemberContext ParentContext { get; }
1213

1314
public Dictionary<MemberInfo, string> Members { get; } = new Dictionary<MemberInfo, string>();
1415
public Dictionary<MemberInfo, string> Aliases { get; } = new Dictionary<MemberInfo, string>();
1516

1617
public SelectContext(NewExpression expression, MemberContext parentContext)
1718
{
18-
Expression = expression ?? throw new ArgumentNullException(nameof(expression));
19+
NewExpression = expression ?? throw new ArgumentNullException(nameof(expression));
1920
ParentContext = parentContext ?? throw new ArgumentNullException(nameof(parentContext));
2021

2122
for (int i = 0; i < expression.Arguments.Count; i++)
2223
{
2324
Expression argument = expression.Arguments[i];
2425
if (argument.NodeType != ExpressionType.MemberAccess)
2526
{
26-
string value = $"v{i}:{parentContext.GetSolrMemberProduct(argument)}";
27-
Aliases.Add(expression.Members[i], value);
27+
Aliases.Add(expression.Members[i], parentContext.GetSolrMemberProduct(argument));
2828
}
2929
else
3030
{
3131
Members.Add(expression.Members[i], parentContext.GetSolrMemberProduct(argument, true));
3232
}
3333
}
3434
}
35+
36+
public SelectContext(MemberInitExpression expression, MemberContext parentContext)
37+
{
38+
NewExpression = expression?.NewExpression ?? throw new ArgumentNullException(nameof(expression));
39+
ParentContext = parentContext ?? throw new ArgumentNullException(nameof(parentContext));
40+
41+
foreach (MemberAssignment binding in expression.Bindings.OfType<MemberAssignment>())
42+
{
43+
if (binding.Expression.NodeType != ExpressionType.MemberAccess)
44+
{
45+
Aliases.Add(binding.Member, parentContext.GetSolrMemberProduct(binding.Expression));
46+
}
47+
else
48+
{
49+
Members.Add(binding.Member, parentContext.GetSolrMemberProduct(binding.Expression, true));
50+
}
51+
}
52+
}
53+
3554
public override bool HasMemberAccess(Expression expression)
3655
{
37-
bool hasMemberAccess = expression.HasMemberAccess(this.Expression.Type);
56+
bool hasMemberAccess = expression.HasMemberAccess(this.NewExpression.Type);
3857
return hasMemberAccess;
3958
}
4059

SolrNet.Linq/Expressions/NodeTypeHelpers/SelectMethod.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,19 @@ public static bool TryVisitSelect(this MethodCallExpression node, QueryOptions o
2525

2626
if (lambda.Body is NewExpression selectMember)
2727
{
28-
newContext = new SelectContext(selectMember, context);
28+
newContext = new SelectContext(selectMember, context);
29+
}
2930

30-
foreach (var pairValue in newContext.Aliases.Values.Concat(newContext.Members.Values))
31+
if (lambda.Body is MemberInitExpression memberInit)
32+
{
33+
newContext = new SelectContext(memberInit, context);
34+
}
35+
36+
if (newContext != null)
37+
{
38+
foreach (var pairValue in newContext.Aliases.Concat(newContext.Members))
3139
{
32-
options.Fields.Add(pairValue);
40+
options.Fields.Add($"{pairValue.Key.Name}:{pairValue.Value}");
3341
}
3442

3543
return result;
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
using SolrNet.Impl;
5+
using SolrNet.Impl.DocumentPropertyVisitors;
6+
using SolrNet.Impl.FieldParsers;
7+
using SolrNet.Impl.ResponseParsers;
8+
using SolrNet.Mapping;
9+
10+
namespace SolrNet.Linq.Impl
11+
{
12+
public static class ExecuterExtensions
13+
{
14+
public static IExecuter<TNew> ChangeType<TNew, TOld>(this IExecuter<TOld> executer)
15+
{
16+
try
17+
{
18+
SolrQueryExecuter<TOld> oldExecuter = executer.Executer;
19+
20+
ISolrConnection connection = oldExecuter.GetSingleField<ISolrConnection>();
21+
ISolrQuerySerializer serializer = oldExecuter.GetSingleField<ISolrQuerySerializer>();
22+
ISolrFacetQuerySerializer facetQuerySerializer = oldExecuter.GetSingleField<ISolrFacetQuerySerializer>();
23+
24+
//TODO:
25+
IReadOnlyMappingManager mapper = new AllPropertiesMappingManager();
26+
27+
ISolrFieldParser sfp = new DefaultFieldParser();
28+
ISolrDocumentPropertyVisitor sdpv = new DefaultDocumentVisitor(mapper, sfp);
29+
30+
ISolrAbstractResponseParser<TNew> parser =
31+
new DefaultResponseParser<TNew>(
32+
new SolrDocumentResponseParser<TNew>(mapper, sdpv, new SolrDocumentActivator<TNew>()));
33+
34+
SolrQueryExecuter<TNew> newExecuter = new SolrQueryExecuter<TNew>(parser, connection, serializer,
35+
facetQuerySerializer,
36+
new SolrMoreLikeThisHandlerQueryResultsParser<TNew>(Enumerable.Repeat(parser, 1)));
37+
38+
newExecuter.DefaultHandler = oldExecuter.DefaultHandler;
39+
newExecuter.DefaultRows = oldExecuter.DefaultRows;
40+
newExecuter.MoreLikeThisHandler = oldExecuter.MoreLikeThisHandler;
41+
42+
return new SelectQueryExecutor<TNew>(newExecuter);
43+
}
44+
catch (Exception e)
45+
{
46+
throw new InvalidOperationException(
47+
$"Unable to change solr query executer from {typeof(TOld)} to {typeof(TNew)}.", e);
48+
}
49+
}
50+
51+
internal static T GetSingleField<T>(this object instance)
52+
{
53+
if (instance == null) throw new ArgumentNullException(nameof(instance));
54+
55+
BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
56+
| BindingFlags.Static;
57+
58+
Type type = instance.GetType();
59+
60+
FieldInfo field = type.GetFields(bindFlags).Single(info => info.FieldType == typeof(T));
61+
62+
return (T)field.GetValue(instance);
63+
}
64+
}
65+
}

SolrNet.Linq/Impl/IExecuter.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Threading.Tasks;
2+
using SolrNet.Commands.Parameters;
3+
using SolrNet.Impl;
4+
5+
namespace SolrNet.Linq.Impl
6+
{
7+
public interface IExecuter<T>
8+
{
9+
SolrQueryResults<T> Execute(ISolrQuery q, QueryOptions options);
10+
11+
Task<SolrQueryResults<T>> ExecuteAsync(ISolrQuery q, QueryOptions options);
12+
13+
SolrQueryExecuter<T> Executer { get; }
14+
}
15+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using SolrNet.Commands.Parameters;
4+
using SolrNet.Impl;
5+
6+
namespace SolrNet.Linq.Impl
7+
{
8+
public class SelectQueryExecutor<T> : IExecuter<T>
9+
{
10+
private readonly SolrQueryExecuter<T> _executer;
11+
12+
public SelectQueryExecutor(SolrQueryExecuter<T> executer)
13+
{
14+
_executer = executer ?? throw new ArgumentNullException(nameof(executer));
15+
}
16+
public SolrQueryResults<T> Execute(ISolrQuery q, QueryOptions options)
17+
{
18+
return this._executer.Execute(q, options);
19+
}
20+
21+
public Task<SolrQueryResults<T>> ExecuteAsync(ISolrQuery q, QueryOptions options)
22+
{
23+
return this._executer.ExecuteAsync(q, options);
24+
}
25+
26+
public SolrQueryExecuter<T> Executer => this._executer;
27+
}
28+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using SolrNet.Commands.Parameters;
4+
using SolrNet.Impl;
5+
6+
namespace SolrNet.Linq.Impl
7+
{
8+
public class SolrQueryExecuterWrapperBasicOperations<T> : IExecuter<T>
9+
{
10+
private readonly ISolrBasicReadOnlyOperations<T> _basicOperations;
11+
12+
public SolrQueryExecuterWrapperBasicOperations(ISolrBasicReadOnlyOperations<T> basicOperations)
13+
{
14+
_basicOperations = basicOperations ?? throw new ArgumentNullException(nameof(basicOperations));
15+
}
16+
public SolrQueryResults<T> Execute(ISolrQuery q, QueryOptions options)
17+
{
18+
return this._basicOperations.Query(q, options);
19+
}
20+
21+
public Task<SolrQueryResults<T>> ExecuteAsync(ISolrQuery q, QueryOptions options)
22+
{
23+
return this._basicOperations.QueryAsync(q, options);
24+
}
25+
26+
public SolrQueryExecuter<T> Executer
27+
{
28+
get
29+
{
30+
if (this._basicOperations is SolrBasicServer<T> sbs)
31+
{
32+
return sbs.GetSingleField<ISolrQueryExecuter<T>>() as SolrQueryExecuter<T>;
33+
}
34+
35+
if (this._basicOperations is SolrServer<T> srv)
36+
{
37+
if (srv.GetSingleField<ISolrBasicOperations<T>>() is SolrBasicServer<T> sbs2)
38+
{
39+
return sbs2.GetSingleField<ISolrQueryExecuter<T>>() as SolrQueryExecuter<T>;
40+
}
41+
}
42+
43+
throw new InvalidOperationException(
44+
$"Unable to get executer from current instance of type {_basicOperations.GetType()}");
45+
}
46+
}
47+
}
48+
}

SolrNet.Linq/SolrOperationsExtensions.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Threading.Tasks;
55
using SolrNet.Commands.Parameters;
6+
using SolrNet.Linq.Impl;
67

78
namespace SolrNet.Linq
89
{
@@ -12,22 +13,33 @@ public static IQueryable<T> AsQueryable<T>(this ISolrBasicReadOnlyOperations<T>
1213
{
1314
SolrNetLinqOptions o = new SolrNetLinqOptions();
1415
setupOptions?.Invoke(o);
15-
return new SolrQuery<T>(new SolrQueryProvider<T>(operations, o, null));
16+
return new SolrQuery<T>(new SolrQueryProvider<T>(
17+
new SolrQueryExecuterWrapperBasicOperations<T>(operations),
18+
o, null));
1619
}
1720

21+
//public static IQueryable<T> AsQueryable<T>(this ISolrQueryExecuter<T> operations, Action<SolrNetLinqOptions> setupOptions = null)
22+
//{
23+
// SolrNetLinqOptions o = new SolrNetLinqOptions();
24+
// setupOptions?.Invoke(o);
25+
// return new SolrQuery<T>(new SolrQueryProvider<T>(
26+
// operations,
27+
// o, null));
28+
//}
29+
1830
public static SolrQueryResults<T> ToSolrQueryResults<T>(this IQueryable<T> queryable)
1931
{
2032
return (SolrQueryResults<T>)queryable.Provider.Execute(queryable.Expression);
2133
}
2234

2335
public static Task<SolrQueryResults<T>> ToSolrQueryResultsAsync<T>(this IQueryable<T> queryable)
24-
{
36+
{
2537
if (queryable.Provider is IAsyncProvider<T> solrProvider)
2638
{
2739
return solrProvider.ExecuteAsync<SolrQueryResults<T>>(queryable.Expression);
2840
}
2941

3042
return Task.FromResult(ToSolrQueryResults(queryable));
31-
}
43+
}
3244
}
3345
}

0 commit comments

Comments
 (0)