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

Commit ae98cb5

Browse files
committed
Add RowVersion support to Delete, import Jesse's other RowVersionTests
1 parent d5c3b8e commit ae98cb5

File tree

5 files changed

+236
-30
lines changed

5 files changed

+236
-30
lines changed

src/ServiceStack.OrmLite/IOrmLiteDialectProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ string GetColumnDefinition(
8080

8181
bool PrepareParameterizedUpdateStatement<T>(IDbCommand cmd, ICollection<string> updateFields = null);
8282

83-
void PrepareParameterizedDeleteStatement<T>(IDbCommand cmd, ICollection<string> deleteFields = null);
83+
bool PrepareParameterizedDeleteStatement<T>(IDbCommand cmd, ICollection<string> deleteFields = null);
8484

8585
void SetParameterValues<T>(IDbCommand dbCmd, object obj);
8686

src/ServiceStack.OrmLite/OrmLiteDialectProviderBase.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -637,22 +637,27 @@ public virtual void AppendFieldConditionFmt(StringBuilder sqlFilter, FieldDefini
637637
fieldDef.GetQuotedValue(objWithProperties));
638638
}
639639

640-
public virtual void PrepareParameterizedDeleteStatement<T>(IDbCommand cmd, ICollection<string> deleteFields = null)
640+
public virtual bool PrepareParameterizedDeleteStatement<T>(IDbCommand cmd, ICollection<string> deleteFields = null)
641641
{
642642
var sqlFilter = new StringBuilder();
643643
var modelDef = typeof(T).GetModelDefinition();
644+
var hadRowVesion = false;
644645
var hasSpecificFilter = deleteFields != null && deleteFields.Count > 0;
645646

646647
cmd.Parameters.Clear();
647648
cmd.CommandTimeout = OrmLiteConfig.CommandTimeout;
648649

649650
foreach (var fieldDef in modelDef.FieldDefinitions)
650651
{
651-
if (fieldDef.IsComputed) continue;
652+
if (fieldDef.IsComputed)
653+
continue;
652654

653-
if (hasSpecificFilter && !deleteFields.Contains(fieldDef.Name))
655+
if (!fieldDef.IsRowVersion && (hasSpecificFilter && !deleteFields.Contains(fieldDef.Name)))
654656
continue;
655657

658+
if (fieldDef.IsRowVersion)
659+
hadRowVesion = true;
660+
656661
try
657662
{
658663
if (sqlFilter.Length > 0)
@@ -668,6 +673,8 @@ public virtual void PrepareParameterizedDeleteStatement<T>(IDbCommand cmd, IColl
668673

669674
cmd.CommandText = string.Format("DELETE FROM {0} WHERE {1}",
670675
GetQuotedTableName(modelDef), sqlFilter);
676+
677+
return hadRowVesion;
671678
}
672679

673680
protected void AddParameter(IDbCommand cmd, FieldDefinition fieldDef)

src/ServiceStack.OrmLite/OrmLiteWriteConnectionExtensions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,17 @@ public static int DeleteById<T>(this IDbConnection dbConn, object id)
267267
return dbConn.Exec(dbCmd => dbCmd.DeleteById<T>(id));
268268
}
269269

270+
/// <summary>
271+
/// Delete 1 row by the PrimaryKey where the rowVersion matches the optimistic concurrency field.
272+
/// Will throw <exception cref="RowModifiedException">RowModefiedExeption</exception> if the
273+
/// row does not exist or has a different row version.
274+
/// E.g: <para>db.DeleteById&lt;Person&gt;(1)</para>
275+
/// </summary>
276+
public static void DeleteById<T>(this IDbConnection dbConn, object id, ulong rowVersion)
277+
{
278+
dbConn.Exec(dbCmd => dbCmd.DeleteById<T>(id, rowVersion));
279+
}
280+
270281
/// <summary>
271282
/// Delete all rows identified by the PrimaryKeys. E.g:
272283
/// <para>db.DeleteById&lt;Person&gt;(new[] { 1, 2, 3 })</para>

src/ServiceStack.OrmLite/OrmLiteWriteExtensions.cs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ internal static int UpdateAll<T>(this IDbCommand dbCmd, IEnumerable<T> objs)
414414

415415
var dialectProvider = OrmLiteConfig.DialectProvider;
416416

417-
dialectProvider.PrepareParameterizedUpdateStatement<T>(dbCmd);
417+
var hadRowVersion = dialectProvider.PrepareParameterizedUpdateStatement<T>(dbCmd);
418418
if (string.IsNullOrEmpty(dbCmd.CommandText))
419419
return 0;
420420

@@ -425,7 +425,11 @@ internal static int UpdateAll<T>(this IDbCommand dbCmd, IEnumerable<T> objs)
425425

426426
dialectProvider.SetParameterValues<T>(dbCmd, obj);
427427

428-
count += dbCmd.ExecNonQuery();
428+
var rowsUpdated = dbCmd.ExecNonQuery();
429+
if (hadRowVersion && rowsUpdated == 0)
430+
throw new RowModifiedException();
431+
432+
count += rowsUpdated;
429433
}
430434

431435
if (dbTrans != null)
@@ -440,22 +444,31 @@ internal static int UpdateAll<T>(this IDbCommand dbCmd, IEnumerable<T> objs)
440444
return count;
441445
}
442446

447+
private static int AssertRowsUpdated(IDbCommand dbCmd, bool hadRowVersion)
448+
{
449+
var rowsUpdated = dbCmd.ExecNonQuery();
450+
if (hadRowVersion && rowsUpdated == 0)
451+
throw new RowModifiedException();
452+
453+
return rowsUpdated;
454+
}
455+
443456
internal static int Delete<T>(this IDbCommand dbCmd, object anonType)
444457
{
445-
OrmLiteConfig.DialectProvider.PrepareParameterizedDeleteStatement<T>(dbCmd, anonType.AllFields<T>());
458+
var hadRowVersion = OrmLiteConfig.DialectProvider.PrepareParameterizedDeleteStatement<T>(dbCmd, anonType.AllFields<T>());
446459

447460
OrmLiteConfig.DialectProvider.SetParameterValues<T>(dbCmd, anonType);
448461

449-
return dbCmd.ExecNonQuery();
462+
return AssertRowsUpdated(dbCmd, hadRowVersion);
450463
}
451464

452465
internal static int DeleteNonDefaults<T>(this IDbCommand dbCmd, T filter)
453466
{
454-
OrmLiteConfig.DialectProvider.PrepareParameterizedDeleteStatement<T>(dbCmd, filter.NonDefaultFields<T>());
467+
var hadRowVersion = OrmLiteConfig.DialectProvider.PrepareParameterizedDeleteStatement<T>(dbCmd, filter.NonDefaultFields<T>());
455468

456469
OrmLiteConfig.DialectProvider.SetParameterValues<T>(dbCmd, filter);
457470

458-
return dbCmd.ExecNonQuery();
471+
return AssertRowsUpdated(dbCmd, hadRowVersion);
459472
}
460473

461474
internal static int Delete<T>(this IDbCommand dbCmd, params object[] objs)
@@ -523,6 +536,38 @@ internal static int DeleteById<T>(this IDbCommand dbCmd, object id)
523536
return dbCmd.ExecuteSql(sql);
524537
}
525538

539+
internal static void DeleteById<T>(this IDbCommand dbCmd, object id, ulong rowVersion)
540+
{
541+
var modelDef = ModelDefinition<T>.Definition;
542+
543+
dbCmd.Parameters.Clear();
544+
545+
var idParam = dbCmd.CreateParameter();
546+
idParam.ParameterName = OrmLiteConfig.DialectProvider.GetParam();
547+
idParam.Value = id;
548+
dbCmd.Parameters.Add(idParam);
549+
550+
var rowVersionField = modelDef.RowVersion;
551+
if (rowVersionField == null)
552+
throw new InvalidOperationException("Cannot use DeleteById with rowVersion for model type without a row version column");
553+
554+
var rowVersionParam = dbCmd.CreateParameter();
555+
rowVersionParam.ParameterName = OrmLiteConfig.DialectProvider.GetParam("rowVersion");
556+
rowVersionParam.Value = rowVersion;
557+
dbCmd.Parameters.Add(rowVersionParam);
558+
559+
var sql = string.Format("DELETE FROM {0} WHERE {1} = {2} AND {3} = {4}",
560+
OrmLiteConfig.DialectProvider.GetQuotedTableName(modelDef),
561+
OrmLiteConfig.DialectProvider.GetQuotedColumnName(modelDef.PrimaryKey.FieldName),
562+
idParam.ParameterName,
563+
OrmLiteConfig.DialectProvider.GetQuotedColumnName(rowVersionField.FieldName),
564+
rowVersionParam.ParameterName);
565+
566+
var rowsAffected = dbCmd.ExecuteSql(sql);
567+
if (rowsAffected == 0)
568+
throw new RowModifiedException("The row was modified or deleted since the last read");
569+
}
570+
526571
internal static int DeleteByIds<T>(this IDbCommand dbCmd, IEnumerable idValues)
527572
{
528573
var sqlIn = idValues.GetIdsInSql();
Lines changed: 163 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Data;
3+
using System.Linq;
24
using NUnit.Framework;
35
using ServiceStack.DataAnnotations;
46
using ServiceStack.Logging;
@@ -7,9 +9,10 @@ namespace ServiceStack.OrmLite.Tests
79
{
810
public class ModelWithRowVersion
911
{
10-
public int Id { get; set; }
12+
[AutoIncrement]
13+
public long Id { get; set; }
1114

12-
public string Name { get; set; }
15+
public string Text { get; set; }
1316

1417
public ulong RowVersion { get; set; }
1518
}
@@ -21,37 +24,177 @@ public RowVersionTests()
2124
//Dialect = Dialect.Sqlite;
2225
}
2326

27+
private IDbConnection db;
28+
29+
[TestFixtureSetUp]
30+
public void FixtureSetUp()
31+
{
32+
using (var dbConn = OpenDbConnection())
33+
{
34+
dbConn.DropAndCreateTable<ModelWithRowVersion>();
35+
}
36+
}
37+
38+
[SetUp]
39+
public void SetUp()
40+
{
41+
db = OpenDbConnection();
42+
}
43+
44+
[TearDown]
45+
public void TearDown()
46+
{
47+
db.Dispose();
48+
}
49+
2450
[Test]
2551
public void Can_create_table_with_RowVersion()
2652
{
27-
LogManager.LogFactory = new ConsoleLogFactory();
28-
using (var db = OpenDbConnection())
53+
db.DropAndCreateTable<ModelWithRowVersion>();
54+
55+
db.Insert(new ModelWithRowVersion { Text = "Text" });
56+
57+
var row = db.SingleById<ModelWithRowVersion>(1);
58+
59+
row.Text += " Updated";
60+
61+
db.Update(row);
62+
63+
var updatedRow = db.SingleById<ModelWithRowVersion>(1);
64+
65+
Assert.That(updatedRow.Text, Is.EqualTo("Text Updated"));
66+
Assert.That(updatedRow.RowVersion, Is.GreaterThan(0));
67+
68+
row.Text += " Again";
69+
70+
//Can't update old record
71+
Assert.Throws<RowModifiedException>(() =>
72+
db.Update(row));
73+
74+
//Can update latest version
75+
updatedRow.Text += " Again";
76+
db.Update(updatedRow);
77+
}
78+
79+
[Test]
80+
public void Select_retrieves_rowversion()
81+
{
82+
var rowId = db.Insert(new ModelWithRowVersion { Text = "One" }, selectIdentity: true);
83+
TouchRow(rowId);
84+
85+
var row = db.SingleById<ModelWithRowVersion>(rowId);
86+
87+
Assert.That(row.RowVersion, Is.Not.EqualTo(0));
88+
}
89+
90+
[Test]
91+
public void Can_update_with_current_rowversion()
92+
{
93+
var rowId = db.Insert(new ModelWithRowVersion { Text = "Two" }, selectIdentity: true);
94+
var row = db.SingleById<ModelWithRowVersion>(rowId);
95+
96+
row.Text = "Three";
97+
db.Update(row);
98+
99+
var actual = db.SingleById<ModelWithRowVersion>(rowId);
100+
Assert.That(actual.Text, Is.EqualTo("Three"));
101+
Assert.That(actual.RowVersion, Is.Not.EqualTo(row.RowVersion));
102+
}
103+
104+
[Test]
105+
public void Can_update_multiple_with_current_rowversions()
106+
{
107+
var rowIds = new[]
29108
{
30-
db.DropAndCreateTable<ModelWithRowVersion>();
109+
db.Insert(new ModelWithRowVersion { Text = "Eleven" }, selectIdentity: true),
110+
db.Insert(new ModelWithRowVersion { Text = "Twelve" }, selectIdentity: true)
111+
};
112+
var rows = rowIds
113+
.Select(id => db.SingleById<ModelWithRowVersion>(id))
114+
.ToArray();
31115

32-
db.Insert(new ModelWithRowVersion { Id = 1, Name = "Name" });
116+
rows[0].Text = "Thirteen";
117+
rows[1].Text = "Fourteen";
118+
db.UpdateAll(rows);
33119

34-
var row = db.SingleById<ModelWithRowVersion>(1);
120+
var actualRows = rowIds
121+
.Select(id => db.SingleById<ModelWithRowVersion>(id))
122+
.ToArray();
123+
Assert.That(actualRows[0].Text, Is.EqualTo("Thirteen"));
124+
Assert.That(actualRows[1].Text, Is.EqualTo("Fourteen"));
125+
}
35126

36-
row.Name += " Updated";
127+
[Test]
128+
public void Can_delete_with_current_rowversion()
129+
{
130+
var rowId = db.Insert(new ModelWithRowVersion { Text = "Four" }, selectIdentity: true);
131+
var row = db.SingleById<ModelWithRowVersion>(rowId);
37132

38-
db.Update(row);
133+
db.Delete(row);
39134

40-
var updatedRow = db.SingleById<ModelWithRowVersion>(1);
135+
var count = db.Count<ModelWithRowVersion>(m => m.Id == rowId);
136+
Assert.That(count, Is.EqualTo(0));
137+
}
41138

42-
Assert.That(updatedRow.Name, Is.EqualTo("Name Updated"));
43-
Assert.That(updatedRow.RowVersion, Is.GreaterThan(0));
139+
[Test]
140+
public void Update_with_outdated_rowversion_throws()
141+
{
142+
var rowId = db.Insert(new ModelWithRowVersion { Text = "Five" }, selectIdentity: true);
143+
var row = db.SingleById<ModelWithRowVersion>(rowId);
144+
TouchRow(rowId);
44145

45-
row.Name += " Again";
146+
row.Text = "Six";
147+
Assert.Throws<RowModifiedException>(() => db.Update(row));
46148

47-
//Can't update old record
48-
Assert.Throws<RowModifiedException>(() =>
49-
db.Update(row));
149+
var actual = db.SingleById<ModelWithRowVersion>(rowId);
150+
Assert.That(actual.Text, Is.Not.EqualTo("Six"));
151+
}
50152

51-
//Can update latest version
52-
updatedRow.Name += " Again";
53-
db.Update(updatedRow);
54-
}
153+
[Test]
154+
public void Update_multiple_with_single_outdated_rowversion_throws_and_all_changes_are_rejected()
155+
{
156+
var rowIds = new[]
157+
{
158+
db.Insert(new ModelWithRowVersion { Text = "Fifteen" }, selectIdentity: true),
159+
db.Insert(new ModelWithRowVersion { Text = "Sixteen" }, selectIdentity: true)
160+
};
161+
var rows = rowIds
162+
.Select(id => db.SingleById<ModelWithRowVersion>(id))
163+
.ToArray();
164+
TouchRow(rowIds[1]);
165+
166+
rows[0].Text = "Seventeen";
167+
rows[1].Text = "Eighteen";
168+
Assert.Throws<RowModifiedException>(() =>
169+
db.UpdateAll(rows));
170+
171+
var actualRows = rowIds
172+
.Select(id => db.SingleById<ModelWithRowVersion>(id))
173+
.ToArray();
174+
175+
Assert.That(actualRows[0].Text, Is.EqualTo("Fifteen"));
176+
Assert.That(actualRows[1].Text, Is.Not.EqualTo("Eighteen"));
177+
}
178+
179+
[Test]
180+
public void Delete_with_outdated_rowversion_throws()
181+
{
182+
var rowId = db.Insert(new ModelWithRowVersion { Text = "Seven" }, selectIdentity: true);
183+
var row = db.SingleById<ModelWithRowVersion>(rowId);
184+
TouchRow(rowId);
185+
186+
Assert.Throws<RowModifiedException>(() =>
187+
db.Delete(row));
188+
189+
var count = db.Count<ModelWithRowVersion>(m => m.Id == rowId);
190+
Assert.That(count, Is.EqualTo(1));
191+
}
192+
193+
private void TouchRow(long rowId)
194+
{
195+
var row = db.SingleById<ModelWithRowVersion>(rowId);
196+
row.Text = "Touched";
197+
db.Update(row);
55198
}
56199
}
57200
}

0 commit comments

Comments
 (0)