Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit ce66e84

Browse files
committed
Add more JOIN tests and short-cut API's for Left/Right/Full Joins
1 parent 8efa96d commit ce66e84

File tree

5 files changed

+357
-181
lines changed

5 files changed

+357
-181
lines changed

src/ServiceStack.OrmLite/Expressions/SqlExpression.Join.cs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,31 @@ public SqlExpression<T> Join<Source, Target>(Expression<Func<Source, Target, boo
1919
return InternalJoin("INNER JOIN", joinExpr);
2020
}
2121

22+
public SqlExpression<T> LeftJoin<Target>(Expression<Func<T, Target, bool>> joinExpr = null)
23+
{
24+
return InternalJoin("LEFT JOIN", joinExpr);
25+
}
26+
2227
public SqlExpression<T> LeftJoin<Source, Target>(Expression<Func<Source, Target, bool>> joinExpr = null)
2328
{
2429
return InternalJoin("LEFT JOIN", joinExpr);
2530
}
2631

32+
public SqlExpression<T> RightJoin<Target>(Expression<Func<T, Target, bool>> joinExpr = null)
33+
{
34+
return InternalJoin("RIGHT JOIN", joinExpr);
35+
}
36+
2737
public SqlExpression<T> RightJoin<Source, Target>(Expression<Func<Source, Target, bool>> joinExpr = null)
2838
{
2939
return InternalJoin("RIGHT JOIN", joinExpr);
3040
}
3141

42+
public SqlExpression<T> FullJoin<Target>(Expression<Func<T, Target, bool>> joinExpr = null)
43+
{
44+
return InternalJoin("FULL JOIN", joinExpr);
45+
}
46+
3247
public SqlExpression<T> FullJoin<Source, Target>(Expression<Func<Source, Target, bool>> joinExpr = null)
3348
{
3449
return InternalJoin("FULL JOIN", joinExpr);
@@ -42,18 +57,14 @@ private SqlExpression<T> InternalJoin<Source, Target>(string joinType,
4257
var sourceDef = typeof (Source).GetModelDefinition();
4358
var targetDef = typeof (Target).GetModelDefinition();
4459

45-
if (tableDefs.Count == 0)
46-
tableDefs.Add(modelDef);
47-
if (!tableDefs.Contains(sourceDef))
48-
tableDefs.Add(sourceDef);
49-
if (!tableDefs.Contains(targetDef))
50-
tableDefs.Add(targetDef);
51-
5260
var fromExpr = FromExpression;
5361
var sbJoin = new StringBuilder();
5462

5563
string sqlExpr;
5664

65+
//Changes how Sql Expressions are generated.
66+
useFieldName = true; sep = " ";
67+
5768
if (joinExpr != null)
5869
{
5970
sqlExpr = Visit(joinExpr).ToString();
@@ -84,12 +95,23 @@ private SqlExpression<T> InternalJoin<Source, Target>(string joinType,
8495
refField.FieldName.SqlColumn());
8596
}
8697

87-
sbJoin.Append(" {0} {1} ".Fmt(joinType, targetDef.ModelName.SqlTable()));
98+
var joinDef = tableDefs.Contains(targetDef) && !tableDefs.Contains(sourceDef)
99+
? sourceDef
100+
: targetDef;
101+
102+
sbJoin.Append(" {0} {1} ".Fmt(joinType, joinDef.ModelName.SqlTable()));
88103
sbJoin.Append(" ON ");
89104
sbJoin.Append(sqlExpr);
90105

91106
FromExpression = fromExpr + sbJoin;
92107

108+
if (tableDefs.Count == 0)
109+
tableDefs.Add(modelDef);
110+
if (!tableDefs.Contains(sourceDef))
111+
tableDefs.Add(sourceDef);
112+
if (!tableDefs.Contains(targetDef))
113+
tableDefs.Add(targetDef);
114+
93115
return this;
94116
}
95117

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
using System.Data;
2+
using System.Linq;
3+
using NUnit.Framework;
4+
using ServiceStack.OrmLite.Tests.UseCase;
5+
using ServiceStack.Text;
6+
7+
namespace ServiceStack.OrmLite.Tests
8+
{
9+
public class LoadReferencesJoinTests
10+
: OrmLiteTestBase
11+
{
12+
private IDbConnection db;
13+
14+
[TestFixtureSetUp]
15+
public new void TestFixtureSetUp()
16+
{
17+
db = base.OpenDbConnection();
18+
CustomerOrdersUseCase.DropTables(db); //Has conflicting 'Order' table
19+
20+
db.DropAndCreateTable<Order>();
21+
db.DropAndCreateTable<Customer>();
22+
db.DropAndCreateTable<CustomerAddress>();
23+
db.DropAndCreateTable<Country>();
24+
}
25+
26+
[SetUp]
27+
public void SetUp()
28+
{
29+
db.DeleteAll<Order>();
30+
db.DeleteAll<CustomerAddress>();
31+
db.DeleteAll<Customer>();
32+
db.DeleteAll<Country>();
33+
}
34+
35+
[TestFixtureTearDown]
36+
public void TestFixtureTearDown()
37+
{
38+
db.Dispose();
39+
}
40+
41+
private Customer AddCustomerWithOrders()
42+
{
43+
var customer = new Customer
44+
{
45+
Name = "Customer 1",
46+
PrimaryAddress = new CustomerAddress
47+
{
48+
AddressLine1 = "1 Humpty Street",
49+
City = "Humpty Doo",
50+
State = "Northern Territory",
51+
Country = "Australia"
52+
},
53+
Orders = new[]
54+
{
55+
new Order {LineItem = "Line 1", Qty = 1, Cost = 1.99m},
56+
new Order {LineItem = "Line 2", Qty = 2, Cost = 2.99m},
57+
}.ToList(),
58+
};
59+
60+
db.Save(customer, references: true);
61+
62+
return customer;
63+
}
64+
65+
public class CustomerJoin
66+
{
67+
public int Id { get; set; }
68+
public string Name { get; set; }
69+
public string AddressLine1 { get; set; }
70+
public string City { get; set; }
71+
public string LineItem { get; set; }
72+
public decimal Cost { get; set; }
73+
public string CountryCode { get; set; }
74+
}
75+
76+
[Test]
77+
public void Can_do_multiple_joins_with_SqlExpression()
78+
{
79+
AddCustomerWithOrders();
80+
81+
var results = db.Select<CustomerJoin, Customer>(q => q
82+
.Join<Customer, CustomerAddress>()
83+
.Join<Customer, Order>());
84+
85+
var costs = results.ConvertAll(x => x.Cost);
86+
Assert.That(costs, Is.EquivalentTo(new[] { 1.99m, 2.99m }));
87+
88+
var expr = db.From<Customer>()
89+
.Join<Customer, CustomerAddress>()
90+
.Join<Customer, Order>();
91+
92+
results = db.Select<CustomerJoin>(expr);
93+
94+
costs = results.ConvertAll(x => x.Cost);
95+
Assert.That(costs, Is.EquivalentTo(new[] { 1.99m, 2.99m }));
96+
}
97+
98+
[Test]
99+
public void Can_do_joins_with_wheres_using_SqlExpression()
100+
{
101+
AddCustomerWithOrders();
102+
103+
var results = db.Select<CustomerJoin, Customer>(q => q
104+
.Join<Customer, CustomerAddress>()
105+
.Join<Customer, Order>((c, o) => c.Id == o.CustomerId && o.Cost < 2));
106+
107+
var costs = results.ConvertAll(x => x.Cost);
108+
Assert.That(costs, Is.EquivalentTo(new[] { 1.99m }));
109+
110+
var orders = db.Select<Order>(q => q
111+
.Join<Order, Customer>()
112+
.Join<Customer, CustomerAddress>()
113+
.Where(o => o.Cost < 2)
114+
.And<Customer>(c => c.Name == "Customer 1"));
115+
116+
costs = orders.ConvertAll(x => x.Cost);
117+
Assert.That(costs, Is.EquivalentTo(new[] { 1.99m }));
118+
119+
results = db.Select<CustomerJoin, Customer>(q => q
120+
.Join<Customer, CustomerAddress>()
121+
.Join<Customer, Order>()
122+
.Where<Order>(o => o.Cost < 2));
123+
124+
costs = results.ConvertAll(x => x.Cost);
125+
Assert.That(costs, Is.EquivalentTo(new[] { 1.99m }));
126+
127+
results = db.Select<CustomerJoin, Customer>(q => q
128+
.Join<Customer, CustomerAddress>()
129+
.Join<Customer, Order>()
130+
.Where<Order>(o => o.Cost < 2 || o.LineItem == "Line 2"));
131+
132+
costs = results.ConvertAll(x => x.Cost);
133+
Assert.That(costs, Is.EquivalentTo(new[] { 1.99m, 2.99m }));
134+
135+
var expr = db.From<Customer>()
136+
.Join<Customer, CustomerAddress>()
137+
.Join<Customer, Order>()
138+
.Where<Order>(o => o.Cost < 2 || o.LineItem == "Line 2");
139+
results = db.Select<CustomerJoin>(expr);
140+
141+
costs = results.ConvertAll(x => x.Cost);
142+
Assert.That(costs, Is.EquivalentTo(new[] { 1.99m, 2.99m }));
143+
}
144+
145+
[Test]
146+
public void Can_do_joins_with_complex_wheres_using_SqlExpression()
147+
{
148+
var customer1 = new Customer
149+
{
150+
Name = "Customer 1",
151+
PrimaryAddress = new CustomerAddress
152+
{
153+
AddressLine1 = "1 Humpty Street",
154+
City = "Humpty Doo",
155+
State = "Northern Territory",
156+
Country = "Australia"
157+
},
158+
Orders = new[]
159+
{
160+
new Order {LineItem = "Line 1", Qty = 1, Cost = 1.99m},
161+
new Order {LineItem = "Line 1", Qty = 2, Cost = 3.98m},
162+
new Order {LineItem = "Line 2", Qty = 1, Cost = 1.49m},
163+
new Order {LineItem = "Line 2", Qty = 2, Cost = 2.98m},
164+
new Order {LineItem = "Australia Flag", Qty = 1, Cost = 9.99m},
165+
}.ToList(),
166+
};
167+
168+
db.Save(customer1, references: true);
169+
170+
var customer2 = new Customer
171+
{
172+
Name = "Customer 2",
173+
PrimaryAddress = new CustomerAddress
174+
{
175+
AddressLine1 = "2 Prospect Park",
176+
City = "Brooklyn",
177+
State = "New York",
178+
Country = "USA"
179+
},
180+
Orders = new[]
181+
{
182+
new Order {LineItem = "USA", Qty = 1, Cost = 20m},
183+
}.ToList(),
184+
};
185+
186+
db.Save(customer2, references: true);
187+
188+
db.Insert(
189+
new Country { CountryName = "Australia", CountryCode = "AU" },
190+
new Country { CountryName = "USA", CountryCode = "US" });
191+
192+
var results = db.Select<CustomerJoin, Customer>(q => q
193+
.Join<Customer, CustomerAddress>()
194+
.Join<Customer, Order>()
195+
.Where(c => c.Name == "Customer 1")
196+
.And<Order>(o => o.Cost < 2)
197+
.Or<Order>(o => o.LineItem == "Australia Flag"));
198+
199+
var costs = results.ConvertAll(x => x.Cost);
200+
Assert.That(costs, Is.EquivalentTo(new[] { 1.99m, 1.49m, 9.99m }));
201+
202+
results = db.Select<CustomerJoin, Customer>(q => q
203+
.Join<Customer, CustomerAddress>()
204+
.Join<Customer, Order>()
205+
.Where(c => c.Name == "Customer 2")
206+
.And<CustomerAddress, Order>((a, o) => a.Country == o.LineItem));
207+
208+
costs = results.ConvertAll(x => x.Cost);
209+
Assert.That(costs, Is.EquivalentTo(new[] { 20m }));
210+
211+
var countryResults = db.Select<CustomerJoin>(db.From<Customer>()
212+
.Join<Order>((c, o) => c.Id == o.CustomerId) //explicit join condition
213+
.Join<CustomerAddress>() //implicit join with Customer
214+
.Join<CustomerAddress, Country>((ca, c) => ca.Country == c.CountryName)
215+
.Where(c => c.Name == "Customer 2") //implicit condition with Customer
216+
.And<CustomerAddress, Order>((a, o) => a.Country == o.LineItem));
217+
218+
db.GetLastSql().Print();
219+
220+
costs = countryResults.ConvertAll(x => x.Cost);
221+
Assert.That(costs, Is.EquivalentTo(new[] { 20m }));
222+
Assert.That(countryResults.ConvertAll(x => x.CountryCode), Is.EquivalentTo(new[] { "US" }));
223+
}
224+
225+
[Test]
226+
public void Can_do_LeftJoins_using_SqlExpression()
227+
{
228+
var customers = new[]
229+
{
230+
new Customer
231+
{
232+
Name = "Customer 1",
233+
PrimaryAddress = new CustomerAddress
234+
{
235+
AddressLine1 = "1 Humpty Street",
236+
City = "Humpty Doo",
237+
State = "Northern Territory",
238+
Country = "Australia"
239+
},
240+
},
241+
new Customer
242+
{
243+
Name = "Customer 2",
244+
PrimaryAddress = new CustomerAddress
245+
{
246+
AddressLine1 = "2 Humpty Street",
247+
City = "Humpty Doo",
248+
State = "Northern Territory",
249+
Country = "USA"
250+
},
251+
},
252+
new Customer
253+
{
254+
Name = "Customer 3",
255+
PrimaryAddress = new CustomerAddress
256+
{
257+
AddressLine1 = "3 Humpty Street",
258+
City = "Humpty Doo",
259+
State = "Northern Territory",
260+
Country = "Canada"
261+
},
262+
},
263+
};
264+
265+
customers.Each(c =>
266+
db.Save(c, references: true));
267+
268+
db.Insert(
269+
new Country { CountryName = "Australia", CountryCode = "AU" },
270+
new Country { CountryName = "USA", CountryCode = "US" },
271+
new Country { CountryName = "Italy", CountryCode = "IT" },
272+
new Country { CountryName = "Spain", CountryCode = "ED" });
273+
274+
//Normal Join
275+
var dbCustomers = db.Select<Customer>(q => q
276+
.Join<CustomerAddress>()
277+
.Join<CustomerAddress, Country>((ca, c) => ca.Country == c.CountryName));
278+
279+
Assert.That(dbCustomers.Count, Is.EqualTo(2));
280+
281+
//Left Join
282+
dbCustomers = db.Select<Customer>(q => q
283+
.Join<CustomerAddress>()
284+
.LeftJoin<CustomerAddress, Country>((ca, c) => ca.Country == c.CountryName));
285+
286+
Assert.That(dbCustomers.Count, Is.EqualTo(3));
287+
288+
//Warning: Right and Full Joins are not implemented by Sqlite3. Avoid if possible.
289+
var dbCountries = db.Select<Country>(q => q
290+
.LeftJoin<CustomerAddress>((c, ca) => ca.Country == c.CountryName)
291+
.LeftJoin<CustomerAddress, Customer>());
292+
293+
Assert.That(dbCountries.Count, Is.EqualTo(4));
294+
295+
var dbAddresses = db.Select<CustomerAddress>(q => q
296+
.LeftJoin<Country>((ca, c) => ca.Country == c.CountryName)
297+
.LeftJoin<CustomerAddress, Customer>());
298+
299+
Assert.That(dbAddresses.Count, Is.EqualTo(3));
300+
}
301+
302+
}
303+
}

0 commit comments

Comments
 (0)