Skip to content

Commit 18f58b5

Browse files
committed
+ ConnectionStringBase.ConnectionWrapper
1 parent b9dae91 commit 18f58b5

File tree

3 files changed

+330
-144
lines changed

3 files changed

+330
-144
lines changed

CodeJam.Main.Tests/ConnectionStrings/ConnectionStringTests.cs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
using System.ComponentModel;
33
using System.ComponentModel.DataAnnotations;
44
using System.Diagnostics.CodeAnalysis;
5+
using System.Linq;
6+
7+
using JetBrains.Annotations;
58

69
using NUnit.Framework;
710

@@ -12,7 +15,9 @@ namespace CodeJam.ConnectionStrings
1215
[SuppressMessage("ReSharper", "ObjectCreationAsStatement")]
1316
public class ConnectionStringTests
1417
{
15-
private static readonly DateTimeOffset _defaultDateTimeOffset = new DateTimeOffset(2010, 11, 12, 0, 0, 0, TimeSpan.Zero);
18+
private static readonly DateTimeOffset _defaultDateTimeOffset = new DateTimeOffset(
19+
new DateTime(2010, 11, 12),
20+
TimeSpan.Zero);
1621

1722
public class BaseConnectionString : ConnectionStringBase
1823
{
@@ -50,6 +55,14 @@ public DerivedConnectionString(string connectionString) : base(connectionString)
5055
set => SetValue(nameof(RequiredValue), value);
5156
}
5257

58+
// Never filled. Used to test roundtrip scenario
59+
[UsedImplicitly]
60+
public string OptionalValue
61+
{
62+
get => TryGetValue(nameof(OptionalValue));
63+
set => SetValue(nameof(OptionalValue), value);
64+
}
65+
5366
public DateTimeOffset? DateTimeOffsetValue
5467
{
5568
get => TryGetDateTimeOffsetValue(nameof(DateTimeOffsetValue));
@@ -144,6 +157,7 @@ public void TestGetPropertiesDerived()
144157
{
145158
var x = new DerivedConnectionString("IgnoredValue=aaa");
146159
AreEqual(x.RequiredValue, null);
160+
AreEqual(x.OptionalValue, null);
147161
AreEqual(x.BooleanValue, false);
148162
AreEqual(x.Int32Value, null);
149163
AreEqual(x.DateTimeOffsetValue, null);
@@ -164,14 +178,19 @@ public void TestPropertiesRoundtrip()
164178
};
165179

166180
var s = x.ToString();
167-
AreEqual(s, @"RequiredValue=""A; B=C'"""""";DateTimeOffsetValue=""11/12/2010 00:00:00 +00:00"";BooleanValue=True;Int32Value=-1024");
181+
AreEqual(
182+
s,
183+
@"RequiredValue=""A; B=C'"""""";BooleanValue=True;Int32Value=-1024;DateTimeOffsetValue=""11/12/2010 00:00:00 +00:00""");
168184

169185
var x2 = new DerivedConnectionString(s);
170186
AreEqual(x2.RequiredValue, x.RequiredValue);
171187
AreEqual(x2.BooleanValue, x.BooleanValue);
172188
AreEqual(x2.Int32Value, x.Int32Value);
173189
AreEqual(x2.DateTimeOffsetValue, x.DateTimeOffsetValue);
190+
174191
AreEqual(s, x2.ToString());
192+
IsTrue(x.EquivalentTo(x2));
193+
AreEqual(x.OrderBy(p => p.Key), x2.OrderBy(p => p.Key));
175194
}
176195

177196
[Test]
@@ -192,6 +211,15 @@ public void TestIgnoredProperties()
192211
That(ex.Message, Does.Contain("IgnoredValue"));
193212
}
194213

214+
[Test]
215+
public void TestInvalidProperties()
216+
{
217+
var x = new DerivedConnectionString(@"BooleanValue=aaa;Int32Value=bbb;DateTimeOffsetValue=ccc");
218+
Throws<FormatException>(() => x.BooleanValue.ToString());
219+
Throws<FormatException>(() => x.Int32Value?.ToString());
220+
Throws<FormatException>(() => x.DateTimeOffsetValue?.ToString());
221+
}
222+
195223
[Test]
196224
public void TestNonBrowsableProperties()
197225
{
@@ -210,6 +238,8 @@ public void TestNonBrowsableProperties()
210238
AreEqual(x2.BooleanValue, x.BooleanValue);
211239
AreEqual(x2.Int32Value, x.Int32Value);
212240
AreEqual(s, x2.ToString());
241+
IsFalse(x.EquivalentTo(x2));
242+
AreNotEqual(x.ToArray(), x2.ToArray());
213243

214244
s = x.GetBrowsableConnectionString(false);
215245
AreEqual(s, @"BooleanValue=True;Int32Value=-1024");
@@ -219,6 +249,8 @@ public void TestNonBrowsableProperties()
219249
AreEqual(x2.BooleanValue, x.BooleanValue);
220250
AreEqual(x2.Int32Value, x.Int32Value);
221251
AreEqual(s, x2.ToString());
252+
IsFalse(x.EquivalentTo(x2));
253+
AreNotEqual(x.ToArray(), x2.ToArray());
222254
}
223255
}
224256
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Data.Common;
4+
using System.Globalization;
5+
using System.Linq;
6+
using System.Reflection;
7+
using System.Text;
8+
using System.Threading;
9+
10+
using CodeJam.Collections;
11+
using CodeJam.Reflection;
12+
using CodeJam.Strings;
13+
using CodeJam.Targeting;
14+
15+
using JetBrains.Annotations;
16+
17+
namespace CodeJam.ConnectionStrings
18+
{
19+
partial class ConnectionStringBase
20+
{
21+
private class StringBuilderWrapper : DbConnectionStringBuilder
22+
{
23+
private const string _nonBrowsableValue = "...";
24+
25+
private static IReadOnlyDictionary<string, KeywordDescriptor> GetDescriptorsCore(Type type)
26+
{
27+
KeywordDescriptor GetDescriptor(PropertyInfo property) =>
28+
new KeywordDescriptor(
29+
property.Name,
30+
property.PropertyType,
31+
#if NET35_OR_GREATER || TARGETS_NETSTANDARD || TARGETS_NETCOREAPP
32+
property.IsRequired(),
33+
#else
34+
false,
35+
#endif
36+
property.IsBrowsable());
37+
38+
// Explicit ordering from most derived to base. Explanation:
39+
// The GetProperties method does not return properties in a particular order, such as alphabetical or declaration order.
40+
// Your code must not depend on the order in which properties are returned, because that order varies.
41+
var typeChain = Sequence.CreateWhileNotNull(
42+
type,
43+
t => t.GetBaseType() is var baseType && baseType != typeof(ConnectionStringBase)
44+
? baseType
45+
: null);
46+
var properties = typeChain
47+
.SelectMany(t => t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly));
48+
#if NET45_OR_GREATER || TARGETS_NETSTANDARD || TARGETS_NETCOREAPP
49+
var result = new Dictionary<string, KeywordDescriptor>(StringComparer.OrdinalIgnoreCase);
50+
#else
51+
var result = new DictionaryEx<string, KeywordDescriptor>(StringComparer.OrdinalIgnoreCase);
52+
#endif
53+
foreach (var prop in properties)
54+
{
55+
// DONTTOUCH: most derived property wins
56+
if (result.ContainsKey(prop.Name))
57+
continue;
58+
59+
result[prop.Name] = GetDescriptor(prop);
60+
}
61+
return result;
62+
}
63+
64+
private static readonly Func<Type, IReadOnlyDictionary<string, KeywordDescriptor>> _keywordsCache = Algorithms
65+
.Memoize(
66+
(Type type) => GetDescriptorsCore(type),
67+
LazyThreadSafetyMode.ExecutionAndPublication);
68+
69+
private readonly Type _descriptorType;
70+
71+
public StringBuilderWrapper(string connectionString, Type descriptorType)
72+
{
73+
_descriptorType = descriptorType;
74+
if (connectionString != null)
75+
ConnectionString = connectionString;
76+
}
77+
78+
[NotNull]
79+
public IReadOnlyDictionary<string, KeywordDescriptor> Keywords => _keywordsCache(_descriptorType);
80+
81+
[NotNull]
82+
public new string ConnectionString
83+
{
84+
get => base.ConnectionString;
85+
set
86+
{
87+
base.ConnectionString = value;
88+
if (value.NotNullNorEmpty())
89+
{
90+
foreach (var nameRequiredPair in Keywords.Where(p => p.Value.IsRequired))
91+
{
92+
if (!ContainsKey(nameRequiredPair.Key))
93+
throw CodeExceptions.Argument(
94+
nameof(ConnectionString),
95+
$"The value of required {nameRequiredPair.Key} connection string parameter is empty.");
96+
}
97+
}
98+
}
99+
}
100+
101+
/// <returns></returns>
102+
[NotNull, MustUseReturnValue]
103+
public string GetBrowsableConnectionString(bool includeNonBrowsable = false)
104+
{
105+
var builder = new StringBuilder();
106+
foreach (var browsablePair in Keywords)
107+
{
108+
if (!browsablePair.Value.IsBrowsable && !includeNonBrowsable)
109+
continue;
110+
111+
if (ShouldSerialize(browsablePair.Key) && TryGetValue(browsablePair.Key, out var value))
112+
{
113+
if (!browsablePair.Value.IsBrowsable)
114+
value = _nonBrowsableValue;
115+
var keyValue = Convert.ToString(value, CultureInfo.InvariantCulture);
116+
AppendKeyValuePair(builder, browsablePair.Key, keyValue);
117+
}
118+
}
119+
120+
return builder.ToString();
121+
}
122+
123+
public string GetStringValue(string keyword) => (string)this[keyword];
124+
125+
public bool TryGetStringValue(string keyword, out string value)
126+
{
127+
value = GetStringValue(keyword);
128+
return value != null;
129+
}
130+
131+
#region Use only allowed keywords
132+
/// <inheritdoc />
133+
public override object this[string keyword]
134+
{
135+
get
136+
{
137+
if (Keywords.ContainsKey(keyword))
138+
{
139+
TryGetValue(keyword, out var value);
140+
return value;
141+
}
142+
return base[keyword]; // exception for not supported keyword.
143+
}
144+
set
145+
{
146+
if (Keywords.TryGetValue(keyword, out var descriptor))
147+
{
148+
base[descriptor.Name] = value switch
149+
{
150+
DateTimeOffset x => x.ToInvariantString(),
151+
Guid x => x.ToInvariantString(),
152+
#if NET40_OR_GREATER || TARGETS_NETSTANDARD || TARGETS_NETCOREAPP
153+
TimeSpan x => x.ToInvariantString(),
154+
#else
155+
TimeSpan x => x.ToString(),
156+
#endif
157+
Uri x => x.ToString(),
158+
_ => value
159+
};
160+
}
161+
}
162+
}
163+
#endregion
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)