|
1 | 1 | # Release Notes |
2 | 2 |
|
| 3 | +# v4.0.22 Release Notes |
| 4 | + |
| 5 | +## OrmLite |
| 6 | + |
| 7 | +This was primarily an OrmLite-focused release with the introduction of major new features: |
| 8 | + |
| 9 | +### Typed SQL Expressions now support Joins! |
| 10 | + |
| 11 | +Another [highly requested feature](http://servicestack.uservoice.com/forums/176786-feature-requests/suggestions/4459040-enhance-ormlite-with-common-data-usage-patterns) has been realized in this release with OrmLite's typed SqlExpressions extended to add support for Joins. |
| 12 | + |
| 13 | +The new JOIN support follows OrmLite's traditional approach of a providing a DRY, typed RDBMS-agnostic wrapper that retains a high affinity with SQL, providing an intuitive API that generates predictable SQL and a light-weight mapping to clean POCO's. |
| 14 | + |
| 15 | +### Basic Example |
| 16 | + |
| 17 | +Starting with the most basic example you can simply specify the table you want to join with: |
| 18 | + |
| 19 | +```csharp |
| 20 | +var dbCustomers = db.Select<Customer>(q => q.Join<CustomerAddress>()); |
| 21 | +``` |
| 22 | + |
| 23 | +This query rougly maps to the following SQL: |
| 24 | + |
| 25 | +```sql |
| 26 | +SELECT Customer.* |
| 27 | + FROM Customer |
| 28 | + INNER JOIN |
| 29 | + CustomerAddress ON (Customer.Id == CustomerAddress.Id) |
| 30 | +``` |
| 31 | + |
| 32 | +Just like before `q` is an instance of `SqlExpression<Customer>` which is bounded to the base `Customer` type (and what any subsequent implicit API's apply to). |
| 33 | + |
| 34 | +To better illustrate the above query, lets expand it to the equivalent explicit query: |
| 35 | + |
| 36 | +```csharp |
| 37 | +SqlExpression<Customer> q = db.From<Customer>(); |
| 38 | +q.Join<Customer,CustomerAddress>(); |
| 39 | + |
| 40 | +List<Customer> dbCustomers = db.Select(q); |
| 41 | +``` |
| 42 | + |
| 43 | +### Reference Conventions |
| 44 | + |
| 45 | +The above query joins together the `Customer` and `CustomerAddress` POCO's using the same relationship convention used in [OrmLite's support for References](https://github.com/ServiceStack/ServiceStack.OrmLite/blob/master/tests/ServiceStack.OrmLite.Tests/LoadReferencesTests.cs), i.e. using the referenced table `{ParentType}Id` property convention. |
| 46 | + |
| 47 | +An example of what this looks like can be seen the POCO's below: |
| 48 | + |
| 49 | +```csharp |
| 50 | +class Customer { |
| 51 | + public Id { get; set; } |
| 52 | + ... |
| 53 | +} |
| 54 | +class CustomerAddress { |
| 55 | + public Id { get; set; } |
| 56 | + public CustomerId { get; set; } // Reference based on Property name convention |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +References based on matching alias names is also supported, e.g: |
| 61 | + |
| 62 | +```csharp |
| 63 | +[Alias("LegacyCustomer")] |
| 64 | +class Customer { |
| 65 | + public Id { get; set; } |
| 66 | + ... |
| 67 | +} |
| 68 | +class CustomerAddress { |
| 69 | + public Id { get; set; } |
| 70 | + |
| 71 | + [Alias("LegacyCustomerId")] // Matches `LegacyCustomer` Alias |
| 72 | + public RenamedCustomerId { get; set; } // Reference based on Alias Convention |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +Either convention lets you save a POCO and all its entity references with `db.Save()`, e.g: |
| 77 | + |
| 78 | +```csharp |
| 79 | +var customer = new Customer { |
| 80 | + Name = "Customer 1", |
| 81 | + PrimaryAddress = new CustomerAddress { |
| 82 | + AddressLine1 = "1 Australia Street", |
| 83 | + Country = "Australia" |
| 84 | + }, |
| 85 | +}; |
| 86 | +db.Save(customer, references:true); |
| 87 | +``` |
| 88 | + |
| 89 | +Going back to the above example: |
| 90 | + |
| 91 | +```csharp |
| 92 | +q.Join<CustomerAddress>(); |
| 93 | +``` |
| 94 | + |
| 95 | +Uses the implicit join in the above reference convention to expand into the equivalent explicit API: |
| 96 | + |
| 97 | +```csharp |
| 98 | +q.Join<Customer,CustomerAddress>((customer,address) => customer.Id == address.CustomerId); |
| 99 | +``` |
| 100 | + |
| 101 | +### Selecting multiple columns across joined tables |
| 102 | + |
| 103 | +Another behaviour implicit when selecting from a typed SqlExpression is that results are mapped to the `Customer` POCO. To change this default we just need to explicitly specify what POCO it should map to instead: |
| 104 | + |
| 105 | +```csharp |
| 106 | +List<FullCustomerInfo> customers = db.Select<FullCustomerInfo>( |
| 107 | + db.From<Customer>().Join<CustomerAddress>()); |
| 108 | +``` |
| 109 | + |
| 110 | +Where `FullCustomerInfo` is any POCO that contains a combination of properties matching any of the joined tables in the query. |
| 111 | + |
| 112 | +The above example is also equivalent to the shorthand `db.Select<Into,From>()` API: |
| 113 | + |
| 114 | +```csharp |
| 115 | +var customers = db.Select<FullCustomerInfo,Customer>(q => q.Join<CustomerAddress>()); |
| 116 | +``` |
| 117 | + |
| 118 | +Rules for how results are mapped is simply each property on `FullCustomerInfo` is mapped to the first matching property in any of the tables in the order they were added to the SqlExpression. |
| 119 | + |
| 120 | +As most OrmLite tables have a primary key property named `Id`, the auto-mapping includes a fallback for mapping to a full namespaced Id property in the same `{Type}Id` format. This allows you to auto-populate `CustomerId`, `CustomerAddressId` and `OrderId` columns even though they aren't a match to any of the fields in any of the joined tables. |
| 121 | + |
| 122 | +### Advanced Example |
| 123 | + |
| 124 | +Seeing how the SqlExpression is constructed, joined and mapped, we can take a look at a more advanced example to showcase more of the new API's available: |
| 125 | + |
| 126 | +```csharp |
| 127 | +List<FullCustomerInfo> rows = db.Select<FullCustomerInfo>( // Map results to FullCustomerInfo POCO |
| 128 | + db.From<Customer>() // Create typed Customer SqlExpression |
| 129 | + .LeftJoin<CustomerAddress>() // Implict left join with base table |
| 130 | + .Join<Customer, Order>((c,o) => c.Id == o.CustomerId) // Explicit join and condition |
| 131 | + .Where(c => c.Name == "Customer 1") // Implicit condition on base table |
| 132 | + .And<Order>(o => o.Cost < 2) // Explicit condition on joined Table |
| 133 | + .Or<Customer,Order>((c,o) => c.Name == o.LineItem)); // Explicit condition with joined Tables |
| 134 | +``` |
| 135 | + |
| 136 | +The comments next to each line document each Type of API used. Some of the new API's introduced in this example include: |
| 137 | + |
| 138 | + - Usage of `LeftJoin` for LEFT JOIN'S, `RightJoin` and `FullJoin` also available |
| 139 | + - Usage of `And<Table>()`, to specify a condition on a Joined table |
| 140 | + - Usage of `Or<Table1,Table2>`, to specify a condition against 2 joined tables |
| 141 | + |
| 142 | +More code examples of References and Joined tables are available in: |
| 143 | + |
| 144 | + - [LoadReferencesTests.cs](https://github.com/ServiceStack/ServiceStack.OrmLite/blob/master/tests/ServiceStack.OrmLite.Tests/LoadReferencesTests.cs) |
| 145 | + - [LoadReferencesJoinTests.cs](https://github.com/ServiceStack/ServiceStack.OrmLite/blob/master/tests/ServiceStack.OrmLite.Tests/LoadReferencesJoinTests.cs) |
| 146 | + |
| 147 | +## Optimistic Concurrency |
| 148 | + |
| 149 | +Another major feature added to OrmLite is support for optimistic concurrency which can be added to any table by adding a `ulong RowVersion { get; set; }` property, e.g: |
| 150 | + |
| 151 | +```csharp |
| 152 | +public class Poco |
| 153 | +{ |
| 154 | + ... |
| 155 | + public ulong RowVersion { get; set; } |
| 156 | +} |
| 157 | +``` |
| 158 | + |
| 159 | +RowVersion is implemented efficiently in all major RDBMS's, i.e: |
| 160 | + |
| 161 | + - Uses `rowversion` datatype in SqlServer |
| 162 | + - Uses PostgreSql's `xmin` system column (no column on table required) |
| 163 | + - Uses UPDATE triggers on MySql, Sqlite and Oracle whose lifetime is attached to Create/Drop tables APIs |
| 164 | + |
| 165 | +Despite their differing implementations each provider works the same way where the `RowVersion` property is populated when the record is selected and only updates the record if the RowVersion matches with what's in the database, e.g: |
| 166 | + |
| 167 | +```csharp |
| 168 | +var rowId = db.Insert(new Poco { Text = "Text" }, selectIdentity:true); |
| 169 | + |
| 170 | +var row = db.SingleById<Poco>(rowId); |
| 171 | +row.Text += " Updated"; |
| 172 | +db.Update(row); //success! |
| 173 | +
|
| 174 | +row.Text += "Attempting to update stale record"; |
| 175 | + |
| 176 | +//Can't update stale record |
| 177 | +Assert.Throws<OptimisticConcurrencyException>(() => |
| 178 | + db.Update(row)); |
| 179 | + |
| 180 | +//Can update latest version |
| 181 | +var updatedRow = db.SingleById<Poco>(rowId); // fresh version |
| 182 | +updatedRow.Text += "Update Success!"; |
| 183 | +db.Update(updatedRow); |
| 184 | + |
| 185 | +updatedRow = db.SingleById<Poco>(rowId); |
| 186 | +db.Delete(updatedRow); // can delete fresh version |
| 187 | +``` |
| 188 | + |
| 189 | +Optimistic concurrency is only verified on API's that update or delete an entire entity, i.e. it's not enforced in partial updates. There's also an Alternative API available for DELETE's: |
| 190 | + |
| 191 | +```csharp |
| 192 | +db.DeleteById<Poco>(id:updatedRow.Id, rowversion:updatedRow.RowVersion) |
| 193 | +``` |
| 194 | + |
| 195 | +### Other OrmLite features |
| 196 | + |
| 197 | + - New [Limit API's added to JoinSqlBuilder](https://github.com/ServiceStack/ServiceStack.OrmLite/blob/master/tests/ServiceStack.OrmLite.Tests/Expression/SqlExpressionTests.cs#L126-L168) |
| 198 | + - SqlExpression's are now tied to the dialect provider at time of creation |
| 199 | + |
| 200 | +## ServiceStack.Text |
| 201 | + |
| 202 | +A new `JsConfig.ReuseStringBuffer` performance config option is available to JSON and JSV Text Serializers which lets you re-use ThreadStatic StringBuilder when serializing to a string. In initial benchmarks (both synchronous and parallel) it shows around a **~%30 increase in performance** for small POCO's. It can be enabled with: |
| 203 | + |
| 204 | +```csharp |
| 205 | +JsConfig.ReuseStringBuffer = true; |
| 206 | +``` |
| 207 | + |
| 208 | +Default enum values can be excluded from being serialized with: |
| 209 | + |
| 210 | +```csharp |
| 211 | +JsConfig.IncludeDefaultEnums = false; |
| 212 | +``` |
| 213 | + |
| 214 | +## ServiceStack |
| 215 | + |
| 216 | +### [Messaging](https://github.com/ServiceStack/ServiceStack/wiki/Messaging) |
| 217 | + |
| 218 | +Improved support for the MQ Request/Reply pattern with the new `GetTempQueueName()` API now available in all MQ Clients which returns a temporary queue (prefixed with `mq:tmp:`) suitable for use as the ReplyTo queue in Request/Reply scenarios: |
| 219 | + |
| 220 | +```csharp |
| 221 | +mqServer.RegisterHandler<Hello>(m => |
| 222 | + new HelloResponse { Result = "Hello, {0}!".Fmt(m.GetBody().Name) }); |
| 223 | +mqServer.Start(); |
| 224 | + |
| 225 | +using (var mqClient = mqServer.CreateMessageQueueClient()) |
| 226 | +{ |
| 227 | + var replyToMq = mqClient.GetTempQueueName(); |
| 228 | + mqClient.Publish(new Message<Hello>(new Hello { Name = "World" }) { |
| 229 | + ReplyTo = replyToMq |
| 230 | + }); |
| 231 | + |
| 232 | + IMessage<HelloResponse> responseMsg = mqClient.Get<HelloResponse>(replyToMq); |
| 233 | + mqClient.Ack(responseMsg); |
| 234 | + var responseDto = responseMsg.GetBody(); |
| 235 | +} |
| 236 | +``` |
| 237 | + |
| 238 | +On [Rabbit MQ](https://github.com/ServiceStack/ServiceStack/wiki/Rabbit-MQ) it creates an exclusive non-durable queue. |
| 239 | + |
| 240 | +In [Redis MQ](https://github.com/ServiceStack/ServiceStack/wiki/Messaging-and-Redis) there's a new `RedisMqServer.ExpireTemporaryQueues()` API which can be used on StartUp to expire temporary queues after a given period. |
| 241 | + |
| 242 | +Synchronous and Parallel tests for this feature is available in [MqRequestReplyTests.cs](https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.Server.Tests/Messaging/MqRequestReplyTests.cs). |
| 243 | + |
| 244 | +## New NuGet packages |
| 245 | + |
| 246 | + - [ServiceStack.Authentication.LightSpeed](https://www.nuget.org/packages/ServiceStack.Authentication.LightSpeed/) is a new User Auth Repository created by [Herdy Handoko](https://plus.google.com/u/0/+HerdyHandoko/posts) providing a new persistence option for User Authentication backed by [Mindscape's LightSpeed ORM](http://www.mindscapehq.com/products/lightspeed). Checkout the [GitHub Project](https://github.com/hhandoko/ServiceStack.Authentication.LightSpeed) for more info. |
| 247 | + |
| 248 | +### Other Framework Features |
| 249 | + |
| 250 | + - Added support for locking users in all AuthProviders by populating `UserAuth.LockedDate`, effective from next login attempt |
| 251 | + - Reduced dependencies on all Logging providers, now only depends on `ServiceStack.Interfaces` |
| 252 | + - ContentLength is written where possible allowing [Async Progress callbacks on new payloads](https://github.com/ServiceStack/ServiceStack/blob/master/tests/ServiceStack.WebHost.Endpoints.Tests/AsyncProgressTests.cs) |
| 253 | + - Non authenticated requests to `/auth` throw a 401 (otherwise returns basic session info) |
| 254 | + - Metadata filter now supports IE8/IE9 |
| 255 | + - `CopyTo` and `WriteTo` Stream extensions now return bytes transferred |
| 256 | + |
3 | 257 | # v4.0.21 Release Notes |
4 | 258 |
|
5 | 259 | ## Authentication |
|
0 commit comments