Skip to content

Commit 2438947

Browse files
committed
Resolve #8 - Specification expressions may now be transformed to new types
1 parent 6c04cf2 commit 2438947

15 files changed

Lines changed: 601 additions & 2 deletions

CSF.Specifications.Tests/CSF.Specifications.Tests.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@
1919
</PackageReference>
2020
<PackageReference Include="NunitXml.TestLogger" Version="2.1.41" />
2121
<PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
22+
<PackageReference Include="NHibernate" Version="4.0.0.4000">
23+
<NoWarn>NU1701</NoWarn>
24+
</PackageReference>
25+
<PackageReference Include="Iesi.Collections" Version="4.0.0.4000">
26+
<NoWarn>NU1701</NoWarn>
27+
</PackageReference>
28+
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.6.0" />
29+
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.112" />
30+
<PackageReference Include="System.Security.Permissions" Version="4.6.0" />
31+
<PackageReference Include="System.ServiceModel.Primitives" Version="4.6.0">
32+
<NoWarn>NU1701</NoWarn>
33+
</PackageReference>
2234
</ItemGroup>
2335

2436
<ItemGroup>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// PersonMap.cs
3+
//
4+
// Author:
5+
// Craig Fowler <craig@csf-dev.com>
6+
//
7+
// Copyright (c) 2020 Craig Fowler
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
using System;
27+
using CSF.Specifications.Tests.Stubs;
28+
using NHibernate.Mapping.ByCode;
29+
using NHibernate.Mapping.ByCode.Conformist;
30+
31+
namespace CSF.Specifications.Tests.Specifications
32+
{
33+
public class PersonMap : ClassMapping<Person>
34+
{
35+
public PersonMap()
36+
{
37+
Id(x => x.Identity, m => {
38+
m.Generator(Generators.Assigned);
39+
});
40+
41+
Property(x => x.Name);
42+
}
43+
}
44+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// PetMap.cs
3+
//
4+
// Author:
5+
// Craig Fowler <craig@csf-dev.com>
6+
//
7+
// Copyright (c) 2020 Craig Fowler
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
using System;
27+
using CSF.Specifications.Tests.Stubs;
28+
using NHibernate.Mapping.ByCode;
29+
using NHibernate.Mapping.ByCode.Conformist;
30+
31+
namespace CSF.Specifications.Tests.Specifications
32+
{
33+
public class PetMap : ClassMapping<Pet>
34+
{
35+
public PetMap()
36+
{
37+
Id(x => x.Identity, m => {
38+
m.Generator(Generators.Assigned);
39+
});
40+
41+
ManyToOne(x => x.Owner);
42+
}
43+
}
44+
}

CSF.Specifications.Tests/Specifications/SpecificationExpressionTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,24 @@ public void Or_a_function_creates_combined_function_which_must_satisfy_either(Pe
220220
Assert.That(result, Is.EquivalentTo(new[] { personTwo, personThree }));
221221
}
222222

223+
[Test, AutoMoqData]
224+
public void Transform_may_create_a_transformed_matching_specification_expression_for_a_new_type(Pet pet, string name)
225+
{
226+
var spec = Spec.Expr<Person>(x => x.Name == name);
227+
var transformed = spec.Transform(t => t.To<Pet>(x => x.Owner));
228+
pet.Owner.Name = name;
229+
Assert.That(() => transformed.Matches(pet), Is.True);
230+
}
231+
232+
[Test, AutoMoqData]
233+
public void Transform_may_create_a_transformed_non_matching_specification_expression_for_a_new_type(Pet pet)
234+
{
235+
var spec = Spec.Expr<Person>(x => x.Name == "One");
236+
var transformed = spec.Transform(t => t.To<Pet>(x => x.Owner));
237+
pet.Owner.Name = "Two";
238+
Assert.That(() => transformed.Matches(pet), Is.False);
239+
}
240+
223241
ISpecificationExpression<Person> GetSut(string name) => new PersonNameSpecificationExpression(name);
224242
}
225243
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//
2+
// SpecificationExpressionTransformationIntegrationTests.cs
3+
//
4+
// Author:
5+
// Craig Fowler <craig@csf-dev.com>
6+
//
7+
// Copyright (c) 2020 Craig Fowler
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
using System;
27+
using NHibernate;
28+
using NHibernate.Cfg;
29+
using NUnit.Framework;
30+
using NHibernate.Dialect;
31+
using NHibernate.Cfg.MappingSchema;
32+
using NHibernate.Mapping.ByCode;
33+
using System.Reflection;
34+
using NHibernate.Tool.hbm2ddl;
35+
using CSF.Specifications.Tests.Stubs;
36+
using CSF.Specifications;
37+
using NHibernate.Linq;
38+
using System.Linq;
39+
40+
namespace CSF.Specifications.Tests.Specifications
41+
{
42+
[TestFixture,NonParallelizable, Description("Integration tests which use NHibernate to use specifications with an actual database, to verify that specification expressions are compatible.")]
43+
public class SpecificationExpressionTransformationIntegrationTests
44+
{
45+
Configuration config;
46+
ISessionFactory sessionFactory;
47+
48+
[OneTimeSetUp]
49+
public void OneTimeSetup()
50+
{
51+
config = new Configuration();
52+
53+
config.DataBaseIntegration(db => {
54+
db.Dialect<SQLiteDialect>();
55+
db.ConnectionString = "Data Source=:memory:;Version=3;New=True;";
56+
db.ConnectionReleaseMode = ConnectionReleaseMode.OnClose;
57+
});
58+
config.AddMapping(GetMapping());
59+
60+
sessionFactory = config.BuildSessionFactory();
61+
}
62+
63+
HbmMapping GetMapping()
64+
{
65+
var mapper = new ModelMapper();
66+
mapper.AddMappings(Assembly.GetExecutingAssembly().GetExportedTypes());
67+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
68+
}
69+
70+
[OneTimeTearDown]
71+
public void OneTimeTeardown()
72+
{
73+
sessionFactory?.Dispose();
74+
}
75+
76+
[Test]
77+
public void Transform_expression_returns_a_spec_which_is_compatible_with_NHibernate()
78+
{
79+
using (var session = sessionFactory.OpenSession())
80+
{
81+
SetupQueryIntegrationTest(session);
82+
83+
var personSpec = Spec.Expr<Person>(x => x.Name == "Jane Doe");
84+
var petSpec = personSpec.Transform(t => t.To<Pet>(x => x.Owner));
85+
86+
Assert.That(session.Query<Pet>().Where(petSpec).Select(x => x.Identity).FirstOrDefault(), Is.EqualTo(1));
87+
}
88+
}
89+
90+
void SetupQueryIntegrationTest(ISession session)
91+
{
92+
using (var tran = session.BeginTransaction())
93+
{
94+
var exporter = new SchemaExport(config);
95+
exporter.Execute(false, true, false, session.Connection, null);
96+
tran.Commit();
97+
}
98+
99+
using (var tran = session.BeginTransaction())
100+
{
101+
var pet = new Pet
102+
{
103+
Identity = 1,
104+
Owner = new Person { Identity = 1, Name = "Jane Doe" }
105+
};
106+
session.Save(pet);
107+
session.Save(pet.Owner);
108+
session.Flush();
109+
110+
tran.Commit();
111+
}
112+
113+
session.Clear();
114+
}
115+
}
116+
}

CSF.Specifications.Tests/Specifications/SpecificationFunctionTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,24 @@ public void Or_creates_combined_function_which_must_satisfy_either(Person person
168168
Assert.That(result, Is.EquivalentTo(new[] { personTwo, personThree }));
169169
}
170170

171+
[Test, AutoMoqData]
172+
public void Transform_may_create_a_transformed_matching_specification_expression_for_a_new_type(Pet pet, string name)
173+
{
174+
var spec = Spec.Func<Person>(x => x.Name == name);
175+
var transformed = spec.Transform(t => t.To<Pet>(x => x.Owner));
176+
pet.Owner.Name = name;
177+
Assert.That(() => transformed.Matches(pet), Is.True);
178+
}
179+
180+
[Test, AutoMoqData]
181+
public void Transform_may_create_a_transformed_non_matching_specification_expression_for_a_new_type(Pet pet)
182+
{
183+
var spec = Spec.Func<Person>(x => x.Name == "One");
184+
var transformed = spec.Transform(t => t.To<Pet>(x => x.Owner));
185+
pet.Owner.Name = "Two";
186+
Assert.That(() => transformed.Matches(pet), Is.False);
187+
}
188+
171189
ISpecificationFunction<Person> GetSut(string name) => new PersonNameSpecificationFunction(name);
172190
}
173191
}

CSF.Specifications.Tests/Stubs/Person.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ namespace CSF.Specifications.Tests.Stubs
2929
{
3030
public class Person
3131
{
32-
public long Identity
32+
public virtual long Identity
3333
{
3434
get;
3535
set;
3636
}
3737

38-
public string Name
38+
public virtual string Name
3939
{
4040
get;
4141
set;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// Pet.cs
3+
//
4+
// Author:
5+
// Craig Fowler <craig@csf-dev.com>
6+
//
7+
// Copyright (c) 2020 Craig Fowler
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
using System;
27+
namespace CSF.Specifications.Tests.Stubs
28+
{
29+
public class Pet
30+
{
31+
public virtual long Identity { get; set; }
32+
public virtual Person Owner { get; set; }
33+
}
34+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//
2+
// ExpressionPredicateTransformer.cs
3+
//
4+
// Author:
5+
// Craig Fowler <craig@csf-dev.com>
6+
//
7+
// Copyright (c) 2020 Craig Fowler
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
using System;
27+
using System.Linq.Expressions;
28+
29+
namespace CSF
30+
{
31+
/// <summary>
32+
/// A class which transforms a Linq predicate expression (such as would be exposed by a specification),
33+
/// to compose it with a selector in order to change its type.
34+
/// </summary>
35+
class ExpressionPredicateTransformer
36+
{
37+
/// <summary>
38+
/// Transforms the specified predicate expression, adding a selector, in order to change the type of the
39+
/// predicate.
40+
/// </summary>
41+
/// <returns>The transformed predicate expression.</returns>
42+
/// <param name="originalPredicate">The original predicate expression.</param>
43+
/// <param name="selector">A selector expression.</param>
44+
/// <typeparam name="TOrigin">The type of the original predicate parameter.</typeparam>
45+
/// <typeparam name="TTarget">The intended type of the transformed predicate parameter.</typeparam>
46+
internal Expression<Func<TTarget,bool>> Transform<TOrigin,TTarget>(Expression<Func<TOrigin,bool>> originalPredicate,
47+
Expression<Func<TTarget,TOrigin>> selector)
48+
{
49+
if (originalPredicate == null)
50+
throw new ArgumentNullException(nameof(originalPredicate));
51+
if (selector == null)
52+
throw new ArgumentNullException(nameof(selector));
53+
54+
var parameterExpr = Expression.Parameter(typeof(TTarget), "target");
55+
var selectorExpr = Expression.Invoke(selector, new[] { parameterExpr });
56+
var predicateExpr = Expression.Invoke(originalPredicate, new[] { selectorExpr });
57+
58+
return Expression.Lambda<Func<TTarget, bool>>(predicateExpr, new[] { parameterExpr });
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)