@@ -52,7 +52,9 @@ internal const string
5252 Ssl = "ssl" ,
5353 Version = "version" ,
5454 SetClientLibrary = "setlib" ,
55- Protocol = "protocol"
55+ Protocol = "protocol" ,
56+ ReadFrom = "readFrom" ,
57+ Az = "az"
5658 ;
5759
5860 private static readonly Dictionary < string , string > normalizedOptions = new [ ]
@@ -71,6 +73,8 @@ internal const string
7173 Version ,
7274 SetClientLibrary ,
7375 Protocol ,
76+ ReadFrom ,
77+ Az
7478 } . ToDictionary ( x => x , StringComparer . OrdinalIgnoreCase ) ;
7579
7680 public static string TryNormalize ( string value )
@@ -83,13 +87,11 @@ public static string TryNormalize(string value)
8387 }
8488 }
8589
90+ #region Private fields
8691 private bool ? ssl ;
87-
8892 private Proxy ? proxy ;
89-
9093 private RetryStrategy ? reconnectRetryPolicy ;
91-
92- private ReadFrom ? readFrom ;
94+ #endregion
9395
9496 /// <summary>
9597 /// Gets or sets whether connect/configuration timeouts should be explicitly notified via a TimeoutException.
@@ -240,11 +242,7 @@ public RetryStrategy? ReconnectRetryPolicy
240242 /// <summary>
241243 /// The read from strategy and Availability zone if applicable.
242244 /// </summary>
243- public ReadFrom ? ReadFrom
244- {
245- get => readFrom ;
246- set => readFrom = value ;
247- }
245+ public ReadFrom ? ReadFrom { get ; set ; }
248246
249247 /// <summary>
250248 /// Indicates whether endpoints should be resolved via DNS before connecting.
@@ -306,9 +304,9 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow
306304 ResponseTimeout = ResponseTimeout ,
307305 DefaultDatabase = DefaultDatabase ,
308306 reconnectRetryPolicy = reconnectRetryPolicy ,
309- readFrom = readFrom ,
310307 EndPoints = EndPoints . Clone ( ) ,
311308 Protocol = Protocol ,
309+ ReadFrom = ReadFrom
312310 } ;
313311
314312 /// <summary>
@@ -356,6 +354,10 @@ public string ToString(bool includePassword)
356354 Append ( sb , OptionKeys . ResponseTimeout , ResponseTimeout ) ;
357355 Append ( sb , OptionKeys . DefaultDatabase , DefaultDatabase ) ;
358356 Append ( sb , OptionKeys . Protocol , FormatProtocol ( Protocol ) ) ;
357+ if ( ReadFrom . HasValue )
358+ {
359+ FormatReadFrom ( sb , ReadFrom . Value ) ;
360+ }
359361
360362 return sb . ToString ( ) ;
361363
@@ -401,7 +403,7 @@ private void Clear()
401403 ClientName = User = Password = null ;
402404 ConnectTimeout = ResponseTimeout = null ;
403405 ssl = null ;
404- readFrom = null ;
406+ ReadFrom = null ;
405407 reconnectRetryPolicy = null ;
406408 EndPoints . Clear ( ) ;
407409 }
@@ -410,6 +412,9 @@ private void Clear()
410412
411413 private ConfigurationOptions DoParse ( string configuration , bool ignoreUnknown = true )
412414 {
415+ ReadFromStrategy ? tempReadFromStrategy = null ;
416+ string ? tempAz = null ;
417+
413418 if ( configuration == null )
414419 {
415420 throw new ArgumentNullException ( nameof ( configuration ) ) ;
@@ -463,6 +468,12 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown =
463468 case OptionKeys . ResponseTimeout :
464469 ResponseTimeout = OptionKeys . ParseInt32 ( key , value ) ;
465470 break ;
471+ case OptionKeys . ReadFrom :
472+ tempReadFromStrategy = CheckReadFromValue ( value ) ;
473+ break ;
474+ case OptionKeys . Az :
475+ tempAz = CheckAzValue ( value ) ;
476+ break ;
466477 default :
467478 if ( ! ignoreUnknown ) throw new ArgumentException ( $ "Keyword '{ key } ' is not supported.", key ) ;
468479 break ;
@@ -476,9 +487,71 @@ private ConfigurationOptions DoParse(string configuration, bool ignoreUnknown =
476487 }
477488 }
478489 }
490+
491+ // Validate ReadFrom configuration after all parameters have been parsed
492+ if ( tempReadFromStrategy . HasValue )
493+ {
494+ ReadFrom = SetReadFrom ( tempReadFromStrategy , tempAz ) ;
495+ }
496+
479497 return this ;
480498 }
481499
500+ private string CheckAzValue ( string az )
501+ {
502+ if ( string . IsNullOrWhiteSpace ( az ) )
503+ {
504+ throw new ArgumentException ( "Availability zone cannot be empty or whitespace" ) ;
505+ }
506+ return az ;
507+ }
508+
509+ private ReadFrom ? SetReadFrom ( ReadFromStrategy ? strategy , string ? az )
510+ {
511+ if ( strategy . HasValue )
512+ {
513+ // Use ReadFrom constructors based on strategy type - the constructors contain the validation logic
514+ return strategy . Value switch
515+ {
516+ ReadFromStrategy . AzAffinity or ReadFromStrategy . AzAffinityReplicasAndPrimary => new ReadFrom ( strategy . Value , az ! ) ,
517+ ReadFromStrategy . Primary or ReadFromStrategy . PreferReplica => new ReadFrom ( strategy . Value ) ,
518+ _ => throw new ArgumentException ( $ "ReadFrom strategy '{ strategy . Value } ' is not supported. Valid strategies are: Primary, PreferReplica, AzAffinity, AzAffinityReplicasAndPrimary") ,
519+ } ;
520+ }
521+ return null ;
522+ }
523+
524+ private ReadFromStrategy CheckReadFromValue ( string readFrom )
525+ {
526+ if ( string . IsNullOrWhiteSpace ( readFrom ) )
527+ {
528+ throw new ArgumentException ( "ReadFrom strategy cannot be empty" ) ;
529+ }
530+
531+ try
532+ {
533+ return Enum . Parse < ReadFromStrategy > ( readFrom , ignoreCase : true ) ;
534+ }
535+ catch ( ArgumentException )
536+ {
537+ throw new ArgumentException ( $ "ReadFrom strategy '{ readFrom } ' is not supported. Valid strategies are: Primary, PreferReplica, AzAffinity, AzAffinityReplicasAndPrimary") ;
538+ }
539+ }
540+
541+ /// <summary>
542+ /// Formats a ReadFrom struct to its string representation and appends it to the StringBuilder.
543+ /// </summary>
544+ /// <param name="sb">The StringBuilder to append to.</param>
545+ /// <param name="readFromConfig">The ReadFrom configuration to format.</param>
546+ private static void FormatReadFrom ( StringBuilder sb , ReadFrom readFromConfig )
547+ {
548+ Append ( sb , OptionKeys . ReadFrom , readFromConfig . Strategy . ToString ( ) ) ;
549+ if ( ! string . IsNullOrWhiteSpace ( readFromConfig . Az ) )
550+ {
551+ Append ( sb , OptionKeys . Az , readFromConfig . Az ) ;
552+ }
553+ }
554+
482555 /// <summary>
483556 /// Specify the connection protocol type.
484557 /// </summary>
0 commit comments