Skip to content

Commit 6e93b96

Browse files
committed
1.1.0
1 parent 504c5cd commit 6e93b96

File tree

18 files changed

+2454
-75
lines changed

18 files changed

+2454
-75
lines changed

BaseTests/CodexMicroORM.BaseTests.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
</ItemGroup>
1818

1919
<ItemGroup>
20-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
21-
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
22-
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
20+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
21+
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
22+
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
2323
<PackageReference Include="coverlet.collector" Version="3.1.2">
2424
<PrivateAssets>all</PrivateAssets>
2525
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

BaseTests/DBOps.cs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
using System.Diagnostics;
1010
using System.IO;
1111
using System.Linq;
12+
using System.Text;
1213
using System.Text.RegularExpressions;
14+
using System.Threading;
1315
using System.Threading.Tasks;
1416

1517
namespace BaseTests
@@ -31,7 +33,7 @@ public SelfContainedDBOps()
3133
Globals.WrapperSupports = WrappingSupport.Notifications;
3234
Globals.WrappingClassNamespace = null;
3335
Globals.WrapperClassNamePattern = "{0}Wrapped";
34-
CEF.AddGlobalService(DBService.Create(new MSSQLProcBasedProvider($@"Data Source={DB_SERVER};Database=CodexMicroORMTest;Integrated Security=SSPI;MultipleActiveResultSets=true", defaultSchema: "CEFTest")));
36+
CEF.AddGlobalService(DBService.Create(new MSSQLProcBasedProvider($@"Data Source={DB_SERVER};Database=CodexMicroORMTest;Integrated Security=SSPI;MultipleActiveResultSets=true;TrustServerCertificate=true", defaultSchema: "CEFTest")));
3537
CEF.AddGlobalService(new AuditService());
3638

3739
// New in 0.9.12 - this is probably the exception not the rule, where ordinarily better to assume audit fields *will* be first class props on biz objs; but we support the option to not be
@@ -119,6 +121,10 @@ public SelfContainedDBOps()
119121
DBService.RegisterOnSaveParentSave<Receipt>("WidgetGroup");
120122
DBService.RegisterOnSaveParentSave<Shipment>("WidgetGroup");
121123

124+
// Some properties should not participate in CopySharedTo
125+
CEF.RegisterPropertyNameTreatReadOnly(nameof(Person.Kids));
126+
CEF.RegisterPropertyNameTreatReadOnly(nameof(Person.Phones));
127+
122128
// Test encryption for memory back cache - will always be different!
123129
MemoryStream enckeysrc = new MemoryStream();
124130
enckeysrc.Write(Guid.NewGuid().ToByteArray());
@@ -127,13 +133,13 @@ public SelfContainedDBOps()
127133
MemoryFileSystemBacked.SetEncryptionKeySource(MemoryFileSystemBacked.SerializationFileEncryptionType.AES256, enckeysrc);
128134

129135
// This will construct a new test database, if needed - if the script changes, you'll need to drop the CodexMicroORMTest database before running
130-
using (CEF.NewConnectionScope(new ConnectionScopeSettings() { IsTransactional = false, ConnectionStringOverride = $@"Data Source={DB_SERVER};Database=master;Integrated Security=SSPI;MultipleActiveResultSets=true" }))
136+
using (CEF.NewConnectionScope(new ConnectionScopeSettings() { IsTransactional = false, ConnectionStringOverride = $@"Data Source={DB_SERVER};Database=master;Integrated Security=SSPI;MultipleActiveResultSets=true;TrustServerCertificate=true" }))
131137
{
132138
CEF.CurrentDBService().ExecuteRaw(File.ReadAllText("setup.sql"), false);
133139
}
134140

135141
// Perform specialized clean-up for tests
136-
using (CEF.NewConnectionScope(new ConnectionScopeSettings() { IsTransactional = false, ConnectionStringOverride = $@"Data Source={DB_SERVER};Database=CodexMicroORMTest;Integrated Security=SSPI;MultipleActiveResultSets=true" }))
142+
using (CEF.NewConnectionScope(new ConnectionScopeSettings() { IsTransactional = false, ConnectionStringOverride = $@"Data Source={DB_SERVER};Database=CodexMicroORMTest;Integrated Security=SSPI;MultipleActiveResultSets=true;TrustServerCertificate=true" }))
137143
{
138144
CEF.CurrentDBService().ExecuteRaw("EXEC WTest.[up_Widget_TestCleanup]");
139145
}
@@ -468,6 +474,44 @@ public void NewWidgetNewReceiptNewShipment()
468474
// }
469475
//}
470476

477+
[TestMethod]
478+
public void TestDataTableOps()
479+
{
480+
using var ss = CEF.NewServiceScope();
481+
Globals.UseShadowPropertiesForNew = false;
482+
483+
EntitySet<Person> people = new();
484+
people.Add(new Person() { PersonID = 1, Age = 12, Gender = "F", Name = "Zella" });
485+
people.Add(new Person() { PersonID = 2, Age = 13, Gender = "M", Name = "Robert" });
486+
people.Add(new Person() { PersonID = 3, Age = 75, Gender = "M", Name = "Jack" });
487+
ss.AcceptAllChanges();
488+
489+
// Let's assume people has been handed back to a client now - it's not dirty. Let's create a data table from it. Try visualizing the dt variable in VS.
490+
var dt = people.DeepCopyDataTable();
491+
Assert.IsTrue(dt.Rows.Count == 3);
492+
493+
// Let's make a few changes - an insert, update and a delete using the dt
494+
dt.Rows[1]["Age"] = 22;
495+
dt.Rows[0].Delete();
496+
var nr = dt.NewRow();
497+
nr["Age"] = 40;
498+
nr["Gender"] = "F";
499+
nr["Name"] = "Bobby Tables";
500+
dt.Rows.Add(nr);
501+
502+
// Now let's assume you wanted to package up the change to go to a server... sending it back as entity, not dt
503+
EntitySet<Person> peopleCopy = new();
504+
dt.DefaultView.ReconcileDataViewToEntitySet(peopleCopy);
505+
ss.AcceptAllChanges();
506+
507+
// Now let's assume we're applying updates, while on the server, back to our data, to get saved... does the row states imply 1 insert, 1 update and 1 delete? it should!
508+
peopleCopy.ReconcileEntitySetToEntitySet(people);
509+
var debugSS = CEFDebug.ReturnServiceScope();
510+
Assert.IsTrue((from a in ss.GetAllTracked() where a.GetRowState() == ObjectState.Modified select a).Count() == 1);
511+
Assert.IsTrue((from a in ss.GetAllTracked() where a.GetRowState() == ObjectState.Added select a).Count() == 1);
512+
Assert.IsTrue((from a in ss.GetAllTracked() where a.GetRowState() == ObjectState.Deleted select a).Count() == 1);
513+
}
514+
471515
[TestMethod]
472516
public void SingleItemCreateSave()
473517
{

BaseTests/SandboxTests.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,82 @@
77
using System.Collections.Generic;
88
using System.Linq;
99
using System.Text;
10+
using System.Threading.Tasks;
11+
using System.Threading;
1012

1113
namespace BaseTests
1214
{
15+
[TestClass]
16+
public class StandaloneTests
17+
{
18+
[TestMethod]
19+
public async Task ExecWithWaitAsync()
20+
{
21+
StringBuilder sb = new();
22+
23+
var d = async (CancellationToken ct) =>
24+
{
25+
sb.Append("point 0;");
26+
await Task.Delay(6000);
27+
sb.Append("point 1;");
28+
ct.ThrowIfCancellationRequested();
29+
sb.Append("point 2;");
30+
};
31+
32+
var r1 = await d.ExecuteWithMaxWaitAsync(5000);
33+
sb.Append($"result {r1};");
34+
sb = new();
35+
36+
var d2 = async (CancellationToken ct) =>
37+
{
38+
for (int i = 1; i < 6; ++i)
39+
{
40+
await Task.Delay(1000, ct);
41+
sb.Append($"point2 {i};");
42+
}
43+
};
44+
45+
var r2 = await d2.ExecuteWithMaxWaitAsync(5000);
46+
sb.Append($"result {r2};");
47+
sb = new();
48+
49+
var d3 = (CancellationToken ct) =>
50+
{
51+
sb.Append("point3a;");
52+
System.Threading.Thread.Sleep(100);
53+
sb.Append("point3b;");
54+
ct.ThrowIfCancellationRequested();
55+
sb.Append("point3c;");
56+
};
57+
58+
var r3 = await d3.ExecuteWithMaxWaitAsync(5000);
59+
sb.Append($"result {r3};");
60+
sb = new();
61+
62+
int c = 0;
63+
64+
var d4 = (CancellationToken ct) =>
65+
{
66+
sb.Append($"point4 {c};");
67+
++c;
68+
ct.ThrowIfCancellationRequested();
69+
if (c < 3)
70+
{
71+
throw new ApplicationException("some issue");
72+
}
73+
System.Threading.Thread.Sleep(100);
74+
ct.ThrowIfCancellationRequested();
75+
sb.Append("done;");
76+
ct.ThrowIfCancellationRequested();
77+
};
78+
79+
var r4 = await d4.ExecuteWithMaxWaitAsync(10000, true, null, 5);
80+
sb.Append($"result {r4};");
81+
82+
Console.WriteLine(sb.ToString());
83+
}
84+
}
85+
1386
public class SandboxTests : MarshalByRefObject
1487
{
1588
private const string DB_SERVER = @"(local)\sql2016";

CodexMicroORM.Core/Base/CEF.cs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ public static class CEF
7070
private static readonly object _lockKey = new();
7171
private static readonly object _lockPCT = new();
7272

73+
private static readonly ConcurrentDictionary<Type, List<string>> _toSaveProps = new(Globals.DefaultCollectionConcurrencyLevel, Globals.DefaultLargerDictionaryCapacity);
74+
private static readonly ConcurrentDictionary<Type, List<string>> _toSavePropsPersist = new(Globals.DefaultCollectionConcurrencyLevel, Globals.DefaultLargerDictionaryCapacity);
75+
private static readonly ConcurrentBag<string> _globalPropToSave = new();
76+
7377
#endregion
7478

7579
#region "Public methods"
@@ -504,6 +508,15 @@ public static void AcceptAllChanges()
504508
CurrentServiceScope.AcceptAllChanges();
505509
}
506510

511+
public static IEnumerable<(object item, string? message, int status)> DBSaveTransactional(DBSaveSettings? settings = null)
512+
{
513+
using var tx = NewTransactionScope();
514+
var rv = CurrentServiceScope.DBSave(settings);
515+
tx.CanCommit();
516+
tx.Dispose();
517+
return rv!;
518+
}
519+
507520
public static async Task<IEnumerable<(object item, string? message, int status)>> DBSaveTransactionalAsync(DBSaveSettings? settings = null)
508521
{
509522
IEnumerable<(object item, string? message, int status)>? rv = null;
@@ -516,6 +529,7 @@ await Task.Run(() =>
516529
using var tx = NewTransactionScope();
517530
rv = CurrentServiceScope.DBSave(settings);
518531
tx.CanCommit();
532+
tx.Dispose();
519533
}
520534
catch (Exception ex2)
521535
{
@@ -1112,6 +1126,104 @@ public static ICEFValidationHost CurrentValidationService(object? forObject = nu
11121126

11131127
#region "Internals"
11141128

1129+
private static bool IsPersistable(Type t)
1130+
{
1131+
if (t.IsValueType)
1132+
return true;
1133+
1134+
if (t.Equals(typeof(string)))
1135+
return true;
1136+
1137+
if (t.IsPrimitive)
1138+
return true;
1139+
1140+
if (t.IsSerializable)
1141+
return true;
1142+
1143+
return false;
1144+
}
1145+
1146+
internal static List<string> GetToSaveProperties(Type t, bool persisted)
1147+
{
1148+
List<string>? list = null;
1149+
1150+
if (persisted)
1151+
{
1152+
_toSavePropsPersist.TryGetValue(t, out list);
1153+
}
1154+
else
1155+
{
1156+
_toSaveProps.TryGetValue(t, out list);
1157+
}
1158+
1159+
if (list != null)
1160+
{
1161+
return list;
1162+
}
1163+
1164+
// Return all writeable CLR properties, key fields, FK role name fields
1165+
list = (from a in t.GetProperties() where a.CanWrite && IsPersistable(a.PropertyType) select a.Name)
1166+
.Union(_globalPropToSave)
1167+
.Union(from cr in KeyService.ResolveKeyDefinitionForType(t) select cr)
1168+
.Union((from cr in CEF.CurrentKeyService().GetRelationsForChild(t) select cr.ChildResolvedKey).SelectMany((p) => p))
1169+
.Union(from cr in CEF.CurrentDBService().GetPropertyGroupFields(t) select cr)
1170+
.ToList();
1171+
1172+
// For non-persisted, we can look 1 level deeper to identify nested props as well - we iterate properties that belong to the same assembly as the root object
1173+
if (!persisted)
1174+
{
1175+
list.AddRange((from a in t.GetProperties()
1176+
where a.PropertyType.Assembly == t.Assembly
1177+
select (from b in a.PropertyType.GetProperties()
1178+
where b.CanWrite && IsPersistable(b.PropertyType)
1179+
select b.Name)).SelectMany((p) => p));
1180+
}
1181+
1182+
var asvc = CEF.CurrentAuditService();
1183+
1184+
if (asvc != null)
1185+
{
1186+
if (!string.IsNullOrEmpty(asvc.LastUpdatedByField) && !list.Contains(asvc.LastUpdatedByField))
1187+
{
1188+
list.Add(asvc.LastUpdatedByField);
1189+
}
1190+
1191+
if (!string.IsNullOrEmpty(asvc.LastUpdatedDateField) && !list.Contains(asvc.LastUpdatedDateField))
1192+
{
1193+
list.Add(asvc.LastUpdatedDateField);
1194+
}
1195+
}
1196+
1197+
// Support idea of globally excluded properties (i.e. should never persist since might be things like EMailAddressPlain which is just a surrogate for encrypted EMailAddress)
1198+
var sl = (from a in list where !CEF.RegisteredPropertyNameTreatReadOnly.Contains(a) select a).Distinct().OrderBy((p) => p).ToList();
1199+
1200+
if (persisted)
1201+
{
1202+
_toSavePropsPersist[t] = sl;
1203+
}
1204+
else
1205+
{
1206+
_toSaveProps[t] = sl;
1207+
}
1208+
1209+
return sl;
1210+
}
1211+
1212+
internal static IDictionary<string, object?> GetFilteredProperties(Type t, IDictionary<string, object?> props, bool persisted)
1213+
{
1214+
Dictionary<string, object?> ret = new(props.Count);
1215+
1216+
foreach (var pn in GetToSaveProperties(t, persisted))
1217+
{
1218+
if (props.ContainsKey(pn))
1219+
{
1220+
ret[pn] = props[pn];
1221+
}
1222+
}
1223+
1224+
return ret;
1225+
}
1226+
11151227
private static Action<bool> InternalSubscribeAppEvents(Action<bool> subscriber)
11161228
{
11171229
lock (_appEventList)

CodexMicroORM.Core/Base/Collections.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public IEnumerable<KeyValuePair<TKey, TValue>> All()
157157
{
158158
if (bi != null)
159159
{
160-
foreach (var i in bi.Map)
160+
foreach (var i in bi.Map.ToArray())
161161
{
162162
yield return i;
163163
}

CodexMicroORM.Core/Base/Exceptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ public CEFTimeoutException()
180180
}
181181
}
182182

183+
public class CEFInvalidDataException : InvalidOperationException
184+
{
185+
public CEFInvalidDataException(string msg) : base(msg)
186+
{
187+
}
188+
}
189+
183190
public class CEFValidationException : ApplicationException
184191
{
185192
private readonly IEnumerable<(ValidationErrorCode error, string message)>? _messages = null;

0 commit comments

Comments
 (0)